#!/usr/bin/env python3
"""
Entrixy WebSocket-клиент для Raspberry Pi.

Подключается к wss://entrixy.com/ws как device, принимает команды "open"
из приложения Entrixy и на короткое время (типично 1 секунда) замыкает
реле на GPIO — это симулирует нажатие кнопки «Открыть» на плате привода
ворот, шлагбаума или домофона.

Зависимости:
    pip install websockets RPi.GPIO   # или gpiozero вместо RPi.GPIO

Регистрация устройства в Entrixy:
    Настройки → Объекты → Добавить → Устройство (WebSocket) →
    скопируйте device_key и device_secret в раздел CONFIG ниже.

Конфигуратор в браузере (генерирует этот файл с подставленными значениями):
    https://entrixy.com/esp/socket/
"""

import asyncio
import json
import logging
import ssl
import sys

import websockets

# ─────────── CONFIG ───────────
DEVICE_KEY    = "PASTE_DEVICE_KEY_HERE"
DEVICE_SECRET = "PASTE_DEVICE_SECRET_HERE"
WS_URL        = "wss://entrixy.com/ws"

RELAY_GPIO       = 17       # BCM-нумерация. Пин подключён к управляющему входу реле.
RELAY_ACTIVE_LOW = False    # True если реле срабатывает логическим 0 (большинство дешёвых модулей)
PULSE_MS         = 1000     # Длительность импульса на реле, мс. Типично 500–2000 для gate-кнопки.
RECONNECT_SEC    = 5        # Пауза перед переподключением при разрыве

# ─────────── GPIO ───────────
try:
    import RPi.GPIO as GPIO
    GPIO_AVAILABLE = True
except ImportError:
    GPIO_AVAILABLE = False
    print("RPi.GPIO not installed — running in dry-run mode (no hardware)")

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("entrixy")


def relay_init():
    if not GPIO_AVAILABLE:
        return
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(RELAY_GPIO, GPIO.OUT)
    GPIO.output(RELAY_GPIO, GPIO.HIGH if RELAY_ACTIVE_LOW else GPIO.LOW)


def relay_pulse():
    log.info("relay → ON")
    if GPIO_AVAILABLE:
        GPIO.output(RELAY_GPIO, GPIO.LOW if RELAY_ACTIVE_LOW else GPIO.HIGH)
    asyncio.create_task(_relay_off_after(PULSE_MS / 1000.0))


async def _relay_off_after(sec):
    await asyncio.sleep(sec)
    log.info("relay → OFF")
    if GPIO_AVAILABLE:
        GPIO.output(RELAY_GPIO, GPIO.HIGH if RELAY_ACTIVE_LOW else GPIO.LOW)


# ─────────── WebSocket loop ───────────
async def session():
    ssl_ctx = ssl.create_default_context()
    async with websockets.connect(WS_URL, ssl=ssl_ctx, ping_interval=20) as ws:
        await ws.send(json.dumps({
            "type": "device_hello",
            "device_key": DEVICE_KEY,
            "device_secret": DEVICE_SECRET,
        }))
        log.info("device_hello sent")

        async for raw in ws:
            try:
                msg = json.loads(raw)
            except Exception:
                continue
            t = msg.get("type")
            if t == "device_ok":
                log.info("auth OK")
            elif t == "ping":
                await ws.send(json.dumps({"type": "pong"}))
            elif t == "error":
                log.warning("server error: %s", msg.get("reason", "?"))
            elif t == "device_command":
                action = msg.get("action", "")
                command_id = msg.get("command_id", "")
                log.info("device_command action=%s command_id=%s", action, command_id)
                if action == "open":
                    relay_pulse()
                    await ws.send(json.dumps({
                        "type": "device_status",
                        "command_id": command_id,
                        "level": "success",
                        "message": "Открыто",
                        "final": True,
                    }))
                else:
                    await ws.send(json.dumps({
                        "type": "device_status",
                        "command_id": command_id,
                        "level": "warning",
                        "message": "Неизвестная команда",
                        "final": True,
                    }))


async def main():
    relay_init()
    while True:
        try:
            await session()
        except Exception as e:
            log.warning("session error: %s — reconnecting in %ds", e, RECONNECT_SEC)
            await asyncio.sleep(RECONNECT_SEC)


if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        if GPIO_AVAILABLE:
            GPIO.cleanup()
        sys.exit(0)
