Compare commits

...

3 Commits

Author SHA1 Message Date
richonguzman 37162b9708 display font size correction 2025-03-03 12:22:22 -03:00
richonguzman 44d9732aa2 TbeamSupreme Added 2025-03-03 12:15:50 -03:00
richonguzman 312bdc9d9f refactor and blacklist2.0 2025-03-03 11:19:25 -03:00
9 changed files with 261 additions and 121 deletions
+2
View File
@@ -57,6 +57,8 @@ jobs:
chip: esp32s3 chip: esp32s3
- name: ttgo_t_deck_GPS - name: ttgo_t_deck_GPS
chip: esp32s3 chip: esp32s3
- name: ttgo_t_beam_s3_SUPREME_v3
chip: esp32s3
- name: ESP32_DIY_LoRa_A7670 - name: ESP32_DIY_LoRa_A7670
chip: esp32 chip: esp32
- name: ESP32_DIY_LoRa_A7670_915 - name: ESP32_DIY_LoRa_A7670_915
+1 -1
View File
@@ -48,7 +48,7 @@ ___________________________________________________________________*/
#include "A7670_utils.h" #include "A7670_utils.h"
#endif #endif
String versionDate = "2025.02.28"; String versionDate = "2025.03.03";
Configuration Config; Configuration Config;
WiFiClient espClient; WiFiClient espClient;
#ifdef HAS_GPS #ifdef HAS_GPS
+28 -30
View File
@@ -173,37 +173,35 @@ namespace APRS_IS_Utils {
void processLoRaPacket(const String& packet) { void processLoRaPacket(const String& packet) {
if (passcodeValid && (espClient.connected() || modemLoggedToAPRSIS)) { if (passcodeValid && (espClient.connected() || modemLoggedToAPRSIS)) {
if (packet != "") { if (packet.indexOf("NOGATE") == -1 && packet.indexOf("RFONLY") == -1) {
if ((packet.substring(0, 3) == "\x3c\xff\x01") && (packet.indexOf("NOGATE") == -1) && (packet.indexOf("RFONLY") == -1)) { int firstColonIndex = packet.indexOf(":");
int firstColonIndex = packet.indexOf(":"); if (firstColonIndex > 5 && firstColonIndex < (packet.length() - 1) && packet[firstColonIndex + 1] != '}' && packet.indexOf("TCPIP") == -1) {
if (firstColonIndex > 5 && firstColonIndex < (packet.length() - 1) && packet[firstColonIndex + 1] != '}' && packet.indexOf("TCPIP") == -1) { const String& Sender = packet.substring(3, packet.indexOf(">"));
const String& Sender = packet.substring(3, packet.indexOf(">")); if (Sender != Config.callsign && Utils::checkValidCallsign(Sender)) {
if (Sender != Config.callsign && Utils::checkValidCallsign(Sender) && !STATION_Utils::checkBlackList(Sender)) { STATION_Utils::updateLastHeard(Sender);
STATION_Utils::updateLastHeard(Sender); Utils::typeOfPacket(packet.substring(3), 0); // LoRa-APRS
Utils::typeOfPacket(packet.substring(3), 0); // LoRa-APRS const String& AddresseeAndMessage = packet.substring(packet.indexOf("::") + 2);
const String& AddresseeAndMessage = packet.substring(packet.indexOf("::") + 2); String Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":"));
String Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":")); Addressee.trim();
Addressee.trim(); bool queryMessage = false;
bool queryMessage = false; if (packet.indexOf("::") > 10 && Addressee == Config.callsign) { // its a message for me!
if (packet.indexOf("::") > 10 && Addressee == Config.callsign) { // its a message for me! queryMessage = processReceivedLoRaMessage(Sender, checkForStartingBytes(AddresseeAndMessage), false);
queryMessage = processReceivedLoRaMessage(Sender, checkForStartingBytes(AddresseeAndMessage), false); }
} if (!queryMessage) {
if (!queryMessage) { const String& aprsPacket = buildPacketToUpload(packet);
const String& aprsPacket = buildPacketToUpload(packet); if (!Config.display.alwaysOn && Config.display.timeout != 0) {
if (!Config.display.alwaysOn && Config.display.timeout != 0) { displayToggle(true);
displayToggle(true);
}
lastScreenOn = millis();
#ifdef HAS_A7670
stationBeacon = true;
A7670_Utils::uploadToAPRSIS(aprsPacket);
stationBeacon = false;
#else
upload(aprsPacket);
#endif
Utils::println("---> Uploaded to APRS-IS");
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
} }
lastScreenOn = millis();
#ifdef HAS_A7670
stationBeacon = true;
A7670_Utils::uploadToAPRSIS(aprsPacket);
stationBeacon = false;
#else
upload(aprsPacket);
#endif
Utils::println("---> Uploaded to APRS-IS");
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
} }
} }
} }
+35 -37
View File
@@ -117,46 +117,44 @@ namespace DIGI_Utils {
} }
void processLoRaPacket(const String& packet) { void processLoRaPacket(const String& packet) {
if (packet != "") { if (packet.indexOf("NOGATE") == -1) {
if ((packet.substring(0, 3) == "\x3c\xff\x01") && (packet.indexOf("NOGATE") == -1)) { bool thirdPartyPacket = false;
bool thirdPartyPacket = false; String temp, Sender;
String temp, Sender; int firstColonIndex = packet.indexOf(":");
int firstColonIndex = packet.indexOf(":"); if (firstColonIndex > 5 && firstColonIndex < (packet.length() - 1) && packet[firstColonIndex + 1] == '}' && packet.indexOf("TCPIP") > 0) { // 3rd Party
if (firstColonIndex > 5 && firstColonIndex < (packet.length() - 1) && packet[firstColonIndex + 1] == '}' && packet.indexOf("TCPIP") > 0) { // 3rd Party thirdPartyPacket = true;
thirdPartyPacket = true; temp = packet.substring(packet.indexOf(":}") + 2);
temp = packet.substring(packet.indexOf(":}") + 2); Sender = temp.substring(0, temp.indexOf(">"));
Sender = temp.substring(0, temp.indexOf(">")); } else {
} else { temp = packet.substring(3);
temp = packet.substring(3); Sender = packet.substring(3, packet.indexOf(">"));
Sender = packet.substring(3, packet.indexOf(">")); }
if (Sender != Config.callsign) { // Avoid listening to own packets
if (!thirdPartyPacket && !Utils::checkValidCallsign(Sender)) {
return;
} }
if (Sender != Config.callsign && !STATION_Utils::checkBlackList(Sender)) { // Avoid listening to own packets if (STATION_Utils::check25SegBuffer(Sender, temp.substring(temp.indexOf(":") + 2)) || Config.lowPowerMode) {
if (!thirdPartyPacket && !Utils::checkValidCallsign(Sender)) { STATION_Utils::updateLastHeard(Sender);
return; Utils::typeOfPacket(temp, 2); // Digi
} bool queryMessage = false;
if (STATION_Utils::check25SegBuffer(Sender, temp.substring(temp.indexOf(":") + 2)) || Config.lowPowerMode) { if (temp.indexOf("::") > 10) { // it's a message
STATION_Utils::updateLastHeard(Sender); String AddresseeAndMessage = temp.substring(temp.indexOf("::") + 2);
Utils::typeOfPacket(temp, 2); // Digi String Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":"));
bool queryMessage = false; Addressee.trim();
if (temp.indexOf("::") > 10) { // it's a message if (Addressee == Config.callsign) { // it's a message for me!
String AddresseeAndMessage = temp.substring(temp.indexOf("::") + 2); queryMessage = APRS_IS_Utils::processReceivedLoRaMessage(Sender, AddresseeAndMessage, thirdPartyPacket);
String Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":"));
Addressee.trim();
if (Addressee == Config.callsign) { // it's a message for me!
queryMessage = APRS_IS_Utils::processReceivedLoRaMessage(Sender, AddresseeAndMessage, thirdPartyPacket);
}
} }
if (!queryMessage) { }
String loraPacket = generateDigipeatedPacket(packet.substring(3), thirdPartyPacket); if (!queryMessage) {
if (loraPacket != "") { String loraPacket = generateDigipeatedPacket(packet.substring(3), thirdPartyPacket);
if (Config.lowPowerMode) { if (loraPacket != "") {
LoRa_Utils::sendNewPacket(loraPacket); if (Config.lowPowerMode) {
} else { LoRa_Utils::sendNewPacket(loraPacket);
STATION_Utils::addToOutputPacketBuffer(loraPacket); } else {
} STATION_Utils::addToOutputPacketBuffer(loraPacket);
displayToggle(true);
lastScreenOn = millis();
} }
displayToggle(true);
lastScreenOn = millis();
} }
} }
} }
+66 -23
View File
@@ -30,11 +30,16 @@
String lastEpaperText; String lastEpaperText;
#else #else
#include <Adafruit_GFX.h> #include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h> #if defined(TTGO_T_Beam_S3_SUPREME_V3)
#ifdef HELTEC_WSL_V3_DISPLAY #include <Adafruit_SH110X.h>
Adafruit_SSD1306 display(128, 64, &Wire1, OLED_RST); Adafruit_SH1106G display(128, 64, &Wire, OLED_RST);
#else #else
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RST); #include <Adafruit_SSD1306.h>
#ifdef HELTEC_WSL_V3_DISPLAY
Adafruit_SSD1306 display(128, 64, &Wire1, OLED_RST);
#else
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RST);
#endif
#endif #endif
#endif #endif
#endif #endif
@@ -77,17 +82,30 @@ void displaySetup() {
digitalWrite(OLED_RST, HIGH); digitalWrite(OLED_RST, HIGH);
#endif #endif
if(display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { #if defined(TTGO_T_Beam_S3_SUPREME_V3)
displayFound = true; if (!display.begin(0x3c, false)) {
if (Config.display.turn180) display.setRotation(2); displayFound = true;
display.clearDisplay(); if (Config.display.turn180) display.setRotation(2);
display.setTextColor(WHITE); display.clearDisplay();
display.setTextSize(1); display.setTextColor(SH110X_WHITE);
display.setCursor(0, 0); display.setTextSize(1);
display.ssd1306_command(SSD1306_SETCONTRAST); display.setCursor(0, 0);
display.ssd1306_command(1); display.setContrast(1);
display.display(); display.display();
} }
#else
if(display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
displayFound = true;
if (Config.display.turn180) display.setRotation(2);
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.ssd1306_command(SSD1306_SETCONTRAST);
display.ssd1306_command(1);
display.display();
}
#endif
#endif #endif
#endif #endif
delay(1000); delay(1000);
@@ -104,7 +122,11 @@ void displayToggle(bool toggle) {
display.printCenter("EPAPER Display Disabled by toggle..."); display.printCenter("EPAPER Display Disabled by toggle...");
display.update(); display.update();
#else #else
if (displayFound) display.ssd1306_command(SSD1306_DISPLAYON); #if defined(TTGO_T_Beam_S3_SUPREME_V3)
if (displayFound) display.oled_command(SH110X_DISPLAYON);
#else
if (displayFound) display.ssd1306_command(SSD1306_DISPLAYON);
#endif
#endif #endif
#endif #endif
} else { } else {
@@ -115,7 +137,12 @@ void displayToggle(bool toggle) {
display.printCenter("Enabled EPAPER Display..."); display.printCenter("Enabled EPAPER Display...");
display.update(); display.update();
#else #else
if (displayFound) display.ssd1306_command(SSD1306_DISPLAYOFF); #if defined(TTGO_T_Beam_S3_SUPREME_V3)
if (displayFound) display.oled_command(SH110X_DISPLAYOFF);
#else
if (displayFound) display.ssd1306_command(SSD1306_DISPLAYOFF);
#endif
#endif #endif
#endif #endif
} }
@@ -161,7 +188,11 @@ void displayShow(const String& header, const String& line1, const String& line2,
#else #else
if (displayFound) { if (displayFound) {
display.clearDisplay(); display.clearDisplay();
display.setTextColor(WHITE); #if defined(TTGO_T_Beam_S3_SUPREME_V3)
display.setTextColor(SH110X_WHITE);
#else
display.setTextColor(WHITE);
#endif
display.setTextSize(1); display.setTextSize(1);
display.setCursor(0, 0); display.setCursor(0, 0);
display.println(header); display.println(header);
@@ -169,8 +200,12 @@ void displayShow(const String& header, const String& line1, const String& line2,
display.setCursor(0, 8 + (8 * i)); display.setCursor(0, 8 + (8 * i));
display.println(*lines[i]); display.println(*lines[i]);
} }
display.ssd1306_command(SSD1306_SETCONTRAST); #if defined(TTGO_T_Beam_S3_SUPREME_V3)
display.ssd1306_command(1); display.setContrast(1);
#else
display.ssd1306_command(SSD1306_SETCONTRAST);
display.ssd1306_command(1);
#endif
display.display(); display.display();
} }
#endif #endif
@@ -219,7 +254,11 @@ void displayShow(const String& header, const String& line1, const String& line2,
#else #else
if (displayFound) { if (displayFound) {
display.clearDisplay(); display.clearDisplay();
display.setTextColor(WHITE); #if defined(TTGO_T_Beam_S3_SUPREME_V3)
display.setTextColor(SH110X_WHITE);
#else
display.setTextColor(WHITE);
#endif
display.setTextSize(2); display.setTextSize(2);
display.setCursor(0, 0); display.setCursor(0, 0);
display.println(header); display.println(header);
@@ -228,8 +267,12 @@ void displayShow(const String& header, const String& line1, const String& line2,
display.setCursor(0, 16 + (8 * i)); display.setCursor(0, 16 + (8 * i));
display.println(*lines[i]); display.println(*lines[i]);
} }
display.ssd1306_command(SSD1306_SETCONTRAST); #if defined(TTGO_T_Beam_S3_SUPREME_V3)
display.ssd1306_command(1); display.setContrast(1);
#else
display.ssd1306_command(SSD1306_SETCONTRAST);
display.ssd1306_command(1);
#endif
display.display(); display.display();
} }
#endif #endif
+25 -18
View File
@@ -2,6 +2,7 @@
#include <WiFi.h> #include <WiFi.h>
#include "configuration.h" #include "configuration.h"
#include "aprs_is_utils.h" #include "aprs_is_utils.h"
#include "station_utils.h"
#include "board_pinout.h" #include "board_pinout.h"
#include "syslog_utils.h" #include "syslog_utils.h"
#include "ntp_utils.h" #include "ntp_utils.h"
@@ -182,27 +183,33 @@ namespace LoRa_Utils {
int state = radio.readData(packet); int state = radio.readData(packet);
if (state == RADIOLIB_ERR_NONE) { if (state == RADIOLIB_ERR_NONE) {
if (packet != "") { if (packet != "") {
rssi = radio.getRSSI();
snr = radio.getSNR();
freqError = radio.getFrequencyError();
Utils::println("<--- LoRa Packet Rx : " + packet.substring(3));
Utils::println("(RSSI:" + String(rssi) + " / SNR:" + String(snr) + " / FreqErr:" + String(freqError) + ")");
if (!Config.lowPowerMode && !Config.digi.ecoMode) { String sender = packet.substring(3, packet.indexOf(">"));
if (receivedPackets.size() >= 10) { if (packet.substring(0,3) == "\x3c\xff\x01" && !STATION_Utils::checkBlackList(sender)){ // avoid processing BlackListed stations
receivedPackets.erase(receivedPackets.begin()); rssi = radio.getRSSI();
snr = radio.getSNR();
freqError = radio.getFrequencyError();
Utils::println("<--- LoRa Packet Rx : " + packet.substring(3));
Utils::println("(RSSI:" + String(rssi) + " / SNR:" + String(snr) + " / FreqErr:" + String(freqError) + ")");
if (!Config.lowPowerMode && !Config.digi.ecoMode) {
if (receivedPackets.size() >= 10) {
receivedPackets.erase(receivedPackets.begin());
}
ReceivedPacket receivedPacket;
receivedPacket.rxTime = NTP_Utils::getFormatedTime();
receivedPacket.packet = packet.substring(3);
receivedPacket.RSSI = rssi;
receivedPacket.SNR = snr;
receivedPackets.push_back(receivedPacket);
} }
ReceivedPacket receivedPacket;
receivedPacket.rxTime = NTP_Utils::getFormatedTime();
receivedPacket.packet = packet.substring(3);
receivedPacket.RSSI = rssi;
receivedPacket.SNR = snr;
receivedPackets.push_back(receivedPacket);
}
if (Config.syslog.active && WiFi.status() == WL_CONNECTED) { if (Config.syslog.active && WiFi.status() == WL_CONNECTED) {
SYSLOG_Utils::log(1, packet, rssi, snr, freqError); // RX SYSLOG_Utils::log(1, packet, rssi, snr, freqError); // RX
} }
} else {
packet = "";
}
lastRxTime = millis(); lastRxTime = millis();
return packet; return packet;
} }
+55 -12
View File
@@ -4,9 +4,17 @@
#include "power_utils.h" #include "power_utils.h"
#if defined(HAS_AXP192) || defined(HAS_AXP2101) #if defined(HAS_AXP192) || defined(HAS_AXP2101)
#define I2C_SDA 21 #ifdef TTGO_T_Beam_S3_SUPREME_V3
#define I2C_SCL 22 #define I2C0_SDA 17
#define IRQ_PIN 35 #define I2C0_SCL 18
#define I2C1_SDA 42
#define I2C1_SCL 41
#define IRQ_PIN 40
#else
#define I2C_SDA 21
#define I2C_SCL 22
#define IRQ_PIN 35
#endif
#endif #endif
#ifdef HAS_AXP192 #ifdef HAS_AXP192
@@ -54,8 +62,13 @@ namespace POWER_Utils {
#endif #endif
#ifdef HAS_AXP2101 #ifdef HAS_AXP2101
#ifdef TTGO_T_Beam_S3_SUPREME_V3
PMU.setALDO4Voltage(3300);
PMU.enableALDO4();
#else
PMU.setALDO3Voltage(3300); PMU.setALDO3Voltage(3300);
PMU.enableALDO3(); PMU.enableALDO3();
#endif
#endif #endif
#ifdef HELTEC_WIRELESS_TRACKER #ifdef HELTEC_WIRELESS_TRACKER
digitalWrite(VEXT_CTRL, HIGH); digitalWrite(VEXT_CTRL, HIGH);
@@ -69,7 +82,11 @@ namespace POWER_Utils {
#endif #endif
#ifdef HAS_AXP2101 #ifdef HAS_AXP2101
PMU.disableALDO3(); #ifdef TTGO_T_Beam_S3_SUPREME_V3
PMU.disableALDO4();
#else
PMU.disableALDO3();
#endif
#endif #endif
#ifdef HELTEC_WIRELESS_TRACKER #ifdef HELTEC_WIRELESS_TRACKER
digitalWrite(VEXT_CTRL, LOW); digitalWrite(VEXT_CTRL, LOW);
@@ -83,8 +100,13 @@ namespace POWER_Utils {
PMU.enableLDO2(); PMU.enableLDO2();
#endif #endif
#ifdef HAS_AXP2101 #ifdef HAS_AXP2101
PMU.setALDO2Voltage(3300); #ifdef TTGO_T_Beam_S3_SUPREME_V3
PMU.enableALDO2(); PMU.setALDO3Voltage(3300);
PMU.enableALDO3();
#else
PMU.setALDO2Voltage(3300);
PMU.enableALDO2();
#endif
#endif #endif
} }
@@ -93,7 +115,11 @@ namespace POWER_Utils {
PMU.disableLDO2(); PMU.disableLDO2();
#endif #endif
#ifdef HAS_AXP2101 #ifdef HAS_AXP2101
PMU.disableALDO2(); #ifdef TTGO_T_Beam_S3_SUPREME_V3
PMU.disableALDO3();
#else
PMU.disableALDO2();
#endif
#endif #endif
} }
@@ -111,20 +137,29 @@ namespace POWER_Utils {
} }
return result; return result;
#elif defined(HAS_AXP2101) #elif defined(HAS_AXP2101)
bool result = PMU.begin(Wire, AXP2101_SLAVE_ADDRESS, I2C_SDA, I2C_SCL); #ifdef TTGO_T_Beam_S3_SUPREME_V3
bool result = PMU.begin(Wire1, AXP2101_SLAVE_ADDRESS, I2C1_SDA, I2C1_SCL);
#else
bool result = PMU.begin(Wire, AXP2101_SLAVE_ADDRESS, I2C_SDA, I2C_SCL);
#endif
if (result) { if (result) {
PMU.disableDC2(); PMU.disableDC2();
PMU.disableDC3(); PMU.disableDC3();
PMU.disableDC4(); PMU.disableDC4();
PMU.disableDC5(); PMU.disableDC5();
PMU.disableALDO1(); #ifndef TTGO_T_Beam_S3_SUPREME_V3
PMU.disableALDO4(); PMU.disableALDO1();
PMU.disableALDO4();
#endif
PMU.disableBLDO1(); PMU.disableBLDO1();
PMU.disableBLDO2(); PMU.disableBLDO2();
PMU.disableDLDO1(); PMU.disableDLDO1();
PMU.disableDLDO2(); PMU.disableDLDO2();
PMU.setDC1Voltage(3300); PMU.setDC1Voltage(3300);
PMU.enableDC1(); PMU.enableDC1();
#ifdef TTGO_T_Beam_S3_SUPREME_V3
PMU.setALDO1Voltage(3300);
#endif
PMU.setButtonBatteryChargeVoltage(3300); PMU.setButtonBatteryChargeVoltage(3300);
PMU.enableButtonBatteryCharge(); PMU.enableButtonBatteryCharge();
PMU.disableIRQ(XPOWERS_AXP2101_ALL_IRQ); PMU.disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
@@ -152,8 +187,16 @@ namespace POWER_Utils {
#endif #endif
#ifdef HAS_AXP2101 #ifdef HAS_AXP2101
Wire.begin(SDA, SCL); bool beginStatus = false;
if (begin(Wire)) { #ifdef TTGO_T_Beam_S3_SUPREME_V3
Wire1.begin(I2C1_SDA, I2C1_SCL);
Wire.begin(I2C0_SDA, I2C0_SCL);
if (begin(Wire1)) beginStatus = true;
#else
Wire.begin(SDA, SCL);
if (begin(Wire)) beginStatus = true;
#endif
if (beginStatus) {
Serial.println("AXP2101 init done!"); Serial.println("AXP2101 init done!");
} else { } else {
Serial.println("AXP2101 init failed!"); Serial.println("AXP2101 init failed!");
@@ -0,0 +1,37 @@
#ifndef BOARD_PINOUT_H_
#define BOARD_PINOUT_H_
// LoRa Radio
#define HAS_SX1262
#define RADIO_SCLK_PIN 12
#define RADIO_MISO_PIN 13
#define RADIO_MOSI_PIN 11
#define RADIO_CS_PIN 10
#define RADIO_DIO0_PIN -1
#define RADIO_RST_PIN 5
#define RADIO_DIO1_PIN 1
#define RADIO_BUSY_PIN 4
// Display
#define HAS_DISPLAY
#undef OLED_SDA
#undef OLED_SCL
#undef OLED_RST
#define OLED_SDA 17
#define OLED_SCL 18
#define OLED_RST 16
#define OLED_DISPLAY_HAS_RST_PIN
// Aditional Config
#define HAS_AXP2101
// GPS
#define HAS_GPS
#define GPS_RX 8
#define GPS_TX 9
#define BOARD_HAS_PSRAM
#endif
@@ -0,0 +1,12 @@
[env:ttgo_t_beam_s3_SUPREME_v3]
board = esp32-s3-devkitc-1
board_build.mcu = esp32s3
build_flags =
${common.build_flags}
${common.usb_flags}
-D TTGO_T_Beam_S3_SUPREME_V3
lib_deps =
${common.lib_deps}
${common.display_libs}
lewisxhe/XPowersLib @ 0.2.4
adafruit/Adafruit SH110X @ 2.1.10