Further MQTT integr...
 
Notifications
Clear all

Further MQTT integration  

Page 2 / 2
  RSS

Kyle Gabriel
(@kylegabriel)
Member Admin
Joined: 6 years ago
Posts: 275
July 26, 2020 5:22 pm  

Alright, I think I have a working MQTT output module. It's so far untested. Let me know if there are any errors if you give it a run.

# coding=utf-8
#
# mqtt.py - MQTT Output module
#
from flask_babel import lazy_gettext

from mycodo.outputs.base_output import AbstractOutput


def constraints_pass_positive_value(mod_input, value):
"""
Check if the user input is acceptable
:param mod_input: SQL object with user-saved Input options
:param value: float or int
:return: tuple: (bool, list of strings)
"""
errors = []
all_passed = True
# Ensure value is positive
if value <= 0:
all_passed = False
errors.append("Must be a positive value")
return all_passed, errors, mod_input

# Measurements
measurements_dict = {
0: {
'measurement': 'duration_time',
'unit': 's'
}
}

# Output information
OUTPUT_INFORMATION = {
'output_name_unique': 'MQTT_PAHO',
'output_manufacturer': 'Mycodo',
'output_name': 'MQTT Publish',
'output_library': 'paho-mqtt',
'measurements_dict': measurements_dict,

'on_state_internally_handled': False,
'output_types': ['on_off'],

'message': 'An output to publish "on" or "off" to an MQTT server.',

'dependencies_module': [
('pip-pypi', 'paho', 'paho-mqtt')
],

'interfaces': ['Mycodo'],

'options_enabled': [],
'options_disabled': ['interface'],

'custom_options': [
{
'id': 'mqtt_hostname',
'type': 'text',
'default_value': 'localhost',
'required': True,
'name': lazy_gettext('Hostname'),
'phrase': lazy_gettext('The hostname of the MQTT server')
},
{
'id': 'mqtt_port',
'type': 'integer',
'default_value': 1883,
'required': True,
'name': lazy_gettext('Port'),
'phrase': lazy_gettext('The port of the MQTT server')
},
{
'id': 'mqtt_topic',
'type': 'text',
'default_value': 'paho/test/single',
'required': True,
'name': lazy_gettext('Topic'),
'phrase': lazy_gettext('The topic to publish with')
},
{
'id': 'mqtt_keepalive',
'type': 'integer',
'default_value': 60,
'required': True,
'constraints_pass': constraints_pass_positive_value,
'name': lazy_gettext('Keep Alive'),
'phrase': lazy_gettext('The keepalive timeout value for the client. Set to 0 to disable.')
},
{
'id': 'mqtt_clientid',
'type': 'text',
'default_value': 'mycodo_mqtt_client',
'required': True,
'name': lazy_gettext('Client ID'),
'phrase': lazy_gettext('Unique client ID for connecting to the MQTT server')
}
]
}


class OutputModule(AbstractOutput):
"""
An output support class that operates an output
"""
def __init__(self, output, testing=False):
super(OutputModule, self).__init__(output, testing=testing, name=__name__)

self.output_setup = None
self.output_state = False

self.mqtt_hostname = None
self.mqtt_port = None
self.mqtt_topic = None
self.mqtt_keepalive = None
self.mqtt_clientid = None
self.setup_custom_options(
OUTPUT_INFORMATION['custom_options'], output)

if not testing:
self.initialize_output()

def initialize_output(self):
import paho.mqtt.publish as publish

self.publish = publish

def output_switch(self, state, output_type=None, amount=None):
try:
if state == 'on':
self.publish.single(
self.mqtt_topic,
payload="on",
hostname=self.mqtt_hostname,
port=self.mqtt_port,
client_id=self.mqtt_clientid,
keepalive=self.mqtt_keepalive)
self.output_state = True
elif state == 'off':
self.publish.single(
self.mqtt_topic,
payload="off",
hostname=self.mqtt_hostname,
port=self.mqtt_port,
client_id=self.mqtt_clientid,
keepalive=self.mqtt_keepalive)
self.output_state = False
except Exception as e:
self.logger.error("State change error: {}".format(e))

def is_on(self):
if self.is_setup():
return self.output_state

def is_setup(self):
return self.output_setup

def setup_output(self):
self.output_setup = True

Mycodo Developer


ReplyQuote
not_5
(@not_5)
Eminent Member
Joined: 1 year ago
Posts: 28
July 26, 2020 8:35 pm  

@kylegabriel I took some initial steps to add MQTT into mycodo. This is what I was discussing earlier in the thread where there'd be a global setup. It's not quite finished, but for now it adds connection info into the database and has a global MQTT_enabled variable. My thought process is for the majority of use cases there will be one broker, with the same credentials, and people will want to publish to a uniform prefix for the topic (I've defaulted it to mycodo/{clientid}/). The next step is to add in whenever a function is enabled to publish to the MQTT topic (if MQTT_enabled = 1): mycodo/{clientid}/function/{UID}/status "on" & similar logic for measurements... just publish to mycodo/{clientid}/data/{UID}/measurement {temperature:[67.2,"F"]} etc.  

https://github.com/not5/Mycodo


ReplyQuote
Kyle Gabriel
(@kylegabriel)
Member Admin
Joined: 6 years ago
Posts: 275
July 26, 2020 9:13 pm  
Posted by: @not_5

@kylegabriel I took some initial steps to add MQTT into mycodo.

That looks nice. Though, I don't see where it integrates into inputs. Is this supposed to be just the configuration and test button as a proof of concept, with the integration yet to be performed?

There is a new system I've been wanting to introduce that would mesh very nicely with this, which is Input Actions. These would be Input-specific actions that would be able to be added to any Input and would execute following every measurement. This seems like a perfect candidate for the first Input Action. Thoughts?

Mycodo Developer


ReplyQuote
choc_fudge
(@choc_fudge)
Active Member
Joined: 3 weeks ago
Posts: 8
July 26, 2020 9:24 pm  

Awesome, thankyou for building the module.

Output module imports into Mycodo no errors

Attachment show what it looks like on the output settings page

When pressing the on/off buttons on the dashboard widget - errors with float/int definition? 

When testing on Arduino, not seeing anything MQTT subscribe messages come through on the Arduino Serial Monitor from Mycodo Dashboard on/off presses. 

 

My use case was to use MQTT as a controller for remote ESP32 arduino's

1. LED pattern selection. ON: pattern 1 - Lights on, OFF: pattern 2, Lights off ) 

2. Mains voltage Relays - on/off per MQTT topic 

Going further, the MQTT module could send send integers, useful to control LED patterns such as Color spectrum 1, Color Spectrum 2, etc  testing growth rate vs spectrum). 

I could probably make the on/off setup work with more than 2 led patterns, by subscribing to a new topic for each LED pattern and handle the switching in the Arduino code, or something like that. 


ReplyQuote
Kyle Gabriel
(@kylegabriel)
Member Admin
Joined: 6 years ago
Posts: 275
July 26, 2020 9:31 pm  
Posted by: @choc_fudge

When pressing the on/off buttons on the dashboard widget - errors

Don't test with anything except control from the Outputs page. I can't guarantee any other part will work except the most basic use. Also, it would help more to paste any errors from the Daemon log. Toaster (popup) messages are very minimal and don't provide nearly enough information to solve an issue.

Mycodo Developer


ReplyQuote
Kyle Gabriel
(@kylegabriel)
Member Admin
Joined: 6 years ago
Posts: 275
July 26, 2020 9:34 pm  
Posted by: @kylegabriel

Don't test with anything except control from the Outputs page

Ah, I see there's no option to turn on from the Outputs page. I'll fix that and send a new module to test.

Mycodo Developer


ReplyQuote
Kyle Gabriel
(@kylegabriel)
Member Admin
Joined: 6 years ago
Posts: 275
July 26, 2020 9:42 pm  
# coding=utf-8
# mqtt.py - MQTT Output module
from flask_babel import lazy_gettext

from mycodo.outputs.base_output import AbstractOutput


def constraints_pass_positive_value(mod_input, value):
"""
Check if the user input is acceptable
:param mod_input: database object, SQL object with user-saved Output options
:param value: input value, float or int
:return: tuple: (bool, list of strings, database object)
"""
errors = []
all_passed = True
if value <= 0: # Ensure value is positive
all_passed = False
errors.append("Must be a positive value")
return all_passed, errors, mod_input


measurements_dict = {
0: {
'measurement': 'duration_time',
'unit': 's'
}
}

OUTPUT_INFORMATION = {
'output_name_unique': 'MQTT_PAHO',
'output_manufacturer': 'Mycodo',
'output_name': "{} MQTT Publish".format(lazy_gettext('On/Off')),
'output_library': 'paho-mqtt',
'measurements_dict': measurements_dict,
'url_additional': 'http://www.eclipse.org/paho/',

'on_state_internally_handled': False,
'output_types': ['on_off'],
'interfaces': ['Mycodo'],

'message': 'An output to publish "on" or "off" to an MQTT server.',

'dependencies_module': [
('pip-pypi', 'paho', 'paho-mqtt')
],

'options_enabled': [
'on_off_state_on',
'on_off_state_startup',
'on_off_state_shutdown',
'trigger_functions_startup',
'current_draw',
'button_on',
'button_send_duration'
],
'options_disabled': ['interface'],

'custom_options': [
{
'id': 'hostname',
'type': 'text',
'default_value': 'localhost',
'required': True,
'name': lazy_gettext('Hostname'),
'phrase': lazy_gettext('The hostname of the MQTT server')
},
{
'id': 'port',
'type': 'integer',
'default_value': 1883,
'required': True,
'name': lazy_gettext('Port'),
'phrase': lazy_gettext('The port of the MQTT server')
},
{
'id': 'topic',
'type': 'text',
'default_value': 'paho/test/single',
'required': True,
'name': lazy_gettext('Topic'),
'phrase': lazy_gettext('The topic to publish with')
},
{
'id': 'keepalive',
'type': 'integer',
'default_value': 60,
'required': True,
'constraints_pass': constraints_pass_positive_value,
'name': lazy_gettext('Keep Alive'),
'phrase': lazy_gettext('The keepalive timeout value for the client. Set to 0 to disable.')
},
{
'id': 'clientid',
'type': 'text',
'default_value': 'mycodo_mqtt_client',
'required': True,
'name': lazy_gettext('Client ID'),
'phrase': lazy_gettext('Unique client ID for connecting to the MQTT server')
},
{
'id': 'payload_on',
'type': 'text',
'default_value': 'on',
'required': True,
'name': lazy_gettext('On Payload'),
'phrase': lazy_gettext('The payload to send when turned on')
},
{
'id': 'payload_off',
'type': 'text',
'default_value': 'off',
'required': True,
'name': lazy_gettext('Off Payload'),
'phrase': lazy_gettext('The payload to send when turned off')
}
]
}


class OutputModule(AbstractOutput):
"""
An output support class that operates an output
"""
def __init__(self, output, testing=False):
super(OutputModule, self).__init__(output, testing=testing, name=__name__)

self.output_setup = None
self.output_state = False

self.hostname = None
self.port = None
self.topic = None
self.keepalive = None
self.clientid = None
self.payload_off = None
self.payload_on = None
self.setup_custom_options(
OUTPUT_INFORMATION['custom_options'], output)

if not testing:
self.initialize_output()

def initialize_output(self):
import paho.mqtt.publish as publish

self.publish = publish

def output_switch(self, state, output_type=None, amount=None):
try:
if state == 'on':
self.publish.single(
self.topic,
self.payload_on,
hostname=self.hostname,
port=self.port,
client_id=self.clientid,
keepalive=self.keepalive)
self.output_state = True
elif state == 'off':
self.publish.single(
self.topic,
payload=self.payload_off,
hostname=self.hostname,
port=self.port,
client_id=self.clientid,
keepalive=self.keepalive)
self.output_state = False
except Exception as e:
self.logger.error("State change error: {}".format(e))

def is_on(self):
if self.is_setup():
return self.output_state

def is_setup(self):
return self.output_setup

def setup_output(self):
self.output_setup = True

Mycodo Developer


ReplyQuote
not_5
(@not_5)
Eminent Member
Joined: 1 year ago
Posts: 28
July 26, 2020 9:48 pm  

@kylegabriel yes, I haven't integrated into the inputs/functions/outputs yet. The test button works, so knowing the pub_mqtt function in send_message.py is working, should be pretty quick to start publishing status and measurements to topics. After that I'm going to extend it one more step and have a subscribe piece where it will listen for commands to enable/disable and modify some things (like setpoints) for functions.

 

Input Actions sounds like a nice feature for Mycodo. For this particular use case, it could be helpful if someone doesn't want all of their inputs/outputs/functions publishing to an MQTT topic and wants to only monitor a specific input. 

This post was modified 2 weeks ago by not_5

ReplyQuote
choc_fudge
(@choc_fudge)
Active Member
Joined: 3 weeks ago
Posts: 8
July 26, 2020 9:49 pm  

thanks for the new module!

uploaded no errors

attachment of how it looks in settings 

daemon log below (when pressing on/off in settings page)

2020-07-27 02:46:18,050 - ERROR - mycodo.daemon - Could not turn output off: output_switch() got an unexpected keyword argument 'duty_cycle'
Traceback (most recent call last):
  File "/var/mycodo-root/mycodo/mycodo_daemon.py", line 734, in output_off
    trigger_conditionals=trigger_conditionals)
  File "/var/mycodo-root/mycodo/controllers/controller_output.py", line 632, in output_on_off
    self.output_switch(output_id, 'off')
  File "/var/mycodo-root/mycodo/controllers/controller_output.py", line 692, in output_switch
    self.output[output_id].output_switch(state, duty_cycle=duty_cycle)
TypeError: output_switch() got an unexpected keyword argument 'duty_cycle'

ReplyQuote
Kyle Gabriel
(@kylegabriel)
Member Admin
Joined: 6 years ago
Posts: 275
July 26, 2020 9:57 pm  
Posted by: @not_5

Input Actions sounds like a nice feature for Mycodo. For this particular use case, it could be helpful if someone doesn't want all of their inputs/outputs/functions publishing to an MQTT topic and wants to only monitor a specific input. 

Right. Just like Functions that can have Function Actions, these are added by the user, not enabled by default. So, for an Input Action, the user has at disposal a list of available Input Actions that can be added to any Input. They're always optional.

Mycodo Developer


ReplyQuote
Kyle Gabriel
(@kylegabriel)
Member Admin
Joined: 6 years ago
Posts: 275
July 26, 2020 10:05 pm  
Posted by: @choc_fudge

TypeError: output_switch() got an unexpected keyword argument 'duty_cycle'

I was developing the output for the current output system refactor I've been performing but have yet to push to github. This includes the removal of the duty_cycle parameter. For yours to work, however, you'll need to add duty_cycle=None to output_switch(), so it looks like:

    def output_switch(self, state, output_type=None, amount=None, duty_cycle=None):

Mycodo Developer


ReplyQuote
choc_fudge
(@choc_fudge)
Active Member
Joined: 3 weeks ago
Posts: 8
July 27, 2020 4:14 am  

its all working great! thankyou!! (doing a little dance)


ReplyQuote
muehlbucks
(@muehlbucks)
New Member
Joined: 4 weeks ago
Posts: 2
July 27, 2020 10:53 am  
Posted by: @not_5

@kylegabriel ...people will want to publish to a uniform prefix for the topic (I've defaulted it to mycodo/{clientid}/). The next step is to add in whenever a function is enabled to publish to the MQTT topic (if MQTT_enabled = 1): mycodo/{clientid}/function/{UID}/status "on" & similar logic for measurements... just publish to mycodo/{clientid}/data/{UID}/measurement {temperature:[67.2,"F"]} etc.  

https://github.com/not5/Mycodo

How about building towards the the Homie convention?  This would save the trouble of building middleware for integrations in some cases.  For example, I'm hoping to integrate Mycodo with OpenHAB such that Mycodo controls my environments but OpenHAB's google assistant bindings can be used to query the temp/humidity/etc.


ReplyQuote
Page 2 / 2