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.
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.
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)