Premessa
Mi perdonerete, se il titolo riferisce a Raspberry, mentre per la programmazione è stata usata una SBC, Small Computer Board, Orange PI, modello One Plus H6, ma, mio malgrado, l’ultimo Raspberry in mio possesso ha gettato la spugna.
La soluzione si applica, comunque, a tutte le SBC, per le quali è stata scritta e/o adattata la libreria per il controllo GPIO (General Purpose, Input, Output); RPi.GPIO per Raspberry PI, OPi.GPIO per Orange PI, etc.
Il progetto
Il progetto nasce dalla fusione di altri due precedenti, Multipresa smart MQTT integrata in Home Assistant e Multipresa smart con Raspberry PI che hanno ispirato lo sviluppo di questo codice che all’occorrenza può essere esteso e adattato ad esigenze analoghe.
L'obiettivo
L’obiettivo è stato ottenere una scheda relay, comandata tramite comandi MQTT, pienamente integrata nella piattaforma Home Assistant, in grado di essere il più possibile resiliente e capace di ripristinare lo stato di esercizio al variare delle condizione dell’ambiente che si possono creare, come l’assenza di rete, il riavvio del server MQTT o il riavvio della stessa piattaforma Home Assistant.
Il codice
relayd.py - Il deamon (demone) o servizio Linux
Relayd.py è il cuore del sistema, viene eseguito all’avvio dell’ SBC e attende di ricevere messaggi dalle code MQTT relativi a ciascun relay, configurato nel un file JSON. Quando si eseguono i vari comandi in Home Assistant, per un device che utilizza MQTT, questi sono inviati alla coda alla quale il device viene “subscripted” (abbonato) per uno o più “topic” (argomento) che riguardano il comando e il cambio di stato avvenuto a seguito del comando appena eseguito.
# ----------------------------------------------------------------------------------
# - Script file name : relayd..py
# - Author : Nicola Montemurro
# - DNS administrator : Nicola Montemurro - Tel. xxx, Mobile: xxx
# - Create : 18.01.2025
# - Last Update : 01.02.2025
# - Description :
# - Position : /usr/local/script/python
# - note : NON modificare senza AUTORIZZAZIONE dell'AMMINISTRATORE
# -----------------------------------------------------------------------------------
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
# PAHO Usage and API ==> https://eclipse.dev/paho/files/paho.mqtt.python/html/index.html
# MQTT Reason Code ==> https://www.emqx.com/en/blog/mqtt5-new-features-reason-code-and-ack
### ORANGPI.ONEPLUS GPIOs
# ORANGEPI.ONEPLUS IN USE
# |---------------|-----|
# | PIN GPI | |
# | | |
# | 3 | 230 | * |
# | 5 | 229 | * |
# | 7 | 228 | |
# | 8 | 117 | * |
# | 10 | 118 | * |
# | 11 | 120 | |
# | 12 | 73 | |
# | 13 | 119 | |
# | 15 | 122 | |
# | 16 | 72 | |
# | 18 | 71 | |
# | 19 | 66 | |
# | 21 | 67 | * |
# | 22 | 121 | * |
# | 23 | 64 | * |
# | 24 | 69 | * |
# | 26 | 227 | |
# | | | |
# |---------------|-----|
'''
import orangepi.oneplus
from OPi import GPIO
#from OPi import sysfs
from time import sleep
from paho.mqtt import client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes
#from paho.mqtt.enums import CallbackAPIVersion, ConnackCode, LogLevel, MessageState, MessageType, MQTTErrorCode, MQTTProtocolVersion, PahoClientMode, _ConnectionState
from paho.mqtt.enums import CallbackAPIVersion, MQTTErrorCode, _ConnectionState
import argparse
import logging
from logging.handlers import RotatingFileHandler
import time
import json
import uuid
import sys
import os
GPIO.setmode(orangepi.oneplus.BOARD)
GPIO.setwarnings(False)
# client ID
cid = str(uuid.uuid4())
# mqtt client
mqttc = None
# mqttc state
isconnected = False
# json file
json_config_file = None
# json object
config=None
# channel instant states list
chan_states = []
# channel pin list
chan_pins = []
# channel name list
chan_names = []
if sys.version_info < (3,):
class error(IOError): ...
else:
error = OSError
def init_json():
scriptdir = os.path.dirname(os.path.abspath(__file__))
def_json_file = os.path.splitext(os.path.basename(__file__))[0] + '.json'
global json_config_file
if args.config:
json_config_file = args.config
else:
json_config_file = os.path.join(scriptdir, def_json_file)
try:
with open(json_config_file, "r") as config_file:
config = json.load(config_file)
except FileNotFoundError:
print("Content-Type: text/plain\n")
print(f"ERROR: Configuration file not found or invalid JSON.")
return[]
except json.JSONDecodeError:
print(f"ERROR: '{json_config_file}' invalid JSON.")
logging.error(f"Error copying {json_config_file}: ")
return []
return config
def validate_config(_config):
if 'channels' not in _config:
logging.error("Missing channels in configuration.")
return False
if ('mqtt' not in _config or 'host' not in _config['mqtt'] or 'port' not in _config['mqtt'] or
'username' not in _config['mqtt'] or 'password' not in _config['mqtt']):
logging.error("json: Invalid mqtt configuration.")
return False
if ('mqttc' not in _config or 'bind_address' not in _config['mqttc']):
logging.error("json: Invalid mqttc configuration.")
return False
if 'log' not in _config or 'log_dir' not in _config['log'] or 'log_file' not in _config['log']:
print("json: Invalid logging configuration.")
logging.error("json: Invalid logging configuration.")
return False
return True
def init_logging(_config):
if validate_config:
log_file = os.path.join(_config['log']['log_dir'], _config['log']['log_file'])
# File Logger
#file_handler = RotatingFileHandler(log_file, maxBytes=10*1024*1024, backupCount=5)
file_handler = RotatingFileHandler(log_file, _config['log']['max_size_MB']*1024*1024, _config['log']['rotate_count'])
file_handler.setLevel(getattr(logging, args.log_level.upper(), logging.INFO))
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[file_handler])
logging.info("Logging is set up. Log file created at: {}".format(log_file))
return True
def init_channels():
global chan_states
global chan_names
global chan_pins
for name, channel in config['channels'].items():
pin = channel['rpi_pin']
json_init_state = channel['rpi_init_state']
# init lists
chan_names.append(name)
chan_pins.append(pin)
if ((json_init_state == "on") or (json_init_state == "0") or (json_init_state == 0) or (json_init_state == "False")):
# LOW = on
GPIO.setup(pin, GPIO.OUT, GPIO.LOW)
# init states list with channel current state
chan_states.append(GPIO.LOW)
#chan_states.append(0)
elif ((json_init_state == "off") or (json_init_state == "1") or (json_init_state == 1) or (json_init_state == "True")):
# HIGH = off
print(str(pin))
GPIO.setup(pin, GPIO.OUT, GPIO.HIGH)
print(str(pin))
# init states list with channel current state
chan_states.append(GPIO.HIGH)
#chan_states.append(1)
else:
# HIGH = off
#GPIO.output(pin, GPIO.HIGH)
pass
sleep(0.1)
return[]
def channel_on(client, pin):
# here switch pin and SET state on list
state = GPIO.LOW
GPIO.output(pin, state)
idx = chan_pins.index(pin)
chan_states[idx] = state
print("pin: " + str(pin) + ", state: " + str(state))
def channel_off(client, pin):
# here switch pin and SET state on list
state = GPIO.HIGH
GPIO.output(pin, state)
idx = chan_pins.index(pin)
chan_states[idx] = state
print("pin: " + str(pin) + ", state: " + str(state))
def on_connect(client, userdata, flags, rc, properties):
print("function on_connect")
logging.debug("called function function on_connect")
global isconnected
if flags.session_present:
# ...
pass
if rc == 0:
# success connect
isconnected = True
subscribe(client)
print("Client MQTT Connected (code: " + str(rc) + ")")
logging.info("Client MQTT Connected (code: " + str(rc) + ")")
if rc > 0:
# error processing
isconnected = False
return []
def on_connect_fail(client, userdata):
print("Called function on_connect_fail - Userdata: " + str(userdata))
pass
def on_disconnect(client, userdata, flags, rc, properties):
print("Called function on_disconnect - Error: " + str(rc))
logging.debug("Called function on_disconnect - Error: " + str(rc))
global isconnected
if rc == 0:
# success disconnect
client.loop_stop()
client.disconnect()
isconnected = False
pass
elif rc == MQTTErrorCode.MQTT_ERR_CONN_LOST:
# error processing
isconnected = False
else:
isconnected = False
return[]
def on_publish(client, userdata, msd, rc, properties):
print("Called function on_publish")
logging.debug("Called function on_publish")
print("data published \n")
return []
def on_message(client, userdata, msg):
print("userdata: " + str(userdata))
print("msg: " + str(msg))
logging.debug("Called function on_message")
logging.info("userdata: " + str(userdata))
logging.info("msg: " + str(msg))
print("Received " + msg.payload.decode() + " from topic: " + msg.topic)
logging.info("Received " + msg.payload.decode() + " from topic: " + msg.topic)
for name, channel in config['channels'].items():
pin = channel['rpi_pin']
cmdtopic = channel['mqtt_msg_command_topic']
statetopic = channel['mqtt_msg_state_topic']
if msg.topic == cmdtopic:
if msg.payload.decode() == "on":
channel_on(client, pin)
elif msg.payload.decode() == "off":
channel_off(client, pin)
else:
print( "decode " + msg.topic + " failed")
if msg.topic == "homeassistant/status":
if msg.payload.decode() == "online":
hastatus = True
update_all_states_and_publish(client)
elif msg.payload.decode() == "offline":
hastatus = False
else:
print( "decode " + msg.topic + " failed")
return []
def on_subscribe(client, userdata, mid, rc_list, properties):
print("Called function on_subscribe")
if rc_list[0].is_failure:
print(f"Broker rejected the subscription: {rc_list[0]}")
logging.debug(f"Broker rejected the subscription: {rc_list[0]}")
else:
print(f"Broker granted the following QoS: {rc_list[0].value}")
logging.debug(f"Broker granted the following QoS: {rc_list[0].value}")
return rc_list[0].value
def on_unsubscribe(client, userdata, mid, rc_list, properties):
if len(rc_list) == 0 or not rc_list[0].is_failure:
print("Unsubscribe succeeded (if SUBACK is received in MQTTv3 it success)")
logging.debug("Unsubscribe succeeded (if SUBACK is received in MQTTv3 it success)")
else:
print(f"Broker replied with failure: {reason_code_list[0]}")
logging.debug(f"Broker replied with failure: {reason_code_list[0]}")
client.disconnect()
def publish(client, userdata, msg):
print("function on_publish")
logging.info("Called function publish")
msg_count = 0
while True:
sleep(0.5)
msg = "messages: %s" % (msg_count)
ret = client.publish(topic, msg)
# ret: [0, 1]
state = ret[0]
if state == 0:
print("messages: %s" % (msg_count) + " sent at " + topic)
logging.info("messages: %s" % (msg_count) + " sent at " + topic)
else:
print("Failed to send messages to topic " + topic)
logging.error("Failed to send messages to topic " + topic)
msg_count += 1
return ret
def subscribe(client):
print("function subscribe")
logging.info("Called function subscribe")
for name, channel in config['channels'].items():
cmdtopic = channel['mqtt_msg_command_topic']
print(cmdtopic)
ret = client.subscribe(cmdtopic, 0)
# not implemented yet
# for name, channel in config['channels'].items():
# statetopic = channel['mqtt_msg_state_topic']
# print(statetopic)
# ret = client.subscribe(statetopic, 0)
update_state_and_publish(client, name)
client.subscribe("homeassistant/status", 0)
return ret[0]
def connect_to_broker(client):
print("function connect_to_broker")
logging.debug("called function connect_to_broker")
mqtt_info = config['mqtt']
username = mqtt_info['username']
password = mqtt_info['password']
broker_address = mqtt_info['host']
broker_port = mqtt_info['port']
broker_keepalive = mqtt_info['keepalive']
mqttc_info = config['mqttc']
client_transport = mqttc_info['use_transport']
client_bind_address=mqttc_info['bind_address']
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id = cid, protocol = mqtt.MQTTv5)
if ((username) != "" or (password != "")):
client.username_pw_set(username, password)
client.user_data_set([])
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_subscribe = on_subscribe
client.on_message = on_message
client.on_publish = on_publish
ret = client.connect(host = broker_address, port = broker_port, keepalive = broker_keepalive,
bind_address = client_bind_address, clean_start = mqtt.MQTT_CLEAN_START_FIRST_ONLY)
while True:
#if ret == mqttc.MQTT_ERR_SUCCESS:
if ret == 0:
if not client.is_connected:
print("Attendo...")
sleep(0.1)
else:
break
else:
return[]
sleep(0.2)
ret = client.loop_start()
### False = loop forever and handles the reconnecting
#ret = client.loop_forever(retry_first_connection = True)
print("Received the following message: {client.user_data_get()}")
logging.info("Received the following message: {client.user_data_get()}")
return client
def update_all_states_and_publish(client):
i = 0
for name, channel in config['channels'].items():
statetopic = channel['mqtt_msg_state_topic']
print(statetopic)
if chan_states[i] == 0:
state = "on"
elif chan_states[i] == 1:
state = "off"
ret = client.publish(statetopic, state)
i = i+1
return[]
def update_state_and_publish(client, chan_name):
# print(config['channels'][channel]['mqtt_msg_state_topic'])
for i in range(len(chan_states)):
if chan_names[i] == chan_name:
statetopic = config['channels'][chan_names[i]]['mqtt_msg_state_topic']
if chan_states[i] == 0:
state = "on"
elif chan_states[i] == 1:
state = "off"
else:
print("failed")
ret = client.publish(statetopic, state)
# print("client.publish " + statetopic + " state: " + state)
print("Published " + state + " to topic: " + statetopic + ". Returned error: " + str(ret[0]))
return client
def check_connection(client):
try:
client = connect_to_broker()
print("connection SUCCESS")
return True
except:
return False
def on_connection_lost():
print("Event: no connection available! Calling callback...")
global mqttc
try:
#mqttc = mqtt.Client.reinitialise(cid, True, None)
mqttc = connect_to_broker(mqttc)
# ...code here
reconnect()
except:
pass
def reconnect():
print("try to reconnect...")
if check_connection():
print("connection restored...")
else:
print("Huston, we have a problem...")
def monitor_connection(event_callback):
while True:
if not isconnected:
current_connection_status = check_connection(mqttc)
if not current_connection_status and not isconnected:
event_callback()
elif current_connection_status and isconnected:
print("...connection restored")
print ("check")
#logging.info("check")
sleep(5)
parser = argparse.ArgumentParser(description="Script")
parser.add_argument('--config', type=str,
help='Path to the JSON configuration file (default: <scriptdir/basename>.json)')
parser.add_argument('--log-level', type=str, default='INFO',
help='Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)')
args = parser.parse_args()
def main():
global config
try:
config = init_json()
if init_logging(config):
init_channels()
monitor_connection(on_connection_lost)
finally:
GPIO.cleanup()
print("GPIO cleanup")
logging.info("GPIO cleanup")
pass
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
# raise
pass
finally:
logging.shutdown
NOTA: il termine “channel” può essere fonte di confusione, in quanto a volte, si intendono oggetti diversi dello stesso contesto; infatti un “channel” può essere riferito a un gpio, un pin o un relay; in questo contesto ci riferiremo al channel come a un “canale“, che parte dal gpio (logico) => al pin (della board) riferito al gpio stesso => e al relay (collegato al pin)… channel appunto. (ndr)
relayd.json - Il file di configurazione json
{
"channels": {
"channel0": {
"id": 0,
"rpi_pin": 22,
"rpi_gpio": 230,
"rpi_init_state": "off",
"mqtt_msg_state_on": "on",
"mqtt_msg_state_off": "off",
"mqtt_msg_state_topic": "garage/powerstrip01/channel0",
"mqtt_msg_command_topic": "garage/powerstrip01/channel0",
"mqtt_msg_payload_on": "on",
"mqtt_msg_payload_off": "off"
},
"channel1": {
"id": 1,
"rpi_pin": 23,
"rpi_gpio": 229,
"rpi_init_state": "off",
"mqtt_msg_payload_on": "on",
"mqtt_msg_payload_off": "off",
"mqtt_msg_state_on": "on",
"mqtt_msg_state_off": "off",
"mqtt_msg_state_topic": "garage/powerstrip01/channel1",
"mqtt_msg_command_topic": "garage/powerstrip01/channel1"
},
"channel2": {
"id": 2,
"rpi_pin": 24,
"rpi_gpio": 117,
"rpi_init_state": "off",
"mqtt_msg_payload_on": "on",
"mqtt_msg_payload_off": "off",
"mqtt_msg_state_on": "on",
"mqtt_msg_state_off": "off",
"mqtt_msg_state_topic": "garage/powerstrip01/channel2",
"mqtt_msg_command_topic": "garage/powerstrip01/channel2"
},
"channel3": {
"id": 3,
"rpi_pin": 21,
"rpi_gpio": 118,
"rpi_init_state": "off",
"mqtt_msg_payload_on": "on",
"mqtt_msg_payload_off": "off",
"mqtt_msg_state_on": "on",
"mqtt_msg_state_off": "off",
"mqtt_msg_state_topic": "garage/powerstrip01/channel3",
"mqtt_msg_command_topic": "garage/powerstrip01/channel3"
},
"channel4": {
"id": 4,
"rpi_pin": 10,
"rpi_gpio": 67,
"rpi_init_state": "off",
"mqtt_msg_payload_on": "on",
"mqtt_msg_payload_off": "off",
"mqtt_msg_state_on": "on",
"mqtt_msg_state_off": "off",
"mqtt_msg_state_topic": "garage/powerstrip01/channel4",
"mqtt_msg_command_topic": "garage/powerstrip01/channel4"
},
"channel5": {
"id": 5,
"rpi_pin": 8,
"rpi_gpio": 69,
"rpi_init_state": "off",
"mqtt_msg_payload_on": "on",
"mqtt_msg_payload_off": "off",
"mqtt_msg_state_on": "on",
"mqtt_msg_state_off": "off",
"mqtt_msg_state_topic": "garage/powerstrip01/channel5",
"mqtt_msg_command_topic": "garage/powerstrip01/channel5"
},
"channel6": {
"id": 6,
"rpi_pin": 5,
"rpi_gpio": 64,
"rpi_init_state": "off",
"mqtt_msg_payload_on": "on",
"mqtt_msg_payload_off": "off",
"mqtt_msg_state_on": "on",
"mqtt_msg_state_off": "off",
"mqtt_msg_state_topic": "garage/powerstrip01/channel6",
"mqtt_msg_command_topic": "garage/powerstrip01/channel6"
},
"channel7": {
"id": 7,
"rpi_pin": 3,
"rpi_gpio": 121,
"rpi_init_state": "off",
"mqtt_msg_payload_on": "on",
"mqtt_msg_payload_off": "off",
"mqtt_msg_state_on": "on",
"mqtt_msg_state_off": "off",
"mqtt_msg_state_topic": "garage/powerstrip01/channel7",
"mqtt_msg_command_topic": "garage/powerstrip01/channel7"
}
},
"mqtt": {
"host": "10.150.110.16",
"port": 1883,
"keepalive": 10,
"username": "",
"password": ""
},
"mqttc": {
"bind_address": "0.0.0.0",
"use_transport": "tcp"
},
"log": {
"log_dir": "/usr/local/scripts/python",
"log_file": "relayd.log",
"rotate_count": 5,
"max_size_MB": 2
}
}
Script Powerstrip.py
# ----------------------------------------------------------------------------------
# - Script file name : powerstrip..py
# - Author : Nicola Montemurro
# - DNS administrator : Nicola Montemurro - Tel. xxx, Mobile: xxx
# - Create : 21.01.2025
# - Last Update : 24.01.2025
# - Description :
# - Position : /usr/local/script/python
# - note : NON modificare senza AUTORIZZAZIONE dell'AMMINISTRATORE
# -----------------------------------------------------------------------------------
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# paho info => https://eclipse.dev/paho/files/paho.mqtt.python/html/index.html
#RUN MODE:
# 1: MQTT_NONE = Direct Control GPIO by OPI.GPIO
# 2: MQTT_NOTIFY = Do Direct Control GPIO by OPI.GPIO With MQTT Notify
# 3: MQTT_REQUEST = Require GPIO control to deamon by MQTT Notify
#
#ARRAY PINS:
# each element is the PIN number used to manage the relays (the elements count MUST BE the same of GPIOS array)
#ARRAY GPIOS:
# each element is the GPIO number corresponding the PIN (the elements count MUST BE the same of PINS array)
#ARRAY CHANNELS:
# Used as LABEL in the code (the elements count MUST BE the same of PINS and GPIOS arrays)
#GPIO mode (setmode):
# In OPi.GPIO version the mode are only type BOARD, inside the file "oneplus" mode is set as BCM=BOARD
#
### ORANGPI.ONEPLUS GPIOs
#
# ORANGEPI.ONEPLUS IN USE
# |----------------|-----|
# | PIN | GPIO | |
# | | | |
# | 3 | 230 | * |
# | 5 | 229 | * |
# | 7 | 228 | |
# | 8 | 117 | * |
# | 10 | 118 | * |
# | 11 | 120 | |
# | 12 | 73 | |
# | 13 | 119 | |
# | 15 | 122 | |
# | 16 | 72 | |
# | 18 | 71 | |
# | 19 | 66 | |
# | 21 | 67 | * |
# | 22 | 121 | * |
# | 23 | 64 | * |
# | 24 | 69 | * |
# | 26 | 227 | |
# | | | |
# |----------------|-----|
#
import orangepi.oneplus
from OPi import GPIO
from time import sleep
from paho.mqtt import client as mqtt
import argparse
import uuid
import sys
# Relays GPIO numbers
pins = [ 22, 23, 24, 21, 10, 8, 5, 3 ]
gpios = [ 230, 229, 117, 118, 67, 69, 64, 121 ]
channels = [ "channel0", "channel1", "channel2", "channel3", "channel4", "channel5", "channel6", "channel7"]
# MQTT broker settings
broker_address = '10.150.110.16'
broker_port = 1883
username = ""
password = ""
topic = "garage/powerstrip01"
if sys.version_info < (3,):
class error(IOError): ...
else:
error = OSError
GPIO.setmode(orangepi.oneplus.BOARD)
GPIO.setwarnings(False)
isconnected = False
mqttc = None
def channel_on(pin):
GPIO.setup(pin, GPIO.OUT)
# GPIO.LOW = on
GPIO.output(pin, GPIO.LOW) # out
#print("channel on pin " + str(pin) + " GPIO LOW")
sleep(0.01)
def channel_off(pin):
GPIO.setup(pin, GPIO.OUT)
# GPIO.HIGH = off
GPIO.output(pin, GPIO.HIGH)
#print("channel on pin " + str(pin) + " GPIO HIGH")
sleep(0.01)
def gpio_control_channel(pin, cstate):
try:
if cstate == 'on':
channel_on(pin)
elif cstate == 'off':
channel_off(pin)
else:
print("error")
except:
pass
def connect_to_broker():
## code for both version MQTTv3 and MQTTv5
def on_connect(client, userdata, flags, rc, properties):
global isconnected #Use global variable
if rc == 0:
print("Client MQTT Connected")
isconnected = True #Signal connection
## code for both version MQTTv3 and MQTTv5
def on_disconnect(client, userdata, flags, rc, properties):
global isconnected #Use global variable
isconnected = False
print("Client MQTT Disconnected")
client_id = str(uuid.uuid4())
#mqttc = mqtt.Client(client_id=client_id, clean_session=True, userdata=None, transport="tcp")
mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=client_id, clean_session=True, userdata=None, transport="tcp")
# client.username_pw_set(username, password)
if ((username) != "" or (password != "")):
client.username_pw_set(username, password)
mqttc.user_data_set([])
mqttc.on_connect = on_connect
mqttc.on_publish = on_publish
mqttc.on_disconnect = on_disconnect
mqttc.connect(broker_address, broker_port)
global isconnected
mqttc.loop_start()
while not isconnected: #Wait for connection
sleep(0.01)
return mqttc
def disconnect_from_broker(client):
client.loop_stop()
client.disconnect(client)
def publish(topic, msg, qos=0, retain=False, properties=None):
print("function publish")
pass
## code for both version MQTTv3 and MQTTv5
def on_publish(client, userdata, mid, reason_codes, properties): #create function for callback
print("data published")
print("on_publish, mid {}".format(mid))
#log.debug("on_publish, mid {}".format(mid))
pass
# mode 1
def runmode_mqttc_none(ch, cstate):
for i in range(len(ch)):
gpio_control_channel(pins[ch[i]], cstate)
sleep(0.01)
# mode 2
def runmode_mqttc_notify(ch, cstate):
try:
for i in range(len(ch)):
gpio_control_channel(pins[ch[i]], cstate)
sleep(0.01)
client = connect_to_broker()
for i in range(len(ch)):
if isconnected:
fulltopic = (topic + "/" + str(channels[ch[i]]))
print(fulltopic)
client.publish(fulltopic, cstate, 0) ### qos MUST be 0
sleep(0.01)
disconnect_from_broker(client)
except error as exc:
print('Error: ' + str(exc))
# mode 3
def runmode_mqttc_request(ch, cstate):
try:
client = connect_to_broker()
if isconnected:
for i in range(len(ch)):
print(len(ch))
fulltopic = (topic + "/" + str(channels[ch[i]]))
print(fulltopic)
client.publish(fulltopic, cstate, 0) ### qos MUST be 0
sleep(0.01)
disconnect_from_broker(client)
except error as exc:
print('Error: ' + str(exc))
parser = argparse.ArgumentParser()
parser.add_argument("-m", "--mode", type=int, choices=[1, 2, 3], nargs=1, required=True)
parser.add_argument("-ch", "--channel", type=int, choices=[0, 1, 2, 3, 4, 5, 6, 7], nargs="+", required=True)
parser.add_argument("-s", "--state", choices=["on", "off"], required=True)
args=parser.parse_args()
mode=(args.mode)
state=(args.state)
channel=(args.channel)
def run():
if mode == [1]:
print("runmode mode 1")
runmode_mqttc_none(channel, state)
if mode == [2]:
print("runmode mode 2")
runmode_mqttc_notify(channel, state)
if mode == [3]:
print("runmode mode 3")
runmode_mqttc_request(channel, state)
def main():
try:
run()
# except error as exc:
# print('Error: ' + str(exc))
finally:
pass
if __name__ == "__main__":
try:
main()
finally:
###GPIO.cleanup()
pass
# ----------------------------------------------------------------------------------
# - Script file name : remote-powerstrip.py
# - Author : Nicola Montemurro
# - DNS administrator : Nicola Montemurro - Tel. xxx, Mobile: xxx
# - Create : 24.01.2025
# - Last Update : 26.01.2025
# - Description :
# - Position : E:\Progetti\software\scripts\python\orangepi.oneplus\release
# - note : NON modificare senza AUTORIZZAZIONE dell'AMMINISTRATORE
# -----------------------------------------------------------------------------------
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#RUN MODE:
# 1: MQTT_NONE = Direct Control GPIO by OPI.GPIO
# 2: MQTT_NOTIFY = Do Direct Control GPIO by OPI.GPIO With MQTT Notify
# 3: MQTT_REQUEST = Require GPIO control to deamon by MQTT Notify
#
#ARRAY PINS:
# each element is the PIN number used to manage the relays (the elements count MUST BE the same of GPIOS array)
#ARRAY GPIOS:
# each element is the GPIO number corresponding the PIN (the elements count MUST BE the same of PINS array)
#ARRAY CHANNELS:
# Used as LABEL in the code (the elements count MUST BE the same of PINS and GPIOS arrays)
#GPIO mode (setmode):
# In OPi.GPIO version the mode are only type BOARD, inside the file "oneplus" mode is set as BCM=BOARD
#
### ORANGPI.ONEPLUS GPIOs
#
# ORANGEPI.ONEPLUS IN USE
# |----------------|-----|
# | PIN | GPIO | |
# | | | |
# | 3 | 230 | * |
# | 5 | 229 | * |
# | 7 | 228 | |
# | 8 | 117 | * |
# | 10 | 118 | * |
# | 11 | 120 | |
# | 12 | 73 | |
# | 13 | 119 | |
# | 15 | 122 | |
# | 16 | 72 | |
# | 18 | 71 | |
# | 19 | 66 | |
# | 21 | 67 | * |
# | 22 | 121 | * |
# | 23 | 64 | * |
# | 24 | 69 | * |
# | 26 | 227 | |
# | | | |
# |----------------|-----|
#
from time import sleep
from paho.mqtt import client as mqtt
import argparse
import uuid
import sys
# Relays GPIO numbers
pins = [ 22, 23, 24, 21, 10, 8, 5, 3 ]
gpios = [ 230, 229, 117, 118, 67, 69, 64, 121 ]
channels = [ "channel0", "channel1", "channel2", "channel3", "channel4", "channel5", "channel6", "channel7"]
# MQTT broker settings
broker_address = '10.150.110.16'
broker_port = 1883
username = ""
password = ""
topic = "garage/powerstrip01"
if sys.version_info < (3,):
class error(IOError): ...
else:
error = OSError
isconnected = False
mqttc = None
def connect_to_broker():
## code for both version MQTTv3 and MQTTv5
def on_connect(client, userdata, flags, rc, properties):
global isconnected #Use global variable
if rc == 0:
print("Client MQTT Connected")
isconnected = True #Signal connection
## code for both version MQTTv3 and MQTTv5
def on_disconnect(client, userdata, flags, rc, properties):
global isconnected #Use global variable
isconnected = False
print("Client MQTT Disconnected")
client_id = str(uuid.uuid4())
mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=client_id, clean_session=True, userdata=None, transport="tcp")
# client.username_pw_set(username, password)
if ((username) != "" or (password != "")):
client.username_pw_set(username, password)
mqttc.user_data_set([])
mqttc.on_connect = on_connect
mqttc.on_publish = on_publish
mqttc.on_disconnect = on_disconnect
mqttc.connect(broker_address, broker_port)
global isconnected
mqttc.loop_start()
while not isconnected: #Wait for connection
sleep(0.01)
return mqttc
def disconnect_from_broker(client):
client.loop_stop()
client.disconnect(client)
def publish(topic, msg, qos=0, retain=False, properties=None):
print("function publish")
pass
## code for both version MQTTv3 and MQTTv5
def on_publish(client, userdata, mid, reason_codes, properties): #create function for callback
print("data published")
print("on_publish, mid {}".format(mid))
#log.debug("on_publish, mid {}".format(mid))
pass
# mode 3
def runmode_mqttc_request(ch, cstate):
try:
client = connect_to_broker()
if isconnected:
for i in range(len(ch)):
print(len(ch))
fulltopic = (topic + "/" + str(channels[ch[i]]))
print(fulltopic)
client.publish(fulltopic, cstate, 0)
sleep(0.01)
disconnect_from_broker(client)
except error as exc:
print('Error: ' + str(exc))
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-ch', '--channel', type=int, choices=[ 0, 1, 2, 3, 4, 5, 6 , 7 ], nargs='+', required=True)
parser.add_argument('-s', '--state', choices=['on', 'off'], required=True)
args=parser.parse_args()
channel=(args.channel)
state=(args.state)
def run():
runmode_mqttc_request(channel, state)
if __name__ == '__main__':
run()
set-relayd-service.sh, creare il file per l'avvio del servizio
relay-service.sh
#!/bin/bash
while read -r line
do
echo "$line" >> /etc/systemd/system/relayd.service
done << EOF
[Unit]
Description=relayd python service
After=multi-user.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /usr/local/scripts/python/relayd.py
RemainAfterExit=yes
KillSignal=SIGINT
[Install]
WantedBy=multi-user.target