Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mqttdaemon mode #44

Merged
merged 23 commits into from
Apr 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
912b331
implemented mqttdaemon mode/option
segaura Feb 13, 2021
8fc6ae2
mqttdaemon mode README.md, COMMAND in conf file
segaura Feb 13, 2021
8dc3601
mqttdaemon read via mqtt
segaura Feb 13, 2021
dc007a7
mqttdaemon little fixes and retained publish
segaura Feb 13, 2021
4458618
multiple output_type supported, mqttdaemon auto reread
segaura Feb 15, 2021
8528939
mqttdaemon non-blocking loop, ondisconnect
segaura Feb 15, 2021
1d4e6c6
simplified if auto and not backup_mode condition
segaura Feb 15, 2021
43ee5c8
mqtt loop, -a and --mqtt_daemon can live together
segaura Feb 15, 2021
d70a0ee
mqttdaemon: added qos, retain, addtimestamp config
segaura Feb 15, 2021
788969e
fix: mqttdaemon disconnected by output plugin, more debug
segaura Feb 17, 2021
7c5c41e
README.md mqttdaemon mode section updated
segaura Feb 17, 2021
3aa651d
t_room command fixed, global exception mgmt, more canpi debug
segaura Feb 17, 2021
2951947
better debug and global exception mgmt
segaura Feb 17, 2021
4acbd32
trying to solve mqttdaemon issues
segaura Feb 18, 2021
46eb79a
gitignore: another vscode folder
segaura Feb 23, 2021
964f3c5
README.md formatting
segaura Feb 25, 2021
edde2e8
mqttdaemon loop fix, mysql plugin (( fix, commands.json 4 untested ne…
segaura Feb 26, 2021
20bcf72
README.md typo correction
segaura Mar 22, 2021
91937e4
commands_hpsu.json reverted to DOS EOL
segaura Mar 26, 2021
b402e4f
fixed values (merging Spanni e7a2b46)
segaura Mar 26, 2021
5f15994
commands_hpsu.json fixed commands t_screed_day[7|22] aux_fct as per h…
segaura Mar 27, 2021
5ccd0bb
argparse, many fixes, 'desc' field in JSON output for commands with "…
segaura Apr 4, 2021
c012194
options fields used where possible, configparser more compact instruc…
segaura Apr 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
text=auto
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
install_pyHPSU
install
.vscode/settings.json
.vscode/launch.json
.vscode/launch.json
.vscode/.ropeproject/**
8 changes: 5 additions & 3 deletions HPSU/HPSU.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ def __init__(self, logger=None, driver=None, port=None, cmd=None, lg_code=None):
if not self.listCommands: #if we don't get a dict with commands

# get language, if non given, take it from the system
LANG_CODE = lg_code.upper()[0:2] if lg_code else locale.getdefaultlocale()[0].split('_')[0].upper()
LANG_CODE = lg_code[0:2] if lg_code else locale.getdefaultlocale()[0].split('_')[0].upper()
hpsuDict = {}

# read the translation file. if it doesn't exist, take the english one
command_translations_hpsu = '%s/commands_hpsu_%s.csv' % (self.pathCOMMANDS, LANG_CODE)
if not os.path.isfile(command_translations_hpsu):
command_translations_hpsu = '%s/commands_hpsu_%s.csv' % (self.pathCOMMANDS, "EN")
self.logger.info("loading command traslations file: "+command_translations_hpsu)
self.logger.info("HPSU %s, loading command traslations file: %s" % (cmd, command_translations_hpsu))
# check, if commands are json or csv
# read all known commands
with open(command_translations_hpsu, 'rU',encoding='utf-8') as csvfile:
Expand All @@ -63,7 +63,7 @@ def __init__(self, logger=None, driver=None, port=None, cmd=None, lg_code=None):

# read all known commands
command_details_hpsu = '%s/commands_hpsu.json' % self.pathCOMMANDS
self.logger.info("loading command details file: "+command_details_hpsu)
self.logger.info("HPSU %s, loading command details file: %s" % (cmd, command_details_hpsu))
with open(command_details_hpsu, 'rU',encoding='utf-8') as jsonfile:
self.all_commands = json.load(jsonfile)
self.command_dict=self.all_commands["commands"]
Expand Down Expand Up @@ -220,6 +220,8 @@ def umConversion(self, cmd, response, verbose):
resp = str(response["resp"])
else:
resp = str(response["resp"])
# TODO replaces resp with the decoded value, major breaking change to be discussed
# meanwhile, a 'desc' field is added to the output json
#if cmd["value_code"]:
# resp=cmd["value_code"][resp]
return resp
16 changes: 9 additions & 7 deletions HPSU/canpi.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class CanPI(object):
def __init__(self, hpsu=None):
self.hpsu = hpsu
try:
# TODO evaluate can.ThreadSafeBus
self.bus = can.interface.Bus(channel='can0', bustype='socketcan_native')
except Exception:
self.hpsu.logger.exception('Error opening bus can0')
Expand Down Expand Up @@ -77,7 +78,7 @@ def sendCommandWithID(self, cmd, setValue=None, priority=1):
command = command+" %02X %02X" % (setValue >> 8, setValue & 0xff)
if cmd["type"] == "value":
setValue = int(setValue)
command = command+" 00 %02X" % (setValue)
command = command+" %02X %02X" % (setValue >> 8, setValue & 0xff)

msg_data = [int(r, 16) for r in command.split(" ")]
notTimeout = True
Expand All @@ -86,6 +87,7 @@ def sendCommandWithID(self, cmd, setValue=None, priority=1):
try:
msg = can.Message(arbitration_id=receiver_id, data=msg_data, extended_id=False, dlc=7)
self.bus.send(msg)
self.hpsu.logger.debug("CanPI, %s sent: %s" % (cmd['name'], msg))

except Exception:
self.hpsu.logger.exception('Error sending msg')
Expand All @@ -107,17 +109,17 @@ def sendCommandWithID(self, cmd, setValue=None, priority=1):
if (msg_data[2] == 0xfa and msg_data[3] == rcBUS.data[3] and msg_data[4] == rcBUS.data[4]) or (msg_data[2] != 0xfa and msg_data[2] == rcBUS.data[2]):
rc = "%02X %02X %02X %02X %02X %02X %02X" % (rcBUS.data[0], rcBUS.data[1], rcBUS.data[2], rcBUS.data[3], rcBUS.data[4], rcBUS.data[5], rcBUS.data[6])
notTimeout = False
#print("got: " + str(rc))
self.hpsu.logger.debug("CanPI %s, got: %s" % (cmd['name'], str(rc)))
else:
self.hpsu.logger.error('SEND:%s' % (str(msg_data)))
self.hpsu.logger.error('RECV:%s' % (str(rcBUS.data)))
self.hpsu.logger.warning('CanPI %s, SEND: %s' % (cmd['name'], str(msg_data)))
self.hpsu.logger.warning('CanPI %s, RECV: %s' % (cmd['name'], str(rcBUS.data)))
else:
self.hpsu.logger.error('Not aquired bus')
self.hpsu.logger.warning('CanPI %s, Not aquired bus' % cmd['name'])

if notTimeout:
self.hpsu.logger.warning('msg not sync, retry: %s' % i)
self.hpsu.logger.warning('CanPI %s, msg not sync, retry: %s' % (cmd['name'], i))
if i >= self.retry:
self.hpsu.logger.error('msg not sync, timeout')
self.hpsu.logger.error('CanPI %s, msg not sync, timeout' % cmd['name'])
notTimeout = False
rc = "KO"

Expand Down
2 changes: 0 additions & 2 deletions HPSU/cantcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import uuid
import json

SocketPort = 7060

class CanTCP(object):
sock = None
hpsu = None
Expand Down
82 changes: 26 additions & 56 deletions HPSU/plugins/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,65 +33,41 @@ def __init__(self, hpsu=None, logger=None, config_file=None):
else:
sys.exit(9)

# MQTT hostname or IP
if self.config.has_option('MQTT', 'BROKER'):
self.brokerhost = self.config['MQTT']['BROKER']
else:
self.brokerhost = 'localhost'

# MQTT broker port
if self.config.has_option('MQTT', 'PORT'):
self.brokerport = int(self.config['MQTT']['PORT'])
else:
self.brokerport = 1883

# MQTT client name
if self.config.has_option('MQTT', 'CLIENTNAME'):
self.clientname = self.config['MQTT']['CLIENTNAME']
else:
self.clientname = 'rotex'
# MQTT Username
if self.config.has_option('MQTT', 'USERNAME'):
self.username = self.config['MQTT']['USERNAME']
else:
self.username = None
self.hpsu.logger.error("Username not set!!!!!")

#MQTT Password
if self.config.has_option('MQTT', "PASSWORD"):
self.password = self.config['MQTT']['PASSWORD']
else:
self.password="None"

#MQTT Prefix
if self.config.has_option('MQTT', "PREFIX"):
self.prefix = self.config['MQTT']['PREFIX']
else:
self.prefix = ""

#MQTT QOS
if self.config.has_option('MQTT', "QOS"):
self.qos = self.config['MQTT']['QOS']
else:
self.qos = "0"

# object to store entire MQTT config section
self.mqtt_config = self.config['MQTT']
self.brokerhost = self.mqtt_config.get('BROKER', 'localhost')
self.brokerport = self.mqtt_config.getint('PORT', 1883)
self.clientname = self.mqtt_config.get('CLIENTNAME', 'rotex')
self.username = self.mqtt_config.get('USERNAME', None)
if self.username is None:
self.logger.error("Username not set!!!!!")
self.password = self.mqtt_config.get('PASSWORD', "NoPasswordSpecified")
self.prefix = self.mqtt_config.get('PREFIX', "")
self.qos = self.mqtt_config.getint('QOS', 0)
# every other value implies false
self.retain = self.mqtt_config.get('RETAIN', "NOT TRUE") == "True"
# every other value implies false
self.addtimestamp = self.mqtt_config.get('ADDTIMESTAMP', "NOT TRUE") == "True"

self.logger.info("configuration parsing complete")

# no need to create a different client name every time, because it only publish
self.logger.info("creating new mqtt client instance: " + self.clientname)
self.client=mqtt.Client(self.clientname)
#self.client.on_publish = self.on_publish()
self.client.on_publish = self.on_publish
if self.username:
self.client.username_pw_set(self.username, password=self.password)
self.client.enable_logger()



#def on_publish(self,client,userdata,mid):
# self.hpsu.logger.debug("data published, mid: " + str(mid) + "\n")
# pass

def on_publish(self,client,userdata,mid):
self.hpsu.logger.debug("mqtt output plugin data published, mid: " + str(mid))

def pushValues(self, vars=None):

#self.msgs=[]
for r in vars:
self.logger.info("connecting to broker: " + self.brokerhost + ", port: " + str(self.brokerport))
self.client.connect(self.brokerhost, port=self.brokerport)
msgs=[]
if self.prefix:
Expand All @@ -101,10 +77,4 @@ def pushValues(self, vars=None):
ret=self.client.publish(r['name'],payload=r['resp'], qos=int(self.qos))
topic=r['name']
msg={'topic':topic,'payload':r['resp'], 'qos':self.qos, 'retain':False}
self.client.disconnect()






self.client.disconnect()
2 changes: 1 addition & 1 deletion HPSU/plugins/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def __init__(self, hpsu=None, logger=None, config_file=None, config=None):
if db_config.has_option('MYSQL','DB_USER'):
db_user=db_config['MYSQL']['DB_USER']
else:
self.hpsu.logger.error(("No database user defined in config file.")
self.hpsu.logger.error("No database user defined in config file.")
sys.exit(9)

if db_config.has_option('MYSQL','DB_PASSWORD'):
Expand Down
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,45 @@ The pyHPSUD.py is started via systemd:
root@rotex:# systemctl enable hpsud.service
root@rotex:# systemctl start hpsud.service

4. MQTT Daemon mode
pyHPSU starts in daemon mode, it subscribe an MQTT topic and listen forever waiting for commands.
MQTT coordinates are specified through configuration file: the same property used by mqtt output plugin plus additional COMMANDTOPIC and STATUSTOPIC.
The daemon subscribe to the topic
` PREFIX / COMMANDTOPIC / +`
and publish to the topic
` PREFIX / <hpsu-command-name>`
publishing to COMMANDTOPIC with value '' or 'read' results in property red from hpsu and published to mqtt (same topics used by mqtt output plugin)
publishing to COMMANDTOPIC with another value results in pyHPSU trying to change that value on specified hpsu property and than re-reading the same property and publishing the obtained value
```
e.g.
configuration file (e.g. /etc/pyHPSU/pyhpsu.conf)
...
[MQTT]
BROKER = 192.168.1.94
PREFIX = myhpsu
COMMANDTOPIC = command
...

root@rotex:# pyHPSU.py --mqtt_daemon

user@anothersystem:# mosquitto_pub -h 192.168.1.94 -t "myhpsu/command/t_dhw" -m read

publish the current value of t_dhw red from hpsu into the following topic

myhpsu/status/t_dhw

e.g.
(with same config)

root@rotex:# pyHPSU.py --mqtt_daemon -a -o mqtt

user@anothersystem:# mosquitto_pub -h 192.168.1.94 -t "myhpsu/command/t_flow_day" -m 29

set the parameter t_flow_day to 29°C, meanwhile pyHPSU is running in automatic mode and publishing periodically to the appopriate mqtt topics

myhpsu/status/t_dhw
```

Now, you can query multiple values or run multiple pyHPSU.py processes. Simply set as driver HPSUD ("CANTCP") via commandline or the config file (PYHPSU section)
i.e. root@rotex:# pyHPSU.py -d HPSUD -c t_dhw_setpoint1

Loading