Browse Source

* new ui/DisplayDriver classes (just SSD1306Display impl for now)
* companion radio: now with optional UITask (enabled by DISPLAY_CLASS config in target/env)

Scott Powell 1 year ago
parent
commit
372c228210

+ 110 - 0
examples/companion_radio/UITask.cpp

@@ -0,0 +1,110 @@
+#include "UITask.h"
+#include <Arduino.h>
+#include <helpers/TxtDataHelpers.h>
+
+#define AUTO_OFF_MILLIS   15000   // 15 seconds
+
+// 'meshcore', 128x13px
+static const uint8_t meshcore_logo [] PROGMEM = {
+    0x3c, 0x01, 0xe3, 0xff, 0xc7, 0xff, 0x8f, 0x03, 0x87, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 
+    0x3c, 0x03, 0xe3, 0xff, 0xc7, 0xff, 0x8e, 0x03, 0x8f, 0xfe, 0x3f, 0xfe, 0x1f, 0xff, 0x1f, 0xfe, 
+    0x3e, 0x03, 0xc3, 0xff, 0x8f, 0xff, 0x0e, 0x07, 0x8f, 0xfe, 0x7f, 0xfe, 0x1f, 0xff, 0x1f, 0xfc, 
+    0x3e, 0x07, 0xc7, 0x80, 0x0e, 0x00, 0x0e, 0x07, 0x9e, 0x00, 0x78, 0x0e, 0x3c, 0x0f, 0x1c, 0x00, 
+    0x3e, 0x0f, 0xc7, 0x80, 0x1e, 0x00, 0x0e, 0x07, 0x1e, 0x00, 0x70, 0x0e, 0x38, 0x0f, 0x3c, 0x00, 
+    0x7f, 0x0f, 0xc7, 0xfe, 0x1f, 0xfc, 0x1f, 0xff, 0x1c, 0x00, 0x70, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, 
+    0x7f, 0x1f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, 
+    0x7f, 0x3f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x1e, 0x3f, 0xfe, 0x3f, 0xf0, 
+    0x77, 0x3b, 0x87, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xfc, 0x38, 0x00, 
+    0x77, 0xfb, 0x8f, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xf8, 0x38, 0x00, 
+    0x73, 0xf3, 0x8f, 0xff, 0x0f, 0xff, 0x1c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x78, 0x7f, 0xf8, 
+    0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfe, 0x3c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x3c, 0x7f, 0xf8, 
+    0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8, 
+};
+
+void UITask::begin(const char* node_name, const char* build_date) {
+  _prevBtnState = HIGH;
+  _auto_off = millis() + AUTO_OFF_MILLIS;
+  clearMsgPreview();
+  _node_name = node_name;
+  _build_date = build_date;
+  _display->turnOn();
+}
+
+void UITask::clearMsgPreview() {
+  _origin[0] = 0;
+  _msg[0] = 0;
+}
+
+void UITask::showMsgPreview(uint8_t path_len, const char* from_name, const char* text) {
+  if (path_len == 0xFF) {
+    sprintf(_origin, "(F) %s", from_name);
+  } else {
+    sprintf(_origin, "(%d) %s", (uint32_t) path_len, from_name);
+  }
+  StrHelper::strncpy(_msg, text, sizeof(_msg));
+
+  if (!_display->isOn()) _display->turnOn();
+  _auto_off = millis() + AUTO_OFF_MILLIS;  // extend the auto-off timer
+}
+
+void UITask::renderCurrScreen() {
+  char tmp[80];
+  if (_origin[0] && _msg[0]) {
+    // render message preview
+    _display->setCursor(0, 0);
+    _display->setTextSize(1);
+    _display->print(_node_name);
+
+    _display->setCursor(0, 12);
+    _display->print(_origin);
+    _display->setCursor(0, 24);
+    _display->print(_msg);
+
+    //_display->setCursor(100, 9);   TODO
+    //_display->setTextSize(2);
+    //_display->printf("%d", msgs);
+  } else {
+    // render 'home' screen
+    _display->drawXbm(0, 0, meshcore_logo, 128, 13);
+    _display->setCursor(0, 20);
+    _display->setTextSize(1);
+    _display->print(_node_name);
+
+    sprintf(tmp, "Build: %s", _build_date);
+    _display->setCursor(0, 32);
+    _display->print(tmp);
+    //_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf);
+    //_display->printf("bw   : %03.2f cr %d\n", _prefs.bw, _prefs.cr);
+  }
+}
+
+void UITask::loop() {
+  if (millis() >= _next_read) {
+    int btnState = digitalRead(PIN_USER_BTN);
+    if (btnState != _prevBtnState) {
+      if (btnState == LOW) {  // pressed?
+        if (_display->isOn()) {
+          clearMsgPreview();
+        } else {
+          _display->turnOn();
+        }
+        _auto_off = millis() + AUTO_OFF_MILLIS;   // extend auto-off timer
+      }
+      _prevBtnState = btnState;
+    }
+    _next_read = millis() + 100;  // 10 reads per second
+  }
+
+  if (_display->isOn()) {
+    if (millis() >= _next_refresh) {
+      _display->startFrame();
+      renderCurrScreen();
+      _display->endFrame();
+
+      _next_refresh = millis() + 1000;   // refresh every second
+    }
+    if (millis() > _auto_off) {
+      _display->turnOff();
+    }
+  }
+}

+ 22 - 0
examples/companion_radio/UITask.h

@@ -0,0 +1,22 @@
+#pragma once
+
+#include <helpers/ui/DisplayDriver.h>
+
+class UITask {
+  DisplayDriver* _display;
+  unsigned long _next_read, _next_refresh, _auto_off;
+  int _prevBtnState;
+  const char* _node_name;
+  const char* _build_date;
+  char _origin[62];
+  char _msg[80];
+
+  void renderCurrScreen();
+public:
+  UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; }
+  void begin(const char* node_name, const char* build_date);
+
+  void clearMsgPreview();
+  void showMsgPreview(uint8_t path_len, const char* from_name, const char* text);
+  void loop();
+};

+ 27 - 0
examples/companion_radio/main.cpp

@@ -84,6 +84,15 @@
   #error "need to provide a 'board' object"
 #endif
 
+#ifdef DISPLAY_CLASS
+  #include <helpers/ui/SSD1306Display.h>
+
+  static DISPLAY_CLASS  display;
+
+  #include "UITask.h"
+  static UITask ui_task(display);
+#endif
+
 // Believe it or not, this std C function is busted on some platforms!
 static uint32_t _atoi(const char* sp) {
   uint32_t n = 0;
@@ -471,6 +480,9 @@ protected:
     } else {
       soundBuzzer();
     }
+  #ifdef DISPLAY_CLASS
+    ui_task.showMsgPreview(path_len, from.name, text);
+  #endif
   }
 
   void onMessageRecv(const ContactInfo& from, uint8_t path_len, uint32_t sender_timestamp, const char *text) override {
@@ -510,6 +522,9 @@ protected:
     } else {
       soundBuzzer();
     }
+  #ifdef DISPLAY_CLASS
+    ui_task.showMsgPreview(in_path_len < 0 ? 0xFF : in_path_len, "Public", text);
+  #endif
   }
 
   void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) override {
@@ -1077,6 +1092,10 @@ public:
     } else if (!_serial->isWriteBusy()) {
       checkConnections();
     }
+
+  #ifdef DISPLAY_CLASS
+    ui_task.loop();
+  #endif
   }
 };
 
@@ -1132,6 +1151,10 @@ void setup() {
   float tcxo = 1.6f;
 #endif
 
+#ifdef DISPLAY_CLASS
+  display.begin();
+#endif
+
 #if defined(NRF52_PLATFORM)
   SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI);
   SPI.begin();
@@ -1190,6 +1213,10 @@ void setup() {
 #else
   #error "need to define filesystem"
 #endif
+
+#ifdef DISPLAY_CLASS
+  ui_task.begin(the_mesh.getNodeName(), FIRMWARE_BUILD_DATE);
+#endif
 }
 
 void loop() {

+ 10 - 3
platformio.ini

@@ -113,10 +113,14 @@ build_flags =
   -D P_LORA_TX_LED=35
   -D PIN_BOARD_SDA=17
   -D PIN_BOARD_SCL=18
+  -D PIN_USER_BTN=0
   -D SX126X_DIO2_AS_RF_SWITCH=true
   -D SX126X_DIO3_TCXO_VOLTAGE=1.8
   -D SX126X_CURRENT_LIMIT=130.0f  ; for best TX power!
 build_src_filter = ${esp32_base.build_src_filter}
+lib_deps =
+  ${esp32_base.lib_deps}
+	adafruit/Adafruit SSD1306 @ ^2.5.13
 
 [env:Heltec_v3_repeater]
 extends = Heltec_lora32_v3
@@ -162,11 +166,12 @@ build_flags =
   ${Heltec_lora32_v3.build_flags} 
   -D MAX_CONTACTS=100
   -D MAX_GROUP_CHANNELS=1
+  -D DISPLAY_CLASS=SSD1306Display
 ;  -D ENABLE_PRIVATE_KEY_IMPORT=1
 ;  -D ENABLE_PRIVATE_KEY_EXPORT=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_DEBUG=1
-build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/companion_radio/main.cpp>
+build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<helpers/ui/*.cpp> +<../examples/companion_radio>
 lib_deps =
   ${Heltec_lora32_v3.lib_deps}
   densaugeo/base64 @ ~1.4.0
@@ -177,13 +182,14 @@ build_flags =
   ${Heltec_lora32_v3.build_flags} 
   -D MAX_CONTACTS=100
   -D MAX_GROUP_CHANNELS=1
+  -D DISPLAY_CLASS=SSD1306Display
   -D BLE_PIN_CODE=123456
   -D BLE_DEBUG_LOGGING=1
 ;  -D ENABLE_PRIVATE_KEY_IMPORT=1
 ;  -D ENABLE_PRIVATE_KEY_EXPORT=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_DEBUG=1
-build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<helpers/esp32/*.cpp> +<../examples/companion_radio/main.cpp>
+build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<helpers/ui/*.cpp> +<helpers/esp32/*.cpp> +<../examples/companion_radio>
 lib_deps =
   ${Heltec_lora32_v3.lib_deps}
   densaugeo/base64 @ ~1.4.0
@@ -194,6 +200,7 @@ build_flags =
   ${Heltec_lora32_v3.build_flags} 
   -D MAX_CONTACTS=100
   -D MAX_GROUP_CHANNELS=1
+  -D DISPLAY_CLASS=SSD1306Display
   -D WIFI_DEBUG_LOGGING=1
   -D WIFI_SSID="\"myssid\""
   -D WIFI_PWD="\"mypwd\""
@@ -201,7 +208,7 @@ build_flags =
 ;  -D ENABLE_PRIVATE_KEY_EXPORT=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_DEBUG=1
-build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<helpers/esp32/*.cpp> +<../examples/companion_radio/main.cpp>
+build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<helpers/ui/*.cpp> +<helpers/esp32/*.cpp> +<../examples/companion_radio>
 lib_deps =
   ${Heltec_lora32_v3.lib_deps}
   densaugeo/base64 @ ~1.4.0

+ 2 - 0
src/helpers/HeltecV3Board.h

@@ -17,6 +17,7 @@
 #define  PIN_ADC_CTRL_ACTIVE    LOW
 #define  PIN_ADC_CTRL_INACTIVE  HIGH
 #define  PIN_LED_BUILTIN 35
+#define  PIN_VEXT_EN     36
 
 #include "ESP32Board.h"
 
@@ -28,6 +29,7 @@ public:
     ESP32Board::begin();
 
     pinMode(PIN_ADC_CTRL, OUTPUT);
+    //pinMode(PIN_VEXT_EN, OUTPUT);
 
     esp_reset_reason_t reason = esp_reset_reason();
     if (reason == ESP_RST_DEEPSLEEP) {

+ 27 - 0
src/helpers/ui/DisplayDriver.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#include <stdint.h>
+
+class DisplayDriver {
+  int _w, _h;
+protected:
+  DisplayDriver(int w, int h) { _w = w; _h = h; }
+public:
+  enum Color { DARK, LIGHT };
+
+  int width() const { return _w; }
+  int height() const { return _h; }
+
+  virtual bool isOn() = 0;
+  virtual void turnOn() = 0;
+  virtual void turnOff() = 0;
+  virtual void startFrame(Color bkg = DARK) = 0;
+  virtual void setTextSize(int sz) = 0;
+  virtual void setColor(Color c) = 0;
+  virtual void setCursor(int x, int y) = 0;
+  virtual void print(const char* str) = 0;
+  virtual void fillRect(int x, int y, int w, int h) = 0;
+  virtual void drawRect(int x, int y, int w, int h) = 0;
+  virtual void drawXbm(int x, int y, const uint8_t* bits, int w, int h) = 0;
+  virtual void endFrame() = 0;
+};

+ 56 - 0
src/helpers/ui/SSD1306Display.cpp

@@ -0,0 +1,56 @@
+#include "SSD1306Display.h"
+
+bool SSD1306Display::begin() {
+  return display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_ADDRESS);
+}
+
+void SSD1306Display::turnOn() {
+  display.ssd1306_command(SSD1306_DISPLAYON);
+  _isOn = true;
+}
+
+void SSD1306Display::turnOff() {
+  display.ssd1306_command(SSD1306_DISPLAYOFF);
+  _isOn = false;
+}
+
+void SSD1306Display::startFrame(Color bkg) {
+  display.clearDisplay();  // TODO: apply 'bkg'
+  _color = SSD1306_WHITE;
+  display.setTextColor(_color);
+  display.setTextSize(1);
+  display.cp437(true);         // Use full 256 char 'Code Page 437' font
+}
+
+void SSD1306Display::setTextSize(int sz) {
+  display.setTextSize(sz);
+}
+
+void SSD1306Display::setColor(Color c) {
+  _color = (c == LIGHT) ? SSD1306_WHITE : SSD1306_BLACK;
+  display.setTextColor(_color);
+}
+
+void SSD1306Display::setCursor(int x, int y) {
+  display.setCursor(x, y);
+}
+
+void SSD1306Display::print(const char* str) {
+  display.print(str);
+}
+
+void SSD1306Display::fillRect(int x, int y, int w, int h) {
+  display.fillRect(x, y, w, h, _color);
+}
+
+void SSD1306Display::drawRect(int x, int y, int w, int h) {
+  display.drawRect(x, y, w, h, _color);
+}
+
+void SSD1306Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
+  display.drawBitmap(x, y, bits, w, h, SSD1306_WHITE);
+}
+
+void SSD1306Display::endFrame() {
+  display.display();
+}

+ 37 - 0
src/helpers/ui/SSD1306Display.h

@@ -0,0 +1,37 @@
+#pragma once
+
+#include "DisplayDriver.h"
+#include <Wire.h>
+#include <Adafruit_GFX.h>
+#include <Adafruit_SSD1306.h>
+
+#ifndef PIN_OLED_RESET
+  #define PIN_OLED_RESET        21 // Reset pin # (or -1 if sharing Arduino reset pin)
+#endif
+
+#ifndef DISPLAY_ADDRESS
+  #define DISPLAY_ADDRESS   0x3C
+#endif
+
+class SSD1306Display : public DisplayDriver {
+  Adafruit_SSD1306 display;
+  bool _isOn;
+  uint8_t _color;
+
+public:
+  SSD1306Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; }
+  bool begin();
+
+  bool isOn() override { return _isOn; }
+  void turnOn() override;
+  void turnOff() override;
+  void startFrame(Color bkg = DARK) override;
+  void setTextSize(int sz) override;
+  void setColor(Color c) override;
+  void setCursor(int x, int y) override;
+  void print(const char* str) override;
+  void fillRect(int x, int y, int w, int h) override;
+  void drawRect(int x, int y, int w, int h) override;
+  void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
+  void endFrame() override;
+};