FHEM: Home Automation remote display using small OLED and MQTT

I wanted to have a small display with some essential Home Automation data. So I buy an 1.3″ OLED display with I2C interface and started to look for some existing code for an ESP8266. The display should be feed using WiFi and be portable, so I can place it where needed.

I started with ESPeasy, as there is already support for multiple pages with text lines. Unfortunately the code is written to show sensor data of directly connected sensor only. No option to publish free text or data from a Home Automation system. The ESPeasy code will save the text to flash memory on every change when using the Web GUI. The flash memory would wear out after some month with my use case.

Finally I decided to start my own code. First I had to find out that the OLED display is not an SD1306 but a SH1106 one. Although some people state that these are more or less the same, the SD1306 libraries did not work for me. Then I found https://github.com/ThingPulse/esp8266-oled-ssd1306 which works well for me.

Starting with SSD1306UiDemo.ino I added WiFi and MQTT libaries and code.

My code will show 6 lines of text on two frames (3 lines each). The frames will change every 5 seconds. The content of the text lines is published by a FHEM server. As there was no easy way to integrate the display using a MQTT_DEVICE definition in FHEM, I wrote my own perl mqttmsg sub function and used the FHEM notify definition to publish changes to the display.

a small video

The FHEM raw code for one of these notifications is:

defmod nTerrasse notify SD_WS07_TH_2:temperature:.* { mqttmsg ("display1/text4", "Terrass ". ReadingsVal('SD_WS07_TH_2','temperature', '0').'°C');;;; }

The mqttmsg code is inserted in the 99_myUtils.pm FHEM modul file. The MQTT messages are published with retain flag set, so they will persist and immediately published to any subscriber.

package main;
use strict;
use warnings;
use POSIX;
use Time::Local;
...
sub mqttmsg{
 my @args = @_;
 my $topic=$args[0];
 my $msg="";
 my $cnt=scalar @args;
 foreach my $i (1..$#args){
    $msg.=" ".$args[$i];
 }
 $msg =~ s/^\s+|\s+$//g;
 qx(/usr/bin/mosquitto_pub -h 192.168.0.40 -t '$topic' -m '$msg' -q 1 -r);
}
...
1;

The SH1106_mqtt_display.ino code uses 6 separate text variables (and one for the clock text). I know that there are better ways, but that is easy to code and understand:

...
String myText1;
String myText2;
String myText3;
String myText4;
String myText5;
String myText6;
String myClockText;
...
void msOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) {
 display->setTextAlignment(TEXT_ALIGN_RIGHT);
 display->setFont(ArialMT_Plain_10);
 String rssi="dbm ";
 rssi+=String(WiFi.RSSI());
 display->drawString(128, 0, rssi);
 display->setTextAlignment(TEXT_ALIGN_LEFT);
 display->drawString(0, 0, myClockText);
 
}
...
void drawFrame2(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
 display->setTextAlignment(TEXT_ALIGN_LEFT);
 y*=3;
 //display->setFont(ArialMT_Plain_10);
 display->setFont(myFont);//ArialMT_Plain_16);
 
 display->drawString(0 + x, 10 + y, myText1);// "Arial 10");

display->drawString(0 + x, 24 + y, myText2);

//display->setFont(ArialMT_Plain_24);
 display->drawString(0 + x, 38 + y, myText3);
}
...
void callback(char* topic, byte* payload, unsigned int length) {
 Serial.print("Message arrived [");
 Serial.print(topic);
 
 Serial.print("] ");
 display.clear();
 String msg;
 for (int i = 0; i < length; i++) {
 Serial.print((char)payload[i]);
 //display.clear();
 msg += (char)payload[i];
 }
 //assign text lines?
 String topicS="";
 topicS+=topic;
 if(topicS.indexOf( "text1" )>0)
 myText1=msg;
 else if(topicS.indexOf( "text2" )>0)
 myText2=msg;
 else if(topicS.indexOf( "text3" )>0)
 myText3=msg;
 else if(topicS.indexOf( "text4" )>0)
 myText4=msg;
 else if(topicS.indexOf( "text5" )>0)
 myText5=msg;
 else if(topicS.indexOf( "text6" )>0)
 myText6=msg;
 else if(topicS.indexOf( "clock" )>0)
 myClockText=msg;
...
 client.publish("display1/ip", (char*) payload.c_str(), true);
 
 // ... and resubscribe
 client.subscribe("display1/#",1);
...

At the top row the OLED display will show the date and time on the left, updated every minute fro the FHEM server using an ‘at’ definition. On the right the top line will show the RSSI of the WiFi signal.