Files
OpenMQTT-Gateway/main/ZdisplaySSD1306.ino
T
FlorianandFlorian 3187ed6154 [SERIAL] Enable SERIAL to X communication (#2068)
Add Serial as a communication layer so as to transmit data to/from serial
Enable discovery of BT sensors through serial
Add serial heartbeat

Change discovery principle for interval and intervalacts, keep them always discovered and deactivate adaptivescan when changed. Instead of removing their discovery when adaptive scan is activated

Increase JSON_MSG_BUFFER_MAX as we are now using const char* and it requires more memory for Arduino Json Deserialization

Co-authored-by: Florian <1technophile@users.noreply.github.com>
2024-09-26 06:55:26 -05:00

528 lines
16 KiB
Arduino

/*
OpenMQTTGateway Addon - ESP8266 or Arduino program for home automation
Act as a gateway between your 433mhz, infrared IR, BLE, LoRa signal and one interface like an MQTT broker
Send and receiving command by MQTT
Supported boards with displays
HELTEC ESP32 LORA - SSD1306 / Onboard 0.96-inch 128*64 dot matrix OLED display
LILYGO® LoRa32 V2.1_1.6.1 433 Mhz / https://www.lilygo.cc/products/lora3?variant=42476923879605
Copyright: (c)Florian ROBERT
Contributors:
- 1technophile
- NorthernMan54
This file is part of OpenMQTTGateway.
OpenMQTTGateway is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenMQTTGateway is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if defined(ZdisplaySSD1306)
# include <ArduinoJson.h>
# include "ArduinoLog.h"
# include "User_config.h"
# include "config_SSD1306.h"
# ifdef DISPLAY_BLANKING
# include "driver/touch_sensor.h"
# endif
SemaphoreHandle_t semaphoreOLEDOperation;
boolean logToOLEDDisplay = LOG_TO_OLED;
boolean jsonDisplay = JSON_TO_OLED;
boolean displayFlip = DISPLAY_FLIP;
boolean displayState = DISPLAY_STATE;
boolean idlelogo = DISPLAY_IDLE_LOGO;
uint8_t displayBrightness = DISPLAY_BRIGHTNESS;
/*
Toogle log display
*/
void logToOLED(bool display) {
logToOLEDDisplay = display;
display ? Log.begin(LOG_LEVEL_OLED, &Oled) : Log.begin(LOG_LEVEL, &Serial); // Log on OLED following LOG_LEVEL_OLED
}
/*
module setup, for use in Arduino setup
*/
void setupSSD1306() {
SSD1306Config_init();
SSD1306Config_load();
Log.trace(F("Setup SSD1306 Display" CR));
Log.trace(F("ZdisplaySSD1306 command topic: %s" CR), subjectMQTTtoSSD1306set);
Log.trace(F("ZdisplaySSD1306 log-oled: %T" CR), logToOLEDDisplay);
Log.trace(F("ZdisplaySSD1306 json-oled: %T" CR), jsonDisplay);
Log.trace(F("ZdisplaySSD1306 DISPLAY_PAGE_INTERVAL: %d" CR), DISPLAY_PAGE_INTERVAL);
Log.trace(F("ZdisplaySSD1306 DISPLAY_IDLE_LOGO: %T" CR), idlelogo);
Log.trace(F("ZdisplaySSD1306 DISPLAY_FLIP: %T" CR), displayFlip);
Oled.begin();
Log.notice(F("Setup SSD1306 Display end" CR));
# if LOG_TO_OLED
Log.begin(LOG_LEVEL_OLED, &Oled); // Log on OLED following LOG_LEVEL_OLED
jsonDisplay = false;
# else
jsonDisplay = true;
# endif
}
boolean logoDisplayed = false;
unsigned long nextDisplayPage = uptime() + DISPLAY_PAGE_INTERVAL;
# ifdef DISPLAY_BLANKING
unsigned long blankingStart = uptime() + DISPLAY_BLANKING_START;
touch_value_t touchReadings[TOUCH_READINGS] = {0};
touch_value_t touchCurrentReading = 0;
int touchIndex = 0;
int touchTotal = 0;
int touchAverage = 0;
int touchThreshold = 0;
# endif
/*
module loop, for use in Arduino loop
*/
void loopSSD1306() {
/*
Function to check if json messages are in the queue and send them for display
long enough since the last message and display not being used and a queue message waiting
*/
# ifdef DISPLAY_BLANKING
// Log.trace(F("touchAverage %d, touchCurrentReading %d, touchThreshold %d" CR), touchAverage, touchCurrentReading, touchThreshold);
touchTotal = touchTotal - touchReadings[touchIndex];
touchCurrentReading = touchRead(DISPLAY_BLANKING_TOUCH_GPIO);
touchReadings[touchIndex] = touchCurrentReading;
touchTotal = touchTotal + touchReadings[touchIndex];
touchIndex = (touchIndex + 1) % TOUCH_READINGS;
touchAverage = touchTotal / TOUCH_READINGS;
touchThreshold = touchAverage * TOUCH_THRESHOLD;
if ((touchCurrentReading > touchAverage + touchThreshold || touchCurrentReading < touchAverage - touchThreshold) && displayState) {
blankingStart = uptime() + DISPLAY_BLANKING_START;
Oled.display->displayOn();
}
if (uptime() > blankingStart && displayState) {
Oled.display->displayOff();
}
# endif
if (jsonDisplay && displayState) {
if (uptime() >= nextDisplayPage && uxSemaphoreGetCount(semaphoreOLEDOperation) && currentWebUIMessage && newSSD1306Message) {
if (!Oled.displayPage(currentWebUIMessage)) {
Log.warning(F("[ssd1306] displayPage failed: %s" CR), currentWebUIMessage->title);
}
nextDisplayPage = uptime() + DISPLAY_PAGE_INTERVAL;
logoDisplayed = false;
newSSD1306Message = false;
}
}
/*
Display logo if it has been more than DISPLAY_PAGE_INTERVAL
*/
if (uptime() > nextDisplayPage + 1 && !logoDisplayed && idlelogo && displayState) {
Oled.display->normalDisplay();
Oled.fillScreen(BLACK);
Oled.drawLogo(rand() % 13 - 5, rand() % 32 - 13);
logoDisplayed = true;
}
}
/*
Handler for mqtt commands sent to the module
- log-oled: boolean
Enable / Disable display of log messages on display
*/
void XtoSSD1306(const char* topicOri, JsonObject& SSD1306data) { // json object decoding
bool success = false;
if (cmpToMainTopic(topicOri, subjectMQTTtoSSD1306set)) {
Log.trace(F("MQTTtoSSD1306 json set" CR));
// properties
if (SSD1306data.containsKey("onstate")) {
displayState = SSD1306data["onstate"].as<bool>();
Log.notice(F("Set display state: %T" CR), displayState);
success = true;
}
if (SSD1306data.containsKey("brightness")) {
displayBrightness = SSD1306data["brightness"].as<int>();
Log.notice(F("Set brightness: %d" CR), displayBrightness);
success = true;
}
if (SSD1306data.containsKey("log-oled")) {
logToOLEDDisplay = SSD1306data["log-oled"].as<bool>();
Log.notice(F("Set OLED log: %T" CR), logToOLEDDisplay);
logToOLED(logToOLEDDisplay);
if (logToOLEDDisplay) {
jsonDisplay = false;
}
success = true;
} else if (SSD1306data.containsKey("json-oled")) {
jsonDisplay = SSD1306data["json-oled"].as<bool>();
if (jsonDisplay) {
logToOLEDDisplay = false;
logToOLED(logToOLEDDisplay);
}
Log.notice(F("Set json-oled: %T" CR), jsonDisplay);
success = true;
}
if (SSD1306data.containsKey("idlelogo")) {
idlelogo = SSD1306data["idlelogo"].as<bool>();
success = true;
}
if (SSD1306data.containsKey("display-flip")) {
displayFlip = SSD1306data["display-flip"].as<bool>();
Log.notice(F("Set display-flip: %T" CR), displayFlip);
success = true;
}
// save, load, init, erase
if (SSD1306data.containsKey("save") && SSD1306data["save"]) {
SSD1306Config_save();
success = true;
} else if (SSD1306data.containsKey("load") && SSD1306data["load"]) {
success = SSD1306Config_load();
if (success) {
Log.notice(F("SSD1306 config loaded" CR));
}
} else if (SSD1306data.containsKey("init") && SSD1306data["init"]) {
SSD1306Config_init();
success = true;
if (success) {
Log.notice(F("SSD1306 config initialised" CR));
}
} else if (SSD1306data.containsKey("erase") && SSD1306data["erase"]) {
// Erase config from NVS (non-volatile storage)
preferences.begin(Gateway_Short_Name, false);
if (preferences.isKey("SSD1306Config")) {
success = preferences.remove("SSD1306Config");
}
preferences.end();
if (success) {
Log.notice(F("SSD1306 config erased" CR));
}
}
if (success) {
stateSSD1306Display();
} else {
Log.error(F("[ SSD1306 ] XtoSSD1306 Fail json" CR), SSD1306data);
}
}
}
void SSD1306Config_save() {
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
JsonObject jo = jsonBuffer.to<JsonObject>();
jo["onstate"] = displayState;
jo["brightness"] = displayBrightness;
jo["log-oled"] = logToOLEDDisplay;
jo["json-oled"] = jsonDisplay;
jo["idlelogo"] = idlelogo;
jo["display-flip"] = displayFlip;
// Save config into NVS (non-volatile storage)
String conf = "";
serializeJson(jsonBuffer, conf);
preferences.begin(Gateway_Short_Name, false);
int result = preferences.putString("SSD1306Config", conf);
preferences.end();
Log.notice(F("SSD1306 Config_save: %s, result: %d" CR), conf.c_str(), result);
}
void SSD1306Config_init() {
displayState = DISPLAY_STATE;
displayBrightness = DISPLAY_BRIGHTNESS;
logToOLEDDisplay = LOG_TO_OLED;
jsonDisplay = JSON_TO_OLED;
idlelogo = DISPLAY_IDLE_LOGO;
displayFlip = DISPLAY_FLIP;
Log.notice(F("SSD1306 config initialised" CR));
}
bool SSD1306Config_load() {
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
preferences.begin(Gateway_Short_Name, true);
if (preferences.isKey("SSD1306Config")) {
auto error = deserializeJson(jsonBuffer, preferences.getString("SSD1306Config", "{}"));
preferences.end();
if (error) {
Log.error(F("SSD1306 config deserialization failed: %s, buffer capacity: %u" CR), error.c_str(), jsonBuffer.capacity());
return false;
}
if (jsonBuffer.isNull()) {
Log.warning(F("SSD1306 config is null" CR));
return false;
}
JsonObject jo = jsonBuffer.as<JsonObject>();
displayState = jo["onstate"].as<bool>();
displayBrightness = jo["brightness"].as<int>();
logToOLEDDisplay = jo["log-oled"].as<bool>();
jsonDisplay = jo["json-oled"].as<bool>();
idlelogo = jo["idlelogo"].as<bool>();
displayFlip = jo["display-flip"].as<bool>();
Log.notice(F("Saved SSD1306 config loaded" CR));
return true;
} else {
preferences.end();
Log.notice(F("No SSD1306 config to load" CR));
return false;
}
}
// Simple print methonds
/*
Display three lines of text on display, scroll if needed
*/
void ssd1306Print(char* line1, char* line2, char* line3) {
Oled.println(line1);
Oled.println(line2);
Oled.println(line3);
delay(2000);
}
/*
Display two lines of text on display, scroll if needed
*/
void ssd1306Print(char* line1, char* line2) {
Oled.println(line1);
Oled.println(line2);
delay(2000);
}
/*
Display single line of text on display, scroll if needed
*/
void ssd1306Print(char* line1) {
Oled.println(line1);
delay(2000);
}
// This pattern was borrowed from HardwareSerial and modified to support the ssd1306 display
OledSerial Oled(0); // Not sure about this, came from Hardwareserial
OledSerial::OledSerial(int x) {
# if defined(WIFI_Kit_32) || defined(WIFI_LoRa_32) || defined(WIFI_LoRa_32_V2)
pinMode(RST_OLED, OUTPUT); // https://github.com/espressif/arduino-esp32/issues/4278
digitalWrite(RST_OLED, LOW);
delay(50);
digitalWrite(RST_OLED, HIGH);
display = new SSD1306Wire(0x3c, SDA_OLED, SCL_OLED, GEOMETRY_128_64);
# elif defined(Wireless_Stick)
// pinMode(RST_OLED, OUTPUT); // https://github.com/espressif/arduino-esp32/issues/4278
// digitalWrite(RST_OLED, LOW);
// delay(50);
// digitalWrite(RST_OLED, HIGH);
display = new SSD1306Wire(0x3c, SDA_OLED, SCL_OLED, GEOMETRY_64_32);
# elif defined(ARDUINO_TTGO_LoRa32_v21new) // LILYGO® Disaster-Radio LoRa V2.1_1.6.1
// pinMode(OLED_RST, OUTPUT); // https://github.com/espressif/arduino-esp32/issues/4278
// digitalWrite(OLED_RST, LOW);
// delay(50);
// digitalWrite(OLED_RST, HIGH);
display = new SSD1306Wire(0x3c, OLED_SDA, OLED_SCL, GEOMETRY_128_64);
# elif defined(GenericSSD1306) // a generic ssd1306 oled with of size 128*64
display = new SSD1306Wire(0x3c, OLED_SDA, OLED_SCL, GEOMETRY_128_64);
# endif
}
/*
Initialize ssd1306 oled display for use, and display OMG logo
*/
void OledSerial::begin() {
// SSD1306.begin(); // User OMG serial support
semaphoreOLEDOperation = xSemaphoreCreateBinary();
xSemaphoreGive(semaphoreOLEDOperation);
display->init();
if (displayFlip) {
display->flipScreenVertically();
} else {
display->resetOrientation();
}
display->setFont(ArialMT_Plain_10);
display->setBrightness(round(displayBrightness * 2.55));
drawLogo(0, 0);
display->invertDisplay();
display->setLogBuffer(OLED_TEXT_ROWS, OLED_TEXT_BUFFER);
delay(1000);
if (!displayState) {
display->displayOff();
}
}
/*
Dummy virtual functions carried over from Serial
*/
int OledSerial::available(void) {
}
/*
Dummy virtual functions carried over from Serial
*/
int OledSerial::peek(void) {
}
/*
Dummy virtual functions carried over from Serial
*/
int OledSerial::read(void) {
}
/*
Dummy virtual functions carried over from Serial
*/
void OledSerial::flush(void) {
}
/*
Erase display and paint it with the color. Used to
*/
void OledSerial::fillScreen(OLEDDISPLAY_COLOR color) {
if (xSemaphoreTake(semaphoreOLEDOperation, pdMS_TO_TICKS(30000)) == pdTRUE) {
display->clear();
display->setColor(color);
display->fillRect(0, 0, OLED_WIDTH, OLED_HEIGHT);
xSemaphoreGive(semaphoreOLEDOperation);
}
}
/*
Write line of text to the display with vertical scrolling of screen
*/
size_t OledSerial::write(const uint8_t* buffer, size_t size) {
if (xPortGetCoreID() == CONFIG_ARDUINO_RUNNING_CORE) {
if (xSemaphoreTake(semaphoreOLEDOperation, pdMS_TO_TICKS(30000)) == pdTRUE) {
nextDisplayPage = uptime() + DISPLAY_PAGE_INTERVAL;
display->normalDisplay();
display->clear();
display->setColor(WHITE);
display->setFont(ArialMT_Plain_10);
while (size) {
display->write((char)*buffer++);
size--;
}
display->drawLogBuffer(0, 0);
display->display();
xSemaphoreGive(semaphoreOLEDOperation);
return size;
}
}
// Default to Serial output if the display is not available
return Serial.write(buffer, size);
}
/*
Display full page message on the display.
- Used to display JSON messages published from each gateway module
*/
boolean OledSerial::displayPage(webUIQueueMessage* message) {
if (xPortGetCoreID() == CONFIG_ARDUINO_RUNNING_CORE) {
if (xSemaphoreTake(semaphoreOLEDOperation, pdMS_TO_TICKS(30000)) == pdTRUE) {
display->normalDisplay();
display->clear();
display->setColor(WHITE);
display->setFont(ArialMT_Plain_10);
display->drawString(0, 0, message->title);
display->drawLine(0, 12, OLED_WIDTH, 12);
display->drawString(0, 13, message->line1);
display->drawString(0, 26, message->line2);
display->drawString(0, 39, message->line3);
display->drawString(0, 52, message->line4);
display->display();
xSemaphoreGive(semaphoreOLEDOperation);
return true;
} else {
return false;
}
} else {
return false;
}
}
/*
Primitives behind OpenMQTTGateway logo
*/
void OledSerial::drawLogo(int xshift, int yshift) {
if (xSemaphoreTake(semaphoreOLEDOperation, pdMS_TO_TICKS(30000)) == pdTRUE) {
display->setColor(WHITE);
// line 1
display->drawLine(15 + xshift, 28 + yshift, 20 + xshift, 31 + yshift);
display->drawLine(15 + xshift, 29 + yshift, 20 + xshift, 32 + yshift);
// line 2
display->drawLine(25 + xshift, 29 + yshift, 22 + xshift, 21 + yshift);
display->drawLine(26 + xshift, 29 + yshift, 23 + xshift, 21 + yshift);
// circle 1
display->fillCircle(25 + xshift, 35 + yshift, 7);
display->setColor(BLACK);
display->fillCircle(25 + xshift, 35 + yshift, 5);
// circle 2
display->setColor(WHITE);
display->fillCircle(23 + xshift, 18 + yshift, 4);
display->setColor(BLACK);
display->fillCircle(23 + xshift, 18 + yshift, 2);
// circle 3
display->setColor(WHITE);
display->fillCircle(11 + xshift, 25 + yshift, 5);
display->setColor(BLACK);
display->fillCircle(11 + xshift, 25 + yshift, 3);
// name
display->setColor(WHITE);
display->drawString(32 + xshift, 32 + yshift, "penMQTTGateway");
display->display();
xSemaphoreGive(semaphoreOLEDOperation);
}
}
String stateSSD1306Display() {
//Publish display state
StaticJsonDocument<JSON_MSG_BUFFER> DISPLAYdataBuffer;
JsonObject DISPLAYdata = DISPLAYdataBuffer.to<JsonObject>();
DISPLAYdata["onstate"] = (bool)displayState;
DISPLAYdata["brightness"] = (int)displayBrightness;
DISPLAYdata["display-flip"] = (bool)displayFlip;
DISPLAYdata["idlelogo"] = (bool)idlelogo;
DISPLAYdata["log-oled"] = (bool)logToOLEDDisplay;
DISPLAYdata["json-oled"] = (bool)jsonDisplay;
DISPLAYdata["origin"] = subjectSSD1306toMQTT;
enqueueJsonObject(DISPLAYdata);
// apply
Oled.display->setBrightness(round(displayBrightness * 2.55));
if (!displayState) {
Oled.display->displayOff();
} else {
Oled.display->displayOn();
}
if (displayFlip) {
Oled.display->flipScreenVertically();
} else {
Oled.display->resetOrientation();
}
String output;
serializeJson(DISPLAYdata, output);
return output;
}
#endif