OpenMarine

Full Version: Wireless MQTT Data Display
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
Coding for my first adventure in MQTT is done. I needed a data display for the nav station that always gave me lat/lon speed, etc. Easier to glance up at something when I'm doing log entries.

Now, what is always the harder part - finding a case that looks decent.

https://photos.app.goo.gl/skKmmRFLaYApiAa29
(Not understanding why I can't insert an image here...)
@abarrow : from the picture it looks like you used an ESP32 with some display.
Could you please share more information on the hw used and maybe also some project writeup ?
Okay. The display is an I2C 20x4 LCD display. They are available for very little from places like Amazon or Aliexpress.

The controller is indeed an ESP32. This one is a DOIT DEVKIT V1, but there are lots of generic modules available that will do the same thing. Since all you need in terms of IO are the two I2C connections and a 3.3V/GND, the connections are pretty simple. I would guess that what you are seeing the picture doesn't cost more than around $12 USD for everything.

As I said, I'm still searching for a decent case for it all. 

I programmed this using the Arduino IDE. Here is the code:

Code:
/*
Basic MQTT Connection for NMEA
*/
#include <WiFi.h>
#include <WiFiClient.h>
#include <PubSubClient.h>
#include <TinyGPS++.h>
#include <LiquidCrystal_I2C.h>


// Update these with values suitable for your network.

const char * ssid = "<YOUR WIFI SSID>";
const char * password = "<YOUR WIFI PASSWORD>";
const char * mqtt_server = "<YOUR MQTT SERVER IP ADDRESS>";

// The TinyGPS++ object
TinyGPSPlus gps;

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;

// set the LCD number of columns and rows
int lcdColumns = 20;
int lcdRows = 4;

String DBT = "";
String northSouth = "N ";
String eastWest = "E ";
double latRaw;
double lonRaw;
String latiTude = "";
String longiTude = "";

// set LCD address, number of columns and rows
// if you don't know your display address, run an I2C scanner sketch
LiquidCrystal_I2C lcd(0x27, lcdColumns, lcdRows);

//Create the knot character
byte customChar[8] = {
 0b10000,
 0b10100,
 0b11000,
 0b10100,
 0b00010,
 0b00111,
 0b00010,
 0b00011
};

void setup_wifi() {

 delay(10);
 // We start by connecting to a WiFi network
 Serial.println();
 Serial.print("Connecting to ");
 lcd.setCursor(0,0);
 lcd.print("SSID: ");
 Serial.println(ssid);
 lcd.setCursor(7,0);
 lcd.print(ssid);

 WiFi.begin(ssid, password);
 int dot_cur = 0;
 lcd.setCursor(0,1);
 while (WiFi.status() != WL_CONNECTED) {
   delay(500);
   Serial.print(".");
   lcd.print(".");
 }

 randomSeed(micros());

 Serial.println("");
 lcd.clear();
 Serial.println("WiFi connected");
 lcd.setCursor(0,0);
 lcd.print("WiFi connected");
 Serial.println("IP address: ");
 lcd.setCursor(0,2);
 lcd.print("IP: ");
 Serial.println(WiFi.localIP());
 lcd.setCursor(5,2);
 lcd.print(WiFi.localIP());
}

void setup_lcd(){
 // initialize LCD
 lcd.init();
 // turn on LCD backlight                      
 lcd.backlight();
 // set cursor to first column, first row
 lcd.setCursor(0, 0);
}

void callback(char* topic, byte* payload, unsigned int length) {
 for (int i=0;i<length;i++) {
   if (gps.encode(*payload++))
     displayInfo();
 }
 Serial.println();
}

void reconnect() {
 // Loop until we're reconnected
 while (!client.connected()) {
   Serial.print("Attempting MQTT connection...");
   // Create a random client ID
   String clientId = "ESP32Client-";
   clientId += String(random(0xffff), HEX);
   // Attempt to connect
   if (client.connect(clientId.c_str())) {
     Serial.println("connected");
     client.subscribe("inTopic"); // whatever the topic name is for your NMEA stream from your MQTT server.
     //arrow characters in the upper right when MQTT is connected
     lcd.setCursor(19,0);
     lcd.print(char(127));
     lcd.setCursor(19,1);
     lcd.print(char(126));
     delay(500);
   } else {
     Serial.print("failed, rc=");
     Serial.print(client.state());
     Serial.println(" try again in 5 seconds");
     lcd.setCursor(0,1);
     lcd.print("MQTT Failed");
     lcd.setCursor(0,2);
     lcd.print("rc=");
     lcd.print(client.state());
     // Wait 5 seconds before retrying
     delay(5000);
   }
 }
}

void setup() {
 Serial.begin(115200);
 setup_lcd();
 // create a new custom character for knot
 lcd.createChar(0, customChar);
 setup_wifi();
 client.setServer(mqtt_server, 1883);
 client.setCallback(callback);
 //Delay 5 secs so we can see what happened
 delay (5000);
 lcd.clear();
}

void loop() {

 if (!client.connected()) {
   reconnect();
 }
 client.loop();
}

void displayInfo()
{
 Serial.print(F("Location: "));
 if (gps.location.isValid())
 {
   latRaw = gps.location.lat();
   Serial.print(latRaw);
   if (latRaw < 0){
     northSouth = "S ";
   }
   else
   {
     northSouth = "N ";
   }
   latRaw = abs(latRaw);
   int latDeg = latRaw;
   Serial.println(latDeg);
   double latMins = (latRaw - latDeg) * 60;
   Serial.println(latMins);
   latiTude = northSouth;
   if (latDeg < 100){
     latiTude += " ";
   }
   else if (latDeg < 10) {
     latiTude += "  ";
   }
   latiTude += latDeg;
   latiTude += char(223);
   if (latMins <10)
     latiTude += " ";
   latiTude += String(latMins,3);
   latiTude += "\'  ";
   lcd.setCursor(0,0);
   lcd.print("LAT: ");
   lcd.print(latiTude);
   Serial.print(F(","));
   Serial.print(gps.location.lng(), 6);
   lonRaw = gps.location.lng();
   if (lonRaw < 0){
     eastWest = "W ";    
   }
   else
   {
     eastWest = "E ";
   }
   lonRaw = abs(lonRaw);
   int lonDeg = lonRaw;
   double lonMins = (lonRaw - lonDeg) * 60;
   longiTude = eastWest;
   if (lonDeg < 100){
     longiTude += " ";
   }
   else if (lonDeg < 10) {
     longiTude += "  ";
   }
   longiTude += lonDeg;
   longiTude += char(223);
   if (lonMins < 10)
     longiTude += " ";
   longiTude += String(lonMins,3);
   longiTude += "\'  ";
   lcd.setCursor(0,1);
   lcd.print("LON: ");
   lcd.print(longiTude);
 }
 else
 {
   Serial.print(F("INVALID"));
   lcd.setCursor(0,0);
   lcd.print("========NAN========");
   lcd.setCursor(0,1);
   lcd.print("========NAN========");
 }

 if (gps.speed.isValid()) {
   lcd.setCursor(10,2);
   lcd.print("SOG: ");
   lcd.print(gps.speed.knots(),1);
   //lcd.print("K");
   lcd.write((uint8_t)0);
   if (gps.speed.knots() < 10){
     lcd.print(" ");
   }
 }
 else
 {
   Serial.print(F("INVALID"));
   lcd.setCursor(10,2);
   lcd.print("===NAN===");
 }

 if (gps.course.isValid()){
   Serial.println("");
   Serial.print(F("COG="));
   lcd.setCursor(0,2);
   lcd.print("COG: ");
   Serial.println(gps.course.deg());
   lcd.print(gps.course.deg(), 0);
   lcd.print(char(223));
   if (gps.course.deg()<10){
     lcd.print("   ");    
   }
   else if (gps.course.deg()<100){
     lcd.print("  ");
   }
 }
   else
 {
   Serial.print(F("INVALID"));
   lcd.setCursor(0,2);
   lcd.print("==NAN==");
 }

 Serial.print(F("  Date/Time: "));
 if (gps.date.isValid())
 {
   lcd.setCursor(10,3);
   Serial.print(gps.date.month());
   lcd.print(gps.date.month());
   Serial.print(F("/"));
   lcd.print(F("/"));
   Serial.print(gps.date.day());
   lcd.print(gps.date.day());
   Serial.print(F("/"));
   lcd.print(F("/"));    
   Serial.print(gps.date.year());
   lcd.print(gps.date.year());
 }
 else
 {
   Serial.print(F("INVALID"));
   lcd.setCursor(10,3);
   lcd.print("===NAN===");
 }

 Serial.print(F(" "));
 if (gps.time.isValid())
 {
   lcd.setCursor(0,3);
   if (gps.time.hour() < 10) {
     Serial.print(F("0"));
     lcd.print(F("0"));
   }
   lcd.print(gps.time.hour());
   Serial.print(gps.time.hour());
   Serial.print(F(":"));
   lcd.print(":");
   if (gps.time.minute() < 10){
     Serial.print(F("0"));
     lcd.print(F("0"));
   }
   Serial.print(gps.time.minute());
   lcd.print(gps.time.minute());
   Serial.print(F(":"));
   lcd.print(":");
   if (gps.time.second() < 10) {
     Serial.print(F("0"));
     lcd.print(F("0"));
   }
   Serial.print(gps.time.second());
   lcd.print(gps.time.second());
   Serial.print(F("."));
   if (gps.time.centisecond() < 10){
     Serial.print(F("0"));
   }
   Serial.print(gps.time.centisecond());
   lcd.print("Z");
 }
 else
 {
   Serial.print(F("INVALID"));
   lcd.setCursor(0,3);
   lcd.print("===NAN===");
 }

 Serial.println();
}
Good luck!

Andy
I did similar a while ago but just used easyesp on an esp8266 driving the display then sent data to the screen over mptt from node-red on the Pi. Quite quick to set up. Will it going properly for sure before heading offshore again solo, would be great to have a little carry around display.
You inspired me to go looking around. This looks interesting. Might be more daylight readable, and since it's all one unit, it might be easier to find an enclosure for.

https://www.aliexpress.com/item/AZSMZ-EP...15273.html
E-ink - great idea! Christmas gonna come early this year Wink
That looks great abarrow. I'd like to copy it. Where/how do you get your NMEA stream into MQTT - is it a signalk plugin, do you construct it in signalk and send to mqtt_out, localhost:10110 doesnt work anymore,.... I'm stumped! Can you share how you did it? Huh   

You mentioned a suitable casing. The 4x20 lcd is typically used for the 3d printer display, so there's a bunch of stl files out there - maybe like this https://www.thingiverse.com/thing:1903059.  Our library will 3d print your stl file or there are internet based 3d printing  services like Shapeways. If that does work out - I could print and mail ya one. 

Thx!
It's Node Red. I'll send you the script when I get home.
(2019-02-08, 02:57 AM)abarrow Wrote: [ -> ]It's Node Red. I'll send you the script when I get home.

Here's the script. It is super simple - just connect to a TCP port (which is configured in OpenPlotter to send out NMEA strings), and output to MQTT.

Code:
[
   {
       "id": "d7c474e4.e397e8",
       "type": "tab",
       "label": "Flow 1",
       "disabled": false,
       "info": ""
   },
   {
       "id": "2bb5840f.59abac",
       "type": "debug",
       "z": "d7c474e4.e397e8",
       "name": "",
       "active": true,
       "tosidebar": true,
       "console": false,
       "tostatus": false,
       "complete": "false",
       "x": 719,
       "y": 167,
       "wires": []
   },
   {
       "id": "a00c6215.e7772",
       "type": "mqtt out",
       "z": "d7c474e4.e397e8",
       "name": "",
       "topic": "inTopic",
       "qos": "",
       "retain": "",
       "broker": "30d4d987.926566",
       "x": 990,
       "y": 348,
       "wires": []
   },
   {
       "id": "1e891e14.3239c2",
       "type": "tcp in",
       "z": "d7c474e4.e397e8",
       "name": "",
       "server": "client",
       "host": "127.0.0.1",
       "port": "44444",
       "datamode": "stream",
       "datatype": "utf8",
       "newline": "\\n",
       "topic": "",
       "base64": false,
       "x": 265,
       "y": 326,
       "wires": [
           [
               "a00c6215.e7772",
               "2bb5840f.59abac"
           ]
       ]
   },
   {
       "id": "30d4d987.926566",
       "type": "mqtt-broker",
       "z": "",
       "name": "",
       "broker": "<YOUR MQTT BROKER ADDRESS>",
       "port": "1883",
       "clientid": "",
       "usetls": false,
       "compatmode": true,
       "keepalive": "60",
       "cleansession": true,
       "birthTopic": "",
       "birthQos": "0",
       "birthPayload": "howdy partner",
       "closeTopic": "",
       "closeQos": "0",
       "closePayload": "",
       "willTopic": "",
       "willQos": "0",
       "willPayload": ""
   }
]
Here is the updated code. I put in a watchdog timer to restart the system if it doesn't get a WiFi connection in 30 seconds. This helps because this device powers up at the same time as my OpenPlotter computer, which takes slightly more than 30 seconds to boot and start OpenPlotter.
Code:
/*
Basic MQTT Connection for NMEA
*/
#include <WiFi.h>
#include <WiFiClient.h>
#include <PubSubClient.h>
#include <TinyGPS++.h>
#include <LiquidCrystal_I2C.h>


// Update these with values suitable for your network.

const char* ssid = "<YOUR SSID>";
const char* password = "<YOUR WIFI PASSWORD>";
const char* mqtt_server = "10.10.10.1";
const char* mqttUser = "pi";
const char* mqttPassword = "<YOUR MQTT PASSWORD>";

// The TinyGPS++ object
TinyGPSPlus gps;

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;

// set the LCD number of columns and rows
int lcdColumns = 20;
int lcdRows = 4;

String DBT = "";
String northSouth = "N ";
String eastWest = "E ";
double latRaw;
double lonRaw;
String latiTude = "";
String longiTude = "";

// set LCD address, number of columns and rows
// if you don't know your display address, run an I2C scanner sketch
LiquidCrystal_I2C lcd(0x27, lcdColumns, lcdRows);

//Create the knot character
byte customChar[8] = {
 0b10000,
 0b10100,
 0b11000,
 0b10100,
 0b00010,
 0b00111,
 0b00010,
 0b00011
};

void setup_wifi() {

 delay(10);
 // We start by connecting to a WiFi network
 Serial.println();
 Serial.print("Connecting to ");
 lcd.setCursor(0,0);
 lcd.print("SSID: ");
 Serial.println(ssid);
 lcd.setCursor(7,0);
 lcd.print(ssid);

 WiFi.begin(ssid, password);
 int dot_cur = 0;
 lcd.setCursor(0,1);
 int reset_index = 0;
 while (WiFi.status() != WL_CONNECTED) {
   delay(500);
   Serial.print(".");
   lcd.print(".");
   //If WiFi doesn't connect in 30 seconds, do a software reset
   reset_index ++;
   if (reset_index > 60) {
     Serial.println("WIFI Failed - restarting");
     lcd.setCursor(0,2);
     lcd.print("No WIFI: restarting");
     ESP.restart();
   }
 }

 randomSeed(micros());

 Serial.println("");
 lcd.clear();
 Serial.println("WiFi connected");
 lcd.setCursor(0,0);
 lcd.print("WiFi connected");
 Serial.println("IP address: ");
 lcd.setCursor(0,2);
 lcd.print("IP: ");
 Serial.println(WiFi.localIP());
 lcd.setCursor(5,2);
 lcd.print(WiFi.localIP());
}

void setup_lcd(){
 // initialize LCD
 lcd.init();
 // turn on LCD backlight                      
 lcd.backlight();
 // set cursor to first column, first row
 lcd.setCursor(0, 0);
}

void callback(char* topic, byte* payload, unsigned int length) {
 for (int i=0;i<length;i++) {
   if (gps.encode(*payload++))
     displayInfo();
 }
 Serial.println();
}

void reconnect() {
 // Loop until we're reconnected
 while (!client.connected()) {
   Serial.print("Attempting MQTT connection...");
   // Create a random client ID
   String clientId = "ESP32Client-";
   clientId += String(random(0xffff), HEX);
   // Attempt to connect
   if (client.connect(clientId.c_str(),mqttUser,mqttPassword)) {
     Serial.println("connected");
     client.subscribe("inTopic");
     //arrow characters in the upper right when MQTT is connected
     lcd.setCursor(19,0);
     lcd.print(char(127));
     lcd.setCursor(19,1);
     lcd.print(char(126));
     delay(500);
   } else {
     Serial.print("failed, rc=");
     Serial.print(client.state());
     Serial.println(" try again in 5 seconds");
     lcd.setCursor(0,1);
     lcd.print("MQTT Failed");
     lcd.setCursor(0,2);
     lcd.print("rc=");
     lcd.print(client.state());
     // Wait 5 seconds before retrying
     delay(5000);
   }
 }
}

void setup() {
 Serial.begin(115200);
 setup_lcd();
 // create a new custom character for knot
 lcd.createChar(0, customChar);
 setup_wifi();
 client.setServer(mqtt_server, 1883);
 client.setCallback(callback);
 //Delay 5 secs so we can see what happened
 delay (5000);
 lcd.clear();
}

void loop() {

 if (!client.connected()) {
   reconnect();
 }
 client.loop();
}

void displayInfo()
{
 Serial.print(F("Location: "));
 if (gps.location.isValid())
 {
   latRaw = gps.location.lat();
   Serial.print(latRaw);
   if (latRaw < 0){
     northSouth = "S ";
   }
   else
   {
     northSouth = "N ";
   }
   latRaw = abs(latRaw);
   int latDeg = latRaw;
   Serial.println(latDeg);
   double latMins = (latRaw - latDeg) * 60;
   Serial.println(latMins);
   latiTude = northSouth;
   if (latDeg < 100){
     latiTude += " ";
   }
   else if (latDeg < 10) {
     latiTude += "  ";
   }
   latiTude += latDeg;
   latiTude += char(223);
   if (latMins <10)
     latiTude += " ";
   latiTude += String(latMins,3);
   latiTude += "\'  ";
   lcd.setCursor(0,0);
   lcd.print("LAT: ");
   lcd.print(latiTude);
   Serial.print(F(","));
   Serial.print(gps.location.lng(), 6);
   lonRaw = gps.location.lng();
   if (lonRaw < 0){
     eastWest = "W ";    
   }
   else
   {
     eastWest = "E ";
   }
   lonRaw = abs(lonRaw);
   int lonDeg = lonRaw;
   double lonMins = (lonRaw - lonDeg) * 60;
   longiTude = eastWest;
   if (lonDeg < 100){
     longiTude += " ";
   }
   else if (lonDeg < 10) {
     longiTude += "  ";
   }
   longiTude += lonDeg;
   longiTude += char(223);
   if (lonMins < 10)
     longiTude += " ";
   longiTude += String(lonMins,3);
   longiTude += "\'  ";
   lcd.setCursor(0,1);
   lcd.print("LON: ");
   lcd.print(longiTude);
 }
 else
 {
   Serial.print(F("INVALID"));
   lcd.setCursor(0,0);
   lcd.print("========NAN========");
   lcd.setCursor(0,1);
   lcd.print("========NAN========");
 }

 if (gps.speed.isValid()) {
   lcd.setCursor(10,2);
   lcd.print("SOG: ");
   lcd.print(gps.speed.knots(),1);
   //lcd.print("K");
   lcd.write((uint8_t)0);
   if (gps.speed.knots() < 10){
     lcd.print(" ");
   }
 }
 else
 {
   Serial.print(F("INVALID"));
   lcd.setCursor(10,2);
   lcd.print("===NAN===");
 }

 if (gps.course.isValid()){
   Serial.println("");
   Serial.print(F("COG="));
   lcd.setCursor(0,2);
   lcd.print("COG: ");
   Serial.println(gps.course.deg());
   lcd.print(gps.course.deg(), 0);
   lcd.print(char(223));
   if (gps.course.deg()<10){
     lcd.print("   ");    
   }
   else if (gps.course.deg()<100){
     lcd.print("  ");
   }
 }
   else
 {
   Serial.print(F("INVALID"));
   lcd.setCursor(0,2);
   lcd.print("==NAN==");
 }

 Serial.print(F("  Date/Time: "));
 if (gps.date.isValid())
 {
   lcd.setCursor(10,3);
   Serial.print(gps.date.month());
   lcd.print(gps.date.month());
   Serial.print(F("/"));
   lcd.print(F("/"));
   Serial.print(gps.date.day());
   lcd.print(gps.date.day());
   Serial.print(F("/"));
   lcd.print(F("/"));    
   Serial.print(gps.date.year());
   lcd.print(gps.date.year());
 }
 else
 {
   Serial.print(F("INVALID"));
   lcd.setCursor(10,3);
   lcd.print("===NAN===");
 }

 Serial.print(F(" "));
 if (gps.time.isValid())
 {
   lcd.setCursor(0,3);
   if (gps.time.hour() < 10) {
     Serial.print(F("0"));
     lcd.print(F("0"));
   }
   lcd.print(gps.time.hour());
   Serial.print(gps.time.hour());
   Serial.print(F(":"));
   lcd.print(":");
   if (gps.time.minute() < 10){
     Serial.print(F("0"));
     lcd.print(F("0"));
   }
   Serial.print(gps.time.minute());
   lcd.print(gps.time.minute());
   Serial.print(F(":"));
   lcd.print(":");
   if (gps.time.second() < 10) {
     Serial.print(F("0"));
     lcd.print(F("0"));
   }
   Serial.print(gps.time.second());
   lcd.print(gps.time.second());
   Serial.print(F("."));
   if (gps.time.centisecond() < 10){
     Serial.print(F("0"));
   }
   Serial.print(gps.time.centisecond());
   lcd.print("Z");
 }
 else
 {
   Serial.print(F("INVALID"));
   lcd.setCursor(0,3);
   lcd.print("===NAN===");
 }

 Serial.println();
}
Thx abarrow. Its working! I changed the node-red flow to use port 30330 and modified MQTT out to specify the same MQTT server name as the ClientId in the arduino code, and almost forgot to click the security tab and add the user/pass equal to that at the mqtt tab - and same servernames, ips, user and passes at arduino code.
On MQTT tab added an entry with a topic name of InTopic  - or whatever is used in the arduino code for client.subscribe()  -  type Signal K delta input. Works great!

I really like this.  I'm going to 3d print a case tomorrow, I'll post a pic to see what you think.

Thanks again.
Pages: 1 2