Live RF Plotting using the ESP8266 or ESP32 with NRF24L01 Native Support
This post will cover directly interfacing the NRF24L01 module with either the ESP32 or ESP8266 Wemos modules. In earlier posts I had used the Arduino Mini module as a shim.
Here is the wiring diagram for the ESP8266 Wemos Module.
Here is the Arduino code used to send readings from the NRF24L01 module to a MQTT broker.
#include "SPI.h" #include "NRFLite.h" #include <ESP8266WiFi.h> // For ESP8266 //#include <WiFi.h> // For ESP32, uncomment this line and comment out the above line #include <PubSubClient.h> const static uint8_t RADIO_ID = 0; const static uint8_t PIN_RADIO_CE = 2; const static uint8_t PIN_RADIO_CSN = 15; const static uint8_t ChannelSamples = 16; const static uint8_t ChannelSweep = 2; const int ledPin = LED_BUILTIN; const char* ssid = <Wireless SSID>; // WiFi network name const char* password = <Wireless Password>; // WiFi network password const char* mqttServer = "<MQTT Broker>"; // MQTT Broker address const int mqttPort = 1883; // MQTT port (default 1883 for non-TLS connections) WiFiClient espClient; PubSubClient client(espClient); NRFLite _radio; bool shouldScan = false; // Flag to control scanning int scanCount = 0; // Count scans // MQTT Functions void callback(char* topic, byte* message, unsigned int length) { String messageTemp; for (int i = 0; i < length; i++) { messageTemp += (char)message[i]; } if (String(topic) == "Wemos_NRF24L01/read") { if(messageTemp == "scan"){ shouldScan = true; // Set flag to start scanning scanCount = 0; // Reset scan count } } } void setup() { pinMode(ledPin, OUTPUT); digitalWrite(ledPin, LOW); delay(10); WiFi.begin(ssid, password); // Connect to WiFi while (WiFi.status() != WL_CONNECTED) { delay(100); } client.setServer(mqttServer, mqttPort); // Connect to MQTT Broker client.setCallback(callback); // Set the MQTT callback function while (!client.connected()) { if (client.connect("Wemos_NRF24L01")) { client.subscribe("Wemos_NRF24L01/#"); } else { delay(1000); } } if (!_radio.init(RADIO_ID, PIN_RADIO_CE, PIN_RADIO_CSN)) { while (1); } } void loop() { client.loop(); // Maintain MQTT connection if (!client.connected()) { while (!client.connected()) { if (client.connect("Wemos_NRF24L01")) { } else { delay(100); } } } if (shouldScan && scanCount < 32) { String resultString = ""; uint16_t RFLevel = 0; for (uint8_t channel = 0; channel <= NRFLite::MAX_NRF_CHANNEL; channel += ChannelSweep) { uint8_t signalStrength = _radio.scanChannel(channel, ChannelSamples); resultString += String(signalStrength); if (channel < NRFLite::MAX_NRF_CHANNEL) { resultString += ","; } #if defined(ESP8266) || defined(ESP32) yield(); #endif } client.publish("Wemos_NRF24L01/data", resultString.c_str()); // Publish data to MQTT topic scanCount++; // Increment scan count } if (scanCount >= 32) { shouldScan = false; // Reset flag after 32 scans digitalWrite(ledPin, HIGH); } }
Here is the wiring diagram for the ESP32 Cam Module.
Here is the Arduino code used to send readings from the NRF24L01 module to a MQTT broker.
#include "SPI.h" #include "NRFLite.h" //#include <ESP8266WiFi.h> // For ESP8266 #include <WiFi.h> // For ESP32, uncomment this line and comment out the above line #include <PubSubClient.h> const static uint8_t RADIO_ID = 0; const static uint8_t PIN_RADIO_CE = 2; const static uint8_t PIN_RADIO_CSN = 4; const static uint8_t ChannelSamples = 16; const static uint8_t ChannelSweep = 2; const char* ssid = <Wireless SSID>; // WiFi network name const char* password = <Wireless Password>; // WiFi network password const char* mqttServer = "<MQTT Broker>"; // MQTT Broker address const int mqttPort = 1883; // MQTT port (default 1883 for non-TLS connections) WiFiClient espClient; PubSubClient client(espClient); NRFLite _radio; bool shouldScan = false; // Flag to control scanning int scanCount = 0; // Count scans // MQTT Functions void callback(char* topic, byte* message, unsigned int length) { String messageTemp; for (int i = 0; i < length; i++) { messageTemp += (char)message[i]; } if (String(topic) == "ESP32Cam_NRF24L01/read") { if(messageTemp == "scan"){ shouldScan = true; // Set flag to start scanning scanCount = 0; // Reset scan count } } } void setup() { WiFi.begin(ssid, password); // Connect to WiFi while (WiFi.status() != WL_CONNECTED) { delay(100); } client.setServer(mqttServer, mqttPort); // Connect to MQTT Broker client.setCallback(callback); // Set the MQTT callback function while (!client.connected()) { if (client.connect("ESP32Cam_NRF24L01")) { client.subscribe("ESP32Cam_NRF24L01/#"); } else { delay(1000); } } // Initialize SPI on pins chosen so as not to conflict with camera/SD: // Parameters: SPI.begin(SCK, MISO, MOSI, dummy SS) SPI.begin(14, 12, 13, 2); if (!_radio.init(RADIO_ID, PIN_RADIO_CE, PIN_RADIO_CSN)) { while (1); } } void loop() { client.loop(); // Maintain MQTT connection if (!client.connected()) { while (!client.connected()) { if (client.connect("ESP32Cam_NRF24L01")) { } else { delay(100); } } } if (shouldScan && scanCount < 32) { String resultString = ""; uint16_t RFLevel = 0; for (uint8_t channel = 0; channel <= NRFLite::MAX_NRF_CHANNEL; channel += ChannelSweep) { uint8_t signalStrength = _radio.scanChannel(channel, ChannelSamples); resultString += String(signalStrength); if (channel < NRFLite::MAX_NRF_CHANNEL) { resultString += ","; } #if defined(ESP8266) || defined(ESP32) yield(); #endif } client.publish("ESP32Cam_NRF24L01/data", resultString.c_str()); // Publish data to MQTT topic scanCount++; // Increment scan count } if (scanCount >= 32) { shouldScan = false; // Reset flag after 32 scans } }
Here is the Processing IDE code used to display readings from the MQTT broker. It can be set to subscribe to either the Wemos or ESP32 Cam module feeds, change the comments as needed.
// Import the MQTT client library import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; // Global variables for drawing int cols = 63; // Number of columns for both sketches int rows = 32; // Number of rows for the first sketch float[][] z = new float[rows][cols]; // Array for the first sketch int[] values = new int[cols]; // Array to store the data for the second sketch int[] maxValues = new int[cols]; // Array to store maximum values for each column in the second sketch int currentNumber = 0; // Global MQTT client and data buffer MqttClient client; String mqttData = null; void setup() { size(1320, 580); // Combined sketch size background(0); // Set background to black // Set up the MQTT client try { // Connect to the broker using the specified URI and unique client ID client = new MqttClient("tcp://<MQTT Broker>:1883", "MqttClient2"); MqttConnectOptions options = new MqttConnectOptions(); options.setCleanSession(true); // Set up the callback to receive messages client.setCallback(new MqttCallback() { public void connectionLost(Throwable cause) { println("MQTT connection lost: " + cause.getMessage()); } public void messageArrived(String topic, MqttMessage message) throws Exception { // When a message is received, store it for processing in draw() mqttData = message.toString(); } public void deliveryComplete(IMqttDeliveryToken token) { // Not used (we are only subscribing) } }); // Connect to the broker and subscribe to the topic client.connect(options); // client.subscribe("Wemos_NRF24L01/data"); // Uncomment to subscribe to the Wemos Module client.subscribe("ESP32Cam_NRF24L01/data"); // Replace with your actual topic if needed println("Connected to MQTT broker and subscribed to topic ESP32Cam_NRF24L01/data"); } catch(Exception e) { e.printStackTrace(); } // Initialize the waterfall array and maximum values initializeArray(); for (int i = 0; i < maxValues.length; i++) { maxValues[i] = 0; } } void draw() { background(0); // Clear the background // Draw the two sketches side by side pushMatrix(); translate(0, 0); drawBordersAndText1(); ColorScaledraw1(); popMatrix(); pushMatrix(); translate(660, 0); drawBordersAndText2(); ColorScaledraw2(); popMatrix(); drawRectangles(); drawFooter(); // If MQTT data has been received, process it if (mqttData != null) { String incomingData = mqttData.trim(); processData1(incomingData); // Update the waterfall display processData2(incomingData); // Update the barchart display mqttData = null; // Clear the data after processing } } void ColorScaledraw1() { if (currentNumber == 1) { ViridisColorWaterfall(); } else if (currentNumber == 2) { ThermalColorWaterfall(); } else if (currentNumber == 3) { PlasmaColorWaterfall(); } else if (currentNumber == 4) { InfernoColorWaterfall(); } else { GrayScaleWaterfall(); } } void ColorScaledraw2() { if (currentNumber == 1) { ViridisColorBarchart(); drawViridisMaxLine(); } else if (currentNumber == 2) { ThermalColorBarchart(); drawThermalMaxLine(); } else if (currentNumber == 3) { PlasmaColorBarchart(); drawPlasmaMaxLine(); } else if (currentNumber == 4) { InfernoColorBarchart(); drawInfernoMaxLine(); } else { GrayScaleBarchart(); drawGrayScaleMaxLine(); } } void keyPressed() { if (key == '1') { currentNumber = 1; } else if (key == '2') { currentNumber = 2; } else if (key == '3') { currentNumber = 3; } else if (key == '4') { currentNumber = 4; } else if (key == 'x') { for (int i = 0; i < maxValues.length; i++) { maxValues[i] = 0; // Reset maximum values for the barchart } } else { currentNumber = 0; } } void initializeArray() { for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { z[y][x] = 0; // Initialize the waterfall array with zeros } } } void drawFooter() { fill(255); textSize(12); String middleText = "Enter number for Color Scale: (1) Viridis, (2) Thermo, (3) Plasma, (4) Inferno"; float middleTextWidth = textWidth(middleText); float middleTextX = (width - middleTextWidth) / 2; text(middleText, middleTextX, height - 60); String bottomText = "Press x to reset max reading line, Gray scale (default) by pressing any other key"; float bottomTextWidth = textWidth(bottomText); float bottomTextX = (width - bottomTextWidth) / 2; text(bottomText, bottomTextX, height - 40); textSize(8); String signatureText = "Developed by Patrick Gilfeather - CloudACM.com - Jan 2025"; float signatureTextWidth = textWidth(signatureText); float signatureTextX = (width - signatureTextWidth) / 2; text(signatureText, signatureTextX, height - 25); } void drawRectangles() { fill(0); noStroke(); rect(658, 100, 12, 390); rect(660, 500, 655, 70); } void drawGrid() { stroke(128, 128, 128, 32); for (int i = 0; i <= cols; i++) { float x = map(i, 0, 10, 10, 650); line(x, 100, x, 480); } for (int i = 0; i <= rows; i++) { float y = map(i, 0, 10, 100, 480); line(10, y, 650, y); } } void shiftDataDown() { for (int y = rows - 1; y > 0; y--) { arrayCopy(z[y-1], z[y]); } } void drawBordersAndText1() { fill(255); textSize(20); String topText = "ESP32Cam nRF24L01 Waterfall"; float topTextWidth = textWidth(topText); text(topText, (660 - topTextWidth) / 2, 50); textSize(12); } void drawBordersAndText2() { fill(255); textSize(20); String topText = "ESP32Cam nRF24L01 Barchart"; float topTextWidth = textWidth(topText); text(topText, (660 - topTextWidth) / 2, 50); } void processData1(String data) { // Expect a comma-separated string of 63 numbers ending with a comma if (data.matches("^(\\d+,){63}$")) { String[] values = split(data, ','); shiftDataDown(); for (int i = 0; i < cols; i++) { try { z[0][i] = Float.parseFloat(values[i]); } catch (NumberFormatException e) { println("Error converting value: " + values[i]); } } } } void processData2(String data) { // Process the comma-separated string for the barchart String[] stringValues = data.split(","); for (int i = 0; i < stringValues.length && i < values.length; i++) { values[i] = int(stringValues[i].trim()); maxValues[i] = max(maxValues[i], values[i]); } } void GrayScaleWaterfall() { float xSpacing = (660 - 20) / float(cols); float ySpacing = (580 - 200) / float(rows); for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { fill(map(z[y][x], 0, 16, 0, 255)); rect(10 + x * xSpacing, 100 + y * ySpacing, xSpacing, ySpacing); } } } void ViridisColorWaterfall() { float xSpacing = (660 - 20) / float(cols); float ySpacing = (580 - 200) / float(rows); for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { float value = z[y][x]; float c = map(value, 0, 16, 0, 255); if (c <= 64) { float r = map(c, 0, 64, 32, 128); float g = 0; float b = map(c, 0, 64, 32, 128); fill(r, g, b); } else if (c <= 128) { float r = map(c, 64, 128, 96, 0); float g = map(c, 64, 128, 0, 255); float b = map(c, 64, 128, 96, 0); fill(r, g, b); } else { float r = map(c, 128, 255, 0, 255); float g = 255; float b = 0; fill(r, g, b); } rect(10 + x * xSpacing, 100 + y * ySpacing, xSpacing, ySpacing); } } } void ThermalColorWaterfall() { float xSpacing = (660 - 20) / float(cols); float ySpacing = (580 - 200) / float(rows); for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { float value = z[y][x]; float c = map(value, 0, 16, 0, 255); if (c <= 64) { float r = 0; float g = 0; float b = map(c, 0, 64, 32, 255); fill(r, g, b); } else if (c <= 128) { float r = map(c, 64, 128, 0, 255); float g = 0; float b = map(c, 64, 128, 255, 0); fill(r, g, b); } else { float r = 255; float g = map(c, 128, 255, 0, 255); float b = 0; fill(r, g, b); } rect(10 + x * xSpacing, 100 + y * ySpacing, xSpacing, ySpacing); } } } void PlasmaColorWaterfall() { float xSpacing = (660 - 20) / float(cols); float ySpacing = (580 - 200) / float(rows); for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { float value = z[y][x]; float c = map(value, 0, 16, 0, 255); if (c <= 64) { float r = map(c, 0, 64, 16, 156); float g = map(c, 0, 64, 0, 23); float b = map(c, 0, 64, 128, 158); fill(r, g, b); } else if (c <= 128) { float r = map(c, 64, 128, 156, 241); float g = map(c, 64, 128, 23, 131); float b = map(c, 64, 128, 158, 76); fill(r, g, b); } else { float r = 241; float g = map(c, 128, 255, 131, 255); float b = map(c, 128, 255, 76, 0); fill(r, g, b); } rect(10 + x * xSpacing, 100 + y * ySpacing, xSpacing, ySpacing); } } } void InfernoColorWaterfall() { float xSpacing = (660 - 20) / float(cols); float ySpacing = (580 - 200) / float(rows); for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { float value = z[y][x]; float c = map(value, 0, 16, 0, 255); if (c <= 64) { float r = map(c, 0, 64, 16, 255); float g = 0; float b = 0; fill(r, g, b); } else if (c <= 128) { float r = 255; float g = map(c, 64, 128, 0, 255); float b = 0; fill(r, g, b); } else { float r = 255; float g = 255; float b = map(c, 128, 255, 0, 255); fill(r, g, b); } rect(10 + x * xSpacing, 100 + y * ySpacing, xSpacing, ySpacing); } } } void GrayScaleBarchart() { noStroke(); float barWidth = 640 / float(values.length); for (int i = 0; i < values.length; i++) { int value = values[i]; float barHeight = map(value, 0, 32, 0, 380); fill(map(value, 0, 32, 0, 255)); rect(10 + i * barWidth, 580 - 100 - barHeight, barWidth, barHeight); } } void drawGrayScaleMaxLine() { stroke(128, 128, 128, 128); fill(128, 128, 128, 32); strokeWeight(2); float barWidth = 640 / float(maxValues.length); beginShape(); float lastX = 0; float y = 580 - 100 - map(0, 0, 32, 0, 380); vertex(lastX, y); for (int i = 0; i < maxValues.length; i++) { float x = i * barWidth + barWidth + 10; y = 580 - 100 - map(maxValues[i], 0, 32, 0, 380); if (i >= 0) { vertex(lastX, y); } vertex(x, y); lastX = x; } endShape(); drawGrid(); } void ViridisColorBarchart() { noStroke(); float barWidth = 640 / float(values.length); for (int i = 0; i < values.length; i++) { int value = values[i]; float barHeight = map(value, 0, 32, 0, (height - 200)); if (value <= 3) { float redValue = map(value, 0, 3, 0, 128); float greenValue = 0; float blueValue = map(value, 0, 3, 0, 128); fill(redValue, greenValue, blueValue); } else if (value > 3 && value <= 6) { float redValue = map(value, 3, 6, 128, 0); float greenValue = map(value, 3, 6, 0, 128); float blueValue = map(value, 3, 6, 128, 0); fill(redValue, greenValue, blueValue); } else { float redValue = map(value, 6, 16, 0, 255); float greenValue = map(value, 6, 16, 128, 255); float blueValue = 0; fill(redValue, greenValue, blueValue); } rect(i * barWidth + 10, height - barHeight - 100, barWidth, barHeight); } } void drawViridisMaxLine() { stroke(0, 255, 0, 128); fill(0, 128, 0, 32); strokeWeight(2); float barWidth = 640 / float(maxValues.length); beginShape(); float lastX = 0; float y = 580 - 100 - map(0, 0, 32, 0, 380); vertex(lastX, y); for (int i = 0; i < maxValues.length; i++) { float x = i * barWidth + barWidth + 10; y = 580 - 100 - map(maxValues[i], 0, 32, 0, 380); if (i >= 0) { vertex(lastX, y); } vertex(x, y); lastX = x; } endShape(); drawGrid(); } void ThermalColorBarchart() { noStroke(); float barWidth = 640 / float(values.length); for (int i = 0; i < values.length; i++) { int value = values[i]; float barHeight = map(value, 0, 32, 0, (height - 200)); if (value <= 4) { float redValue = 0; float greenValue = 0; float blueValue = map(value, 0, 4, 0, 255); fill(redValue, greenValue, blueValue); } else if (value > 4 && value <= 8) { float redValue = map(value, 4, 8, 0, 255); float greenValue = 0; float blueValue = map(value, 4, 8, 255, 0); fill(redValue, greenValue, blueValue); } else { float redValue = 255; float greenValue = map(value, 8, 16, 0, 255); float blueValue = 0; fill(redValue, greenValue, blueValue); } rect(i * barWidth + 10, height - barHeight - 100, barWidth, barHeight); } } void drawThermalMaxLine() { stroke(0, 96, 255, 255); fill(0, 0, 196, 32); strokeWeight(2); float barWidth = 640 / float(maxValues.length); beginShape(); float lastX = 0; float y = 580 - 100 - map(0, 0, 32, 0, 380); vertex(lastX, y); for (int i = 0; i < maxValues.length; i++) { float x = i * barWidth + barWidth + 10; y = height - map(maxValues[i], 0, 32, 0, (height - 200)) - 100; if (i >= 0) { vertex(lastX, y); } vertex(x, y); lastX = x; } endShape(); drawGrid(); } void PlasmaColorBarchart() { noStroke(); float barWidth = 640 / float(values.length); for (int i = 0; i < values.length; i++) { int value = values[i]; float barHeight = map(value, 0, 32, 0, (height - 200)); if (value <= 4) { float redValue = map(value, 0, 4, 13, 156); float greenValue = map(value, 0, 4, 8, 23); float blueValue = map(value, 0, 4, 135, 158); fill(redValue, greenValue, blueValue); } else if (value > 4 && value <= 8) { float redValue = map(value, 4, 8, 156, 241); float greenValue = map(value, 4, 8, 23, 131); float blueValue = map(value, 4, 8, 158, 76); fill(redValue, greenValue, blueValue); } else { float redValue = 241; float greenValue = map(value, 8, 16, 131, 255); float blueValue = map(value, 8, 16, 76, 0); fill(redValue, greenValue, blueValue); } rect(i * barWidth + 10, height - barHeight - 100, barWidth, barHeight); } } void drawPlasmaMaxLine() { stroke(196, 0, 196, 196); fill(64, 0, 255, 32); strokeWeight(2); float barWidth = 640 / float(maxValues.length); beginShape(); float lastX = 0; float y = 580 - 100 - map(0, 0, 32, 0, 380); vertex(lastX, y); for (int i = 0; i < maxValues.length; i++) { float x = i * barWidth + barWidth + 10; y = height - map(maxValues[i], 0, 32, 0, (height - 200)) - 100; if (i >= 0) { vertex(lastX, y); } vertex(x, y); lastX = x; } endShape(); drawGrid(); } void InfernoColorBarchart() { noStroke(); float barWidth = 640 / float(values.length); for (int i = 0; i < values.length; i++) { int value = values[i]; float barHeight = map(value, 0, 32, 0, (height - 200)); if (value <= 4) { float redValue = map(value, 0, 4, 0, 255); float greenValue = 0; float blueValue = 0; fill(redValue, greenValue, blueValue); } else if (value > 4 && value <= 8) { float redValue = 255; float greenValue = map(value, 4, 8, 0, 255); float blueValue = 0; fill(redValue, greenValue, blueValue); } else { float redValue = 255; float greenValue = 255; float blueValue = map(value, 8, 16, 0, 255); fill(redValue, greenValue, blueValue); } rect(i * barWidth + 10, height - barHeight - 100, barWidth, barHeight); } } void drawInfernoMaxLine() { stroke(255, 128, 0, 196); fill(255, 0, 0, 32); strokeWeight(2); float barWidth = 640 / float(maxValues.length); beginShape(); float lastX = 0; float y = 580 - 100 - map(0, 0, 32, 0, 380); vertex(lastX, y); for (int i = 0; i < maxValues.length; i++) { float x = i * barWidth + barWidth + 10; y = height - map(maxValues[i], 0, 32, 0, (height - 200)) - 100; if (i >= 0) { vertex(lastX, y); } vertex(x, y); lastX = x; } endShape(); drawGrid(); }
Publishing a read command to the MQTT broker will prompt either ESP based modules to start a scan. Here is a bash script that uses the mosquitto publish command.
#!/bin/bash # cloudacm.com mosquitto_pub -h <MQTT Broker> -t Wemos_NRF24L01/read -m "scan" mosquitto_pub -h <MQTT Broker> -t ESP32Cam_NRF24L01/read -m "scan" exit
I created some Easter Eggs that can be called by changing the publish script.
#!/bin/bash # cloudacm.com mosquitto_pub -h <MQTT Broker> -t ESP32Cam_NRF24L01/Easter -m "Turin" mosquitto_pub -h <MQTT Broker> -t ESP32Cam_NRF24L01/Easter -m "Lisa" exit
Adding these function calls in the MQTT Function section of the Arduino code followed by the functions should do the trick.
// MQTT Functions void callback(char* topic, byte* message, unsigned int length) { String messageTemp; for (int i = 0; i < length; i++) { messageTemp += (char)message[i]; } if (String(topic) == "ESP32Cam_NRF24L01/read") { if(messageTemp == "scan"){ shouldScan = true; // Set flag to start scanning scanCount = 0; // Reset scan count } } if (String(topic) == "ESP32Cam_NRF24L01/Easter") { if(messageTemp == "Turin"){ Turin(); // } } if (String(topic) == "ESP32Cam_NRF24L01/Easter") { if(messageTemp == "Lisa"){ Lisa(); // } } }
Here are the functions.
void Turin() { // Define an array of messages to publish const char* eggString[] = { "11,9,8,6,6,6,5,5,5,4,2,3,3,3,3,2,3,3,6,10,9,5,1,3,5,6,4,2,4,3,4,4,6,3,2,4,3,1,1,1,1,3,3,0,3,3,2,2,1,1,2,2,2,2,4,5,8,8,12,11,8,7,13,", "13,8,8,6,6,4,4,4,4,2,2,1,2,2,3,2,3,1,1,2,4,8,9,9,11,11,11,8,7,10,9,4,5,3,3,5,3,2,3,3,4,3,3,1,3,2,3,3,1,1,1,2,3,4,4,5,5,9,12,12,10,9,7,", "14,9,7,6,6,5,6,5,5,4,3,2,2,3,3,2,4,4,3,3,1,2,1,4,6,8,11,9,9,9,11,12,12,11,11,11,10,10,10,9,8,6,5,3,9,3,2,2,1,1,2,2,4,4,5,5,7,7,9,12,12,9,9,", "14,12,10,11,9,5,7,4,4,4,2,2,3,3,2,3,5,6,6,3,2,3,1,4,7,8,12,9,8,8,11,9,5,5,4,3,2,2,2,6,8,7,9,6,3,2,2,2,2,2,3,4,5,6,7,6,10,9,11,12,14,14,14,", "15,11,7,7,6,5,6,3,3,3,2,1,3,1,3,1,5,6,5,4,2,2,1,4,7,8,13,10,12,11,13,9,6,7,5,6,2,4,3,7,10,3,4,1,1,2,2,3,3,3,4,5,5,8,8,7,8,10,12,14,15,15,15,", "14,12,9,9,6,4,3,3,4,4,2,1,2,2,1,1,6,5,6,5,3,1,1,4,6,9,11,12,12,11,10,8,5,8,7,8,3,4,4,4,10,4,2,1,1,2,3,3,4,4,3,5,4,5,9,7,10,10,11,12,14,15,15,", "10,6,5,4,3,2,2,1,1,0,0,0,3,2,0,0,5,5,5,4,2,1,1,5,5,6,7,8,10,9,10,5,4,6,7,8,1,1,2,3,4,3,2,1,2,3,3,5,4,3,3,5,4,5,7,7,9,9,9,13,14,15,15,", "4,2,2,3,2,1,3,0,1,0,0,1,2,1,0,0,5,6,9,7,4,0,1,7,8,6,7,5,6,8,7,4,3,5,6,7,2,0,3,3,4,3,2,1,2,1,2,2,3,3,3,4,6,5,6,5,8,9,10,14,15,15,15,", "4,2,1,0,0,0,1,0,0,0,0,0,0,0,0,1,2,5,6,5,3,1,3,6,6,6,6,5,8,9,8,5,8,6,6,7,2,1,3,3,3,2,2,1,2,2,3,3,4,3,2,3,3,6,5,4,7,7,10,13,15,15,15,", "4,2,1,0,0,0,2,0,2,1,0,0,1,1,1,0,1,4,4,6,3,1,1,4,7,9,10,10,7,8,7,9,9,5,6,6,3,0,3,4,3,6,2,1,2,2,1,2,4,3,1,3,1,3,5,4,4,6,8,10,11,15,15,", "3,2,1,1,0,0,1,0,2,0,0,1,0,0,1,0,1,3,2,5,3,1,3,4,5,8,11,11,10,10,12,11,8,5,4,7,3,1,3,4,3,8,2,1,1,2,2,3,2,2,1,2,1,3,2,1,2,3,4,6,7,10,13,", "3,2,1,1,1,1,1,1,1,0,0,1,0,1,1,1,1,3,3,7,2,1,1,4,6,5,8,8,5,8,8,7,7,6,7,6,2,1,3,4,8,4,4,2,2,2,3,2,4,3,2,3,3,3,1,2,1,3,3,4,4,7,12,", "5,2,1,0,1,0,0,1,0,0,0,1,0,0,0,0,0,3,5,5,3,2,1,4,5,4,3,3,5,11,11,5,6,8,7,5,2,1,2,6,7,4,4,1,1,4,1,2,3,3,3,5,3,2,0,1,1,2,2,4,4,5,11,", "3,3,1,0,0,0,0,0,0,0,0,1,0,0,0,0,1,4,5,4,3,1,2,4,6,6,5,6,7,10,11,7,9,11,8,7,2,1,2,4,5,7,5,2,1,3,2,3,4,2,2,3,5,3,2,2,2,2,3,5,5,6,9,", "1,2,1,1,0,0,0,0,0,0,0,0,1,0,1,0,0,3,5,6,1,1,1,5,6,6,7,6,5,8,8,7,8,7,6,5,2,3,2,4,6,7,4,3,3,3,4,3,3,2,3,3,2,4,2,3,3,3,2,4,5,7,10,", "0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,0,2,4,8,6,3,3,4,7,8,7,6,5,3,8,8,5,6,7,7,7,5,2,2,3,4,4,4,3,2,2,2,3,2,2,2,3,4,4,3,4,2,1,2,4,5,7,13,", "1,1,1,0,1,0,1,0,0,0,0,1,1,0,0,0,1,4,5,6,4,2,5,8,7,7,7,6,4,8,6,6,5,8,9,8,5,3,1,3,4,3,3,2,1,2,4,3,3,2,2,3,5,3,2,2,2,1,4,9,8,9,14,", "1,0,0,1,0,0,1,0,0,0,1,0,0,0,1,0,0,3,5,6,1,2,2,3,3,4,4,4,5,9,7,5,4,8,7,8,6,3,2,3,4,4,3,0,2,2,3,2,3,2,2,3,3,3,2,2,4,3,6,6,5,7,10,", "0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,3,4,7,2,0,1,2,3,4,5,3,4,8,6,3,4,4,5,5,4,2,2,4,4,4,1,1,2,3,2,3,3,2,3,2,3,1,1,2,2,2,2,4,4,4,6,", "1,1,0,0,0,0,0,0,0,0,0,0,0,2,1,0,2,3,4,7,4,3,1,2,3,4,7,5,5,6,6,3,5,6,5,3,2,2,3,5,4,3,1,0,1,2,3,3,1,2,3,2,3,3,3,3,2,3,2,3,2,4,5,", "1,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,1,2,4,5,3,3,3,4,4,3,4,4,5,8,7,4,4,5,5,3,2,3,3,4,5,2,2,1,1,2,4,3,3,2,3,2,3,2,1,3,3,2,1,2,2,4,6,", "0,0,0,0,0,0,1,0,1,0,1,0,0,1,0,0,1,1,5,5,4,3,5,8,8,9,10,11,7,7,10,7,7,7,6,6,5,4,3,4,5,2,2,1,0,2,4,2,1,2,1,2,3,3,3,3,3,5,2,2,1,4,8,", "0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,2,4,7,5,10,11,5,8,9,9,12,6,7,9,8,9,9,8,6,4,2,4,5,3,3,2,1,1,2,4,3,2,3,1,1,2,2,3,2,3,3,2,4,8,9,12,", "0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,2,4,7,5,7,6,5,5,5,6,12,11,5,7,5,5,6,6,6,5,3,5,9,6,2,2,2,0,2,2,2,2,2,1,2,2,1,2,2,4,8,9,8,3,9,14,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,4,5,5,3,4,9,5,4,6,13,8,8,7,5,5,6,4,4,6,7,6,9,4,2,1,2,1,1,2,2,2,1,1,1,2,7,8,8,8,1,2,3,4,9,14,", "3,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,2,5,5,6,6,4,6,4,5,5,12,11,8,7,8,6,5,4,4,12,5,9,4,3,3,4,5,5,3,5,4,3,4,4,5,4,2,1,2,1,2,2,2,4,8,11,", "5,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,1,3,5,5,8,6,6,5,5,6,5,10,9,7,5,5,5,5,4,5,9,7,8,3,2,2,6,3,2,4,2,2,1,1,1,2,3,4,2,2,2,2,4,2,4,7,11,", "2,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,0,1,2,7,7,13,8,6,5,4,4,4,7,6,5,5,4,6,5,7,10,6,5,4,8,8,5,0,1,1,1,3,1,1,0,1,2,6,2,2,2,2,4,4,4,5,9,", "0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,2,2,8,10,8,9,10,7,6,6,6,6,7,8,3,5,8,9,7,8,8,8,6,2,2,1,1,1,1,2,2,3,3,2,2,3,5,5,2,3,4,3,5,5,7,10,", "1,1,1,0,2,0,0,0,1,0,0,0,0,0,2,1,0,1,2,2,4,7,6,8,9,10,10,7,9,8,9,6,5,10,8,8,7,4,2,1,0,2,2,1,2,2,2,2,2,4,3,2,4,3,5,3,3,3,4,4,5,7,11,", "1,1,1,4,1,2,1,0,1,3,1,3,3,1,0,0,0,1,2,1,1,4,7,10,9,9,9,11,12,11,12,9,8,8,6,8,5,4,1,1,1,3,2,1,2,2,3,3,4,4,2,2,2,3,3,2,4,3,4,6,6,12,12,", "0,1,1,2,1,2,3,2,3,2,0,2,4,5,7,8,8,6,7,8,9,10,6,3,3,4,6,8,8,8,10,10,8,7,7,7,3,2,2,1,2,1,2,2,2,3,1,4,4,3,3,4,4,5,2,2,3,3,4,5,6,6,12," }; const int numStrings = sizeof(eggString) / sizeof(eggString[0]); // Publish each message with a delay for (int i = 0; i < numStrings; i++) { client.publish("ESP32Cam_NRF24L01/data", eggString[i]); delay(500); } } void Lisa() { // Define an array of messages to publish const char* lisaStrings[] = { "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,2,3,2,3,3,4,6,7,7,8,8,7,4,2,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,4,1,0,0,0,1,2,4,6,8,8,9,9,9,9,9,9,8,5,3,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,4,0,0,0,0,2,7,9,9,9,8,9,9,9,9,9,8,7,5,4,4,4,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,4,3,1,0,0,0,3,7,8,9,9,9,9,8,9,8,8,8,6,4,3,3,4,6,6,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,3,2,3,1,0,0,2,4,6,8,9,9,9,8,8,8,8,8,7,4,2,1,3,4,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,2,2,2,1,0,0,2,4,7,8,9,9,8,8,8,8,8,7,5,2,2,1,0,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,1,1,0,1,0,0,0,2,6,8,8,8,8,8,8,8,8,7,5,3,2,2,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,1,0,1,0,0,0,0,4,7,8,8,8,7,7,8,7,7,5,2,2,2,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,1,0,0,1,0,0,0,0,0,3,5,7,7,7,6,7,7,7,5,2,1,2,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,1,1,0,0,0,0,1,4,6,6,5,5,5,6,4,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,4,3,4,3,3,2,0,0,0,0,0,2,5,4,3,3,4,4,2,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,4,4,4,4,4,1,1,0,0,0,0,2,3,1,1,1,2,2,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,3,3,4,5,4,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,2,2,3,3,2,1,1,0,0,0,4,5,3,2,1,1,0,0,0,1,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,3,2,1,0,0,0,4,8,7,6,5,3,1,1,0,0,0,0,0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,1,1,1,2,1,0,0,0,4,7,7,5,4,5,5,3,1,0,0,0,0,0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,2,2,1,1,0,0,0,1,5,6,6,3,3,5,6,4,2,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,2,2,2,1,0,0,0,2,6,7,6,1,5,7,6,4,2,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,2,2,1,1,0,0,0,4,7,8,8,6,6,8,7,5,3,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,2,2,2,2,2,1,0,5,8,8,8,7,7,8,8,7,4,0,0,0,0,0,1,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,5,4,3,5,1,0,6,7,7,8,6,7,8,8,8,5,1,0,0,0,0,0,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,4,5,7,7,7,6,1,0,4,6,4,7,5,5,7,4,5,6,2,0,0,0,0,1,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,8,8,8,7,2,0,3,4,4,7,6,2,4,2,1,2,1,0,0,0,0,1,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,7,8,8,8,8,8,3,0,5,7,8,8,8,7,7,7,6,4,1,0,0,0,0,2,3,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,5,0,5,8,9,8,9,9,7,7,6,5,1,0,0,0,0,3,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,8,8,8,8,8,8,8,3,3,7,9,9,9,9,8,6,4,2,0,0,0,0,0,7,7,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,6,3,7,9,9,9,9,8,6,3,1,0,0,0,0,4,8,8,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,8,7,7,8,8,8,3,5,8,9,9,8,7,4,1,0,0,0,0,0,6,8,9,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,8,8,7,7,8,7,8,6,1,3,5,5,4,3,0,0,0,0,0,0,5,8,8,8,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,7,7,7,7,7,5,1,1,2,0,0,0,0,0,0,0,4,7,8,7,7,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,7,7,7,7,7,7,6,2,1,0,0,0,0,0,2,5,7,7,7,7,7,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,", "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,7,6,7,6,7,7,7,7,6,5,4,4,5,5,7,7,7,8,8,7,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0," }; const int numStrings = sizeof(lisaStrings) / sizeof(lisaStrings[0]); // Publish each message with a delay for (int i = 0; i < numStrings; i++) { client.publish("ESP32Cam_NRF24L01/data", lisaStrings[i]); delay(500); } }
If you want to create your own images, this python script processes an input image that is formatted as an ASCII PGM (Portable Gray Map, see https://people.sc.fsu.edu/~jburkardt/data/pgma/pgma.html for background information) format that has a resolution of 63 x 32.
#!/usr/bin/env python3 import sys import time import os def step1_scaling(input_file="input.pgm", output_file="output.step1"): """ Reads input.pgm, skips the first 4 lines, and for each remaining line, converts the value to an integer, divides it by 16 (using integer division), and writes the result to output.step1. """ try: with open(input_file, 'r') as infile: lines = infile.readlines()[4:] # skip first 4 header lines except FileNotFoundError: sys.exit(1) with open(output_file, 'w') as outfile: for line in lines: stripped_line = line.strip() if not stripped_line: continue # Skip empty lines try: number = int(stripped_line) result = number // 16 outfile.write(f"{result}\n") except ValueError: print(f"Skipping invalid line: {stripped_line}") def step2_processing(input_file="output.step1", output_file="output.step2"): """ Processes output.step1 by reading all numbers and joining them into a single comma-separated string. It then breaks this list into lines, with each line containing 63 fields. """ try: with open(input_file, 'r') as infile: # Read non-empty, stripped lines as fields fields = [line.strip() for line in infile if line.strip()] except FileNotFoundError: sys.exit(1) with open(output_file, 'w') as outfile: # Process fields in chunks of 63 items for i in range(0, len(fields), 63): chunk = fields[i:i+63] line = ",".join(chunk) outfile.write(line + "\n") def step3_sorting(input_file="output.step2", output_file="output.step3"): """ Reverses the order of the lines in output.step2 and writes them to output.step3. """ if not os.path.isfile(input_file): sys.exit(1) with open(input_file, 'r') as infile: lines = infile.readlines() with open(output_file, 'w') as outfile: for line in reversed(lines): outfile.write(line) def process_line(line): """ Formats a given line by wrapping it inside an eggString assignment. """ line = line.rstrip("\n") return '"' + line + ',",' def step4_formatting(input_file="output.step3", output_file="output.step4"): """ Reads each line from output.step3, formats it using process_line(), and writes the result along with MQTT publishing commands to output.step4. """ try: with open(input_file, 'r') as infile, open(output_file, 'w') as outfile: for line in infile: modified_line = process_line(line) outfile.write(modified_line + "\n") except FileNotFoundError: sys.exit(1) def main(): step1_scaling() step2_processing() step3_sorting() step4_formatting() if __name__ == "__main__": main()
Plotting the results in Processing may not be preferred, so this python script will publish the plot to a MQTT broker. From there, Node-Red can subscribe to the topic and display the image. Here is Python code to create a plot in the “inferno” color scale.
import paho.mqtt.client as mqtt import matplotlib.pyplot as plt import numpy as np import io import base64 # Create a global array to hold 32 rows of data. data_array = [None] * 32 # Global counter to track which row to update next. current_row = 0 def update_plot(): """ Update the plot if all 32 rows have been received. The plot is generated as a heatmap and published to the MQTT topic 'sensor/plot'. """ if not all(row is not None for row in data_array): print("Waiting for complete data array...") return # Convert the list of rows to a NumPy array. arr = np.array(data_array) # Create a heatmap of the array. plt.figure(figsize=(8, 6)) plt.style.use('dark_background') # plt.imshow(arr, cmap='viridis', aspect='auto') plt.imshow(arr, cmap='inferno', aspect='auto', origin='lower') plt.title("Sensor Data Array") plt.colorbar() # Save the plot to an in-memory bytes buffer. buf = io.BytesIO() plt.savefig(buf, format='png') plt.close() # Free up memory. buf.seek(0) # Encode the image to a Base64 string. img_base64 = base64.b64encode(buf.read()).decode('utf-8') # Publish the encoded image to the 'sensor/plot' topic. mqtt_client.publish("sensor/plot", img_base64) print("Plot updated and published.") def on_connect(client, userdata, flags, rc): """ Callback for when the MQTT client connects. Subscribes to the 'sensor/data' topic. """ print("Connected with result code " + str(rc)) client.subscribe("sensor/data") def on_message(client, userdata, msg): """ Callback for when an MQTT message is received. Parses the comma-separated data, updates the global data array, and calls update_plot(). """ global data_array, current_row try: # Decode the payload to a string and strip whitespace. payload = msg.payload.decode('utf-8').strip() print("Received message:", payload) # Split the string by commas. # This filters out any empty strings (for example, from a trailing comma). values_str = [s for s in payload.split(',') if s] # Convert the string values to floats. row_values = [float(v) for v in values_str] # Use the current_row counter as the row index. row_index = current_row current_row += 1 # Check if row_index is within bounds. if row_index >= len(data_array): print("Row index out of bounds:", row_index) return # Update the data array with the new row. data_array[row_index] = row_values print(f"Updated row {row_index}.") # Update the plot. update_plot() except Exception as e: print("Error processing message:", e) # Create an MQTT client instance. mqtt_client = mqtt.Client() mqtt_client.on_connect = on_connect mqtt_client.on_message = on_message # Connect to your MQTT broker. mqtt_client.connect("<MQTT Broker>", 1883, 60) # Start the MQTT client network loop - but this was a problem so it's commented out # mqtt_client.loop_forever()
Here is the Node-Red flow.
These are 2 simple MQTT In nodes followed by a template. Here is the template code.
<div> <img src="data:image/png;base64,{{msg.payload}}" style="width:100%;" /> </div>
Inspiration for this post goes out to Rohf Henkel and J. Coliz, https://www.riyas.org/2013/12/working-quick-start-guide-for-nrf24l01.html