''' Created on 23 janv. 2026 @author: denis ''' # process.py #from django.utils.translation import gettext_lazy as _ import os os.environ['BOARD_DEVICE']="RPI" import json from threading import Thread, Event from asgiref.sync import async_to_sync #, sync_to_async from channels.layers import get_channel_layer from django.conf import settings from django.db import transaction from celery import Task from celery.exceptions import Ignore from celery.utils.log import get_task_logger from redis import Redis from modules import gpiolib as gpio, devices as dev from . import models logger = get_task_logger(__name__) redisDB = Redis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=0, decode_responses=True) class RelayManager: def __init__(self): self.shift_out_register = gpio.ShiftOutRegister74HC595( latch_pin=settings.CTL_LATCH_PIN, clock_pin=settings.CTL_CLOCK_PIN, data_pin=settings.CTL_DATA_PIN, number=settings.CTL_NUMBER, ) def create_relay(self, device, publish=None): return dev.Relay(self.shift_out_register, device.to_register(), publish=publish) relay_manager = RelayManager() class RelayProcess(Task): def __init__(self, uuid): super().__init__() self.channel_layer = get_channel_layer() self.uuid = uuid self.group = f'relay_{uuid}' self.stop_event = Event() self.heartbeat = 10 self.relay = None self.device = None def __call__(self, uuid, *args, **kwargs): self.uuid = uuid return self.start(*args, **kwargs) def redis_pub(self, state, **msg): async_to_sync(self.channel_layer.group_send)( self.group, { "type": 'relay.message', "text": { 'topic': state, **msg } } ) self.mqtt_pub(state, **msg) def mqtt_pub(self, state, **payload): topic = f"relay/{self.uuid}/{state}" payload.pop("uuid", None) redisDB.publish('mqtt_pub', json.dumps({"type": "mqtt", "topic": topic, "payload": payload})) #logger.info(f"mqtt_pub {topic}: {payload}") def start(self, *args, **kwargs): try: with transaction.atomic(): self.device = models.Device.objects.select_for_update().get(uuid=self.uuid) self.stop_event.clear() self.start_services() except Exception as e: logger.error(f"Relay error {self.uuid}: {e}") raise Ignore() def stop(self): try: self.redis_pub('state', stop="done") self.stop_event.set() logger.info(f"Relay {self.uuid} stopped.") Event().wait(1.0) finally: self.stop_event.set() gpio.IO_CLEANUP() def start_services(self): Thread(target=self._listen_to_redis, daemon=True).start() Thread(target=self._relay_process, daemon=True).start() def _listen_to_redis(self): try: logger.info(f"RelayProcess {self.group}: listen via redisDB") pubsub = redisDB.pubsub() pubsub.subscribe(f"{self.group}", "relay_broadcast", "mqtt_message") try: for message in pubsub.listen(): #logger.info(f"{message}") if self.stop_event.is_set(): break cmd = json.loads(str(message.get('data'))) if not isinstance(cmd, dict): continue if cmd["type"]=="relay" or cmd["type"]=="mqtt": topic = cmd["topic"] if topic == "start": duration = cmd.get("value", 0) self.relay.relay_start(duration=duration) elif topic == "stop": self.relay.relay_stop() elif topic in ["status", "init"]: self.relay.relay_status() elif topic == "halt": self.relay.relay_halt() elif topic == "restart": self.relay.relay_restart() except Exception as e: logger.error(f'listen_to_redis: {e}') finally: pubsub.unsubscribe() pubsub.close() def _relay_process(self): try: logger.info(f"RelayProcess Start {self.uuid}") self.relay = relay_manager.create_relay(self.device, publish=self.redis_pub) self.relay.relay_stop() while not self.stop_event.is_set(): try: Event().wait(self.device.heartbeat) self.relay.relay_status() except Exception as e: logger.error(f'====> {e}') Event().wait(1.0) except Exception as e: logger.error(f"Relay {self.uuid}: {e}") finally: logger.info(f"End of relay process: {self.uuid}") self.redis_pub('state', relay="stop")