So the MQTT support in MicroPython for ESP8266 and ESP32 modules is a bit unique. The MQTT topic needs to be 'b'string, i.e. a bytestring per python notation.
I have been successful publishing MQTT messages from MicroPython on an ESP module to NodeRed. I just need to encode the topic as utf-8, and the payload, i.e. message as JSON format string. Of course, I am converting the JSON message to JS object.
But when I publish a topic and message from NodeRed, it never is (apparently) received on MicroPython based ESP module. I am pretty sure I am not formatting the topic right? How should I encode the topic string from the NodeRed side?
If I use MQTT to publish and subscribe in MicroPython on ESPs modules, that works. So I know the MicroPython subscribing code is valid, as well as the MQTT message callback in MicroPython on the ESP module.
This is what I do at the ESP32-S2-Mini end to publish and subscribe to Node-RED via MQTT.
Let's assume it's a node in my garage. I run a number of async functions to collect readings/data that are placed in Python dictionary. When its time to send a report I collect the appropriate values from the Python dictionary and structure a report (making use of json.dumps) to send to Node-RED.
mqtt_broker_prefix = skynet
baseID = garage
publish_message = f'{mqtt_broker_prefix}/{baseID}/message'
print(f'Publish path for messages: {publish_message}')
subscribe_to_directives = f'{mqtt_broker_prefix}/{baseID}/directives/#'
from umqttsimple import MQTTClient
import json
# Loads of other stuff goes in here...
def publish_analog_report():
publish_topic = f'{mqtt_broker_prefix}/{baseID}/B/reports'
location = node_details["location"]
lat = node_details["lat"]
lon = node_details["lon"]
map_color = node_details["map_color"]
# Create a dictionary
json_data = {
"report": "Analog reading Vin, Vreg",
"baseID": baseID,
"nodeID": "B",
"mode": "Base Station",
"location": location,
"lat": lat,
"lon": lon,
"map_color": map_color,
"Vin": Vin,
"Vreg": Vreg
}
# Convert dictionary to JSON string
#print("send_analog_report ", json_data)
json_message = json.dumps(json_data)
mqtt_client.publish(publish_topic, json_message)
#collect analog readings
publish_analog_report()
etc....
In my mqtt_callback I have this...
# MQTT callback function to handle incoming messages
def mqtt_callback(topic, msg):
print(f'Received message on topic: {topic}')
my_topic = str(topic)
parts = my_topic.split('/')
base_part = parts[1]
node_part = parts[2]
mqtt_directives = dict(json.loads(msg.decode("utf-8")))
print(f'Recd directive is: {mqtt_directives}')
At the Node-RED end, I use this JavaScript in a function node that connects to a MQTT-Out node.
let baseID = flow.get("baseID");
msg.payload = {"set_named":"closed","state":"0"};
msg.topic = `skynet/${baseID}/directives`;
return msg;
1 Like
Thanks for the example...
So if I read this write, you just set msg.topic to simple text string, nothing special when you publish to a subscriber on the ESP module has established. Humm.... so I have something goofed up since my callback never seems to fire. I was thinking the topic string was malformed or wrong format, hence why my callback never fires.
I am calling check_msg() routinely in a loop, so I know the callback should be getting cycles.
I know I have NodeRed working 100% in that it can publish/subscribe to its-self. So the issue has to be on the ESP side. I can publish from the ESP to NodeRed, so it really comes down to the subscriber not existing correctly, it would seem.
Found the problem... on my publish node in NodeRed I had enabled 'Format JSON string' (in error) and that was goofing up the topic/message ESP was receiving. Turned that off, and ESP callback is firing as expected with the topic received.
1 Like
The other thing to do, unless you have already done it, is to set the output option on the MQTT-In node in Node-RED to this... (as it saves having to use a JSON conversion node in your flow)