From e0c14c7161e782fe9a5930e56450e95af63031a5 Mon Sep 17 00:00:00 2001 From: DomSRx <52894540+DomSRx@users.noreply.github.com> Date: Sun, 26 Apr 2026 09:41:24 +0200 Subject: [PATCH] Add files via upload After test this version is working correctly With 2 equipements installed When 1 equipment unplugged - the other is still polled - the polling error message and the equipment action error message are detailed --- broadlinkd.py | 269 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 broadlinkd.py diff --git a/broadlinkd.py b/broadlinkd.py new file mode 100644 index 0000000..257ad01 --- /dev/null +++ b/broadlinkd.py @@ -0,0 +1,269 @@ +# -*- coding: utf-8 -*- +# This file is part of Jeedom. +# +# Jeedom is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Jeedom is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Jeedom. If not, see . + +import logging +import sys +import os +import time +import datetime +import signal +import argparse +import traceback +import json +from broadlink import broadlink, rm2, a1, mp1, sp2, rm4 +import globals + +from jeedom.jeedom import jeedom_com, jeedom_socket, jeedom_utils, JEEDOM_SOCKET_MESSAGE + +# ---------------------------------------------------------------------------- + + +def listen(): + logging.debug("Start listening...") + jeedom_socket.open() + try: + while 1: + time.sleep(0.02) + read_socket() + read_broadlink() + except KeyboardInterrupt: + shutdown() + + +def read_socket(): + try: + if not JEEDOM_SOCKET_MESSAGE.empty(): + logging.debug("Message received in socket JEEDOM_SOCKET_MESSAGE") + message = JEEDOM_SOCKET_MESSAGE.get().decode('utf-8') + message = json.loads(message) + if message['apikey'] != _apikey: + logging.error("Invalid apikey from socket: %s", message) + return + if message['cmd'] == 'add': + logging.debug('Add device : %s', message['device']) + if 'mac' in message['device']: + globals.KNOWN_DEVICES[message['device']['mac']] = message['device'] + elif message['cmd'] == 'remove': + logging.debug('Remove device : %s', message['device']) + if 'mac' in message['device']: + del globals.KNOWN_DEVICES[message['device']['mac']] + elif message['cmd'] == 'learnin': + logging.debug('Enter in learn mode') + globals.LEARN_MODE = True + globals.JEEDOMCOM.send_change_immediate({'learn_mode': 1}) + devices = broadlink.discover(timeout=5) + logging.debug("found %s", devices) + globals.LEARN_MODE = False + globals.JEEDOMCOM.send_change_immediate({'learn_mode': 0}) + for device in devices: + type = device.type + devtype = device.devtype + ip = device.host[0] + port = device.host[1] + mac = device.mac.hex() + reversemac = "".join(reversed([mac[i:i+2] for i in range(0, len(mac), 2)])) + globals.JEEDOMCOM.add_changes('devices::' + mac, {'type': type, 'ip': ip, 'mac': mac, 'reversemac': reversemac, 'port': port, 'learn': 1, 'devtype': devtype}) + elif message['cmd'] == 'send': + if 'mac' in message['device']: + logging.debug('Send command') + send_broadlink(message) + except Exception as ex: + logging.error(ex) +# ---------------------------------------------------------------------------- + + +def read_broadlink(): + now = datetime.datetime.now(datetime.timezone.utc) + result = {} + for device in list(globals.KNOWN_DEVICES): + dev = globals.KNOWN_DEVICES[device] + mac = dev['mac'] + if mac in globals.LAST_TIME_READ and now < (globals.LAST_TIME_READ[mac]+datetime.timedelta(milliseconds=int(dev['delay'])*1000)): + continue + if mac[-3:] == 'sub': + continue + try: + globals.LAST_TIME_READ[mac] = now + if dev['type'] == 'rm2': + logging.debug('Handling RM2 for ' + dev['name']) + result = rm2.read_rm2(dev) + elif dev['type'] == 'a1': + logging.debug('Handling A1 for ' + dev['name']) + result = a1.read_a1(dev) + elif dev['type'] == 'sp2': + logging.debug('Handling SP2 for ' + dev['name']) + result = sp2.read_sp2(dev) + elif dev['type'] == 'mp1': + logging.debug('Handling MP1 for ' + dev['name']) + result = mp1.read_mp1(dev) + elif dev['type'] == 'rm4': + logging.debug('Handling RM4 for ' + dev['name']) + result = rm4.read_rm4(dev) + if result: + if mac in globals.LAST_STATE and result == globals.LAST_STATE[mac]: + continue + else: + globals.LAST_STATE[mac] = result + globals.JEEDOMCOM.add_changes('devices::'+mac, result) + except Exception as ex: + err = str(ex) + if err in ('timed out', 'The device is offline'): + logging.error('The device is offline [nom:"%s" | mac:%s | ip:%s]', + dev.get('name','?'), mac, dev.get('ip','?')) + else: + logging.error('Erreur [nom:"%s" | mac:%s] : %s', + dev.get('name','?'), mac, ex) + +# ---------------------------------------------------------------------------- + + +def send_broadlink(message): + result = {} + dev = message['device'] + device_label = '[nom:"{}" | mac:{} | ip:{}]'.format( + dev.get('name', '?'), dev.get('mac', '?'), dev.get('ip', '?') + ) + try: + if message['cmdType'] == 'refresh': + if dev['type'] == 'rm2': + result = rm2.read_rm2(dev) + elif dev['type'] == 'a1': + result = a1.read_a1(dev) + elif dev['type'] == 'mp1': + result = mp1.read_mp1(dev) + elif dev['type'] == 'sp2': + result = sp2.read_sp2(dev) + elif dev['type'] == 'rm4': + result = rm4.read_rm4(dev) + if result: + if dev['mac'] in globals.LAST_STATE and result == globals.LAST_STATE[dev['mac']]: + return + else: + globals.LAST_STATE[dev['mac']] = result + globals.JEEDOMCOM.add_changes('devices::'+dev['mac'], result) + return + elif dev['type'] == 'rm2': + if message['cmdType'] == 'learn': + result = rm2.learn_rm2(dev) + if result: + globals.JEEDOMCOM.add_changes('devices::'+dev['mac'], result) + else: + rm2.send_rm2(dev) + elif dev['type'] == 'rm4': + if message['cmdType'] == 'learn': + result = rm4.learn_rm4(dev) + if result: + globals.JEEDOMCOM.add_changes('devices::'+dev['mac'], result) + else: + rm4.send_rm4(dev) + elif dev['type'] == 'mp1': + result = mp1.send_mp1(dev) + if result: + globals.JEEDOMCOM.add_changes('devices::'+dev['mac'], result) + elif dev['type'] == 'sp2': + result = sp2.send_sp2(dev) + if result: + globals.JEEDOMCOM.add_changes('devices::'+dev['mac'], result) + except Exception as ex: + err = str(ex) + if err in ('timed out', 'The device is offline'): + logging.error('The device is offline ' + device_label) + else: + logging.error('Erreur envoi commande vers %s : %s', device_label, ex) + return + + +# ---------------------------------------------------------------------------- + +def handler(signum=None, frame=None): + logging.debug("Signal %i caught, exiting...", signum) + shutdown() + + +def shutdown(): + logging.debug("Shutdown") + logging.debug("Removing PID file %s", _pidfile) + try: + os.remove(_pidfile) + except Exception as ex: + logging.warning("Error while removing PID file: %s", ex) + try: + jeedom_socket.close() + except Exception as ex: + logging.warning("Error while closing socket: %s", ex) + logging.debug("Exit 0") + sys.stdout.flush() + os._exit(0) + +# ---------------------------------------------------------------------------- + + +_log_level = "error" +_socket_port = 55013 +_socket_host = '127.0.0.1' +_pidfile = '/tmp/broadlinkd.pid' +_apikey = '' +_callback = '' +_cycle = 0.3 + +parser = argparse.ArgumentParser(description='Broadlink Daemon for Jeedom plugin') +parser.add_argument("--socketport", help="Socketport for server", type=str) +parser.add_argument("--sockethost", help="Sockethost for server", type=str) +parser.add_argument("--loglevel", help="Log Level for the daemon", type=str) +parser.add_argument("--callback", help="Callback", type=str) +parser.add_argument("--apikey", help="Apikey", type=str) +parser.add_argument("--cycle", help="Cycle to send event", type=str) +parser.add_argument("--pid", help="Pid file", type=str) +args = parser.parse_args() + +if args.socketport: + _socket_port = int(args.socketport) +if args.loglevel: + _log_level = args.loglevel +if args.callback: + _callback = args.callback +if args.apikey: + _apikey = args.apikey +if args.cycle: + _cycle = float(args.cycle) +if args.pid: + _pidfile = args.pid + +jeedom_utils.set_log_level(_log_level) + +logging.info('Start broadlinkd') +logging.info('Log level: %s', _log_level) +logging.debug('Socket port: %s', _socket_port) +logging.debug('Socket host: %s', _socket_host) +logging.debug('PID file: %s', _pidfile) +logging.debug('Callback: %s', _callback) +logging.debug('Cycle: %s', _cycle) + +signal.signal(signal.SIGINT, handler) +signal.signal(signal.SIGTERM, handler) + +try: + jeedom_utils.write_pid(_pidfile) + globals.JEEDOMCOM = jeedom_com(apikey=_apikey, url=_callback, cycle=_cycle) + if not globals.JEEDOMCOM.test(): + logging.error('Network communication issues. Please fix your Jeedom network configuration.') + shutdown() + jeedom_socket = jeedom_socket(port=_socket_port, address=_socket_host) + listen() +except Exception as ex: + logging.error('Fatal error : %s', ex) + logging.debug(traceback.format_exc()) + shutdown() \ No newline at end of file