1253 lines
46 KiB
C
1253 lines
46 KiB
C
// Copyright (C) 2024, Mark Qvist
|
|
|
|
// This program 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.
|
|
|
|
// This program 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 <https://www.gnu.org/licenses/>.
|
|
|
|
#include <Adafruit_GFX.h>
|
|
|
|
#if DISPLAY == OLED
|
|
#include <Wire.h>
|
|
#include <Adafruit_SSD1306.h>
|
|
#define DISPLAY_BLACK SSD1306_BLACK
|
|
#define DISPLAY_WHITE SSD1306_WHITE
|
|
|
|
#if BOARD_MODEL == BOARD_TDECK
|
|
#include <Adafruit_ST7789.h>
|
|
#define DISPLAY_BLACK ST77XX_BLACK
|
|
#define DISPLAY_WHITE ST77XX_WHITE
|
|
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
|
#include <Adafruit_SH110X.h>
|
|
#define DISPLAY_BLACK ST77XX_BLACK
|
|
#define DISPLAY_WHITE ST77XX_WHITE
|
|
#else
|
|
#include <Wire.h>
|
|
#include <Adafruit_SSD1306.h>
|
|
#endif
|
|
|
|
#include "Fonts/Org_01.h"
|
|
|
|
#define DISP_W 128
|
|
#define DISP_H 64
|
|
#elif DISPLAY == EINK_BW || DISPLAY == EINK_3C
|
|
void (*display_callback)();
|
|
void display_add_callback(void (*callback)()) {
|
|
display_callback = callback;
|
|
}
|
|
void busyCallback(const void* p) {
|
|
display_callback();
|
|
}
|
|
#define DISPLAY_BLACK GxEPD_BLACK
|
|
#define DISPLAY_WHITE GxEPD_WHITE
|
|
#endif
|
|
#if DISPLAY == EINK_BW
|
|
// use GxEPD2 because adafruit EPD support for partial refresh is bad
|
|
#include <GxEPD2_BW.h>
|
|
#include <SPI.h>
|
|
#elif DISPLAY == EINK_3C
|
|
#include <GxEPD2_3C.h>
|
|
#include <SPI.h>
|
|
#endif
|
|
|
|
#include "Fonts/Org_01.h"
|
|
#if BOARD_MODEL == BOARD_RNODE_NG_20 || BOARD_MODEL == BOARD_LORA32_V2_0
|
|
#if DISPLAY == OLED
|
|
#define DISP_RST -1
|
|
#define DISP_ADDR 0x3C
|
|
#endif
|
|
#elif BOARD_MODEL == BOARD_TBEAM
|
|
#if DISPLAY == OLED
|
|
#define DISP_RST 13
|
|
#define DISP_ADDR 0x3C
|
|
#define DISP_CUSTOM_ADDR true
|
|
#endif
|
|
#elif BOARD_MODEL == BOARD_HELTEC32_V2 || BOARD_MODEL == BOARD_LORA32_V1_0
|
|
#if DISPLAY == OLED
|
|
#define DISP_RST 16
|
|
#define DISP_ADDR 0x3C
|
|
#define SCL_OLED 15
|
|
#define SDA_OLED 4
|
|
#endif
|
|
#elif BOARD_MODEL == BOARD_HELTEC32_V3
|
|
#define DISP_RST 21
|
|
#define DISP_ADDR 0x3C
|
|
#define SCL_OLED 18
|
|
#define SDA_OLED 17
|
|
#elif BOARD_MODEL == BOARD_RNODE_NG_21
|
|
#if DISPLAY == OLED
|
|
#define DISP_RST -1
|
|
#define DISP_ADDR 0x3C
|
|
#endif
|
|
#elif BOARD_MODEL == BOARD_T3S3
|
|
#if DISPLAY == OLED
|
|
#define DISP_RST 21
|
|
#define DISP_ADDR 0x3C
|
|
#define SCL_OLED 17
|
|
#define SDA_OLED 18
|
|
#endif
|
|
#elif BOARD_MODEL == BOARD_OPENCOM_XL
|
|
#if DISPLAY == OLED
|
|
// RAK1921/SSD1306
|
|
#define DISP_RST -1
|
|
#define DISP_ADDR 0x3C
|
|
#define SCL_OLED 14
|
|
#define SDA_OLED 13
|
|
// todo: add support for OLED board
|
|
#elif DISPLAY == EINK_BW
|
|
// todo: change this to be defined in Boards.h in the future
|
|
#define DISP_W 250
|
|
#define DISP_H 122
|
|
#define DISP_ADDR -1
|
|
#define DISPLAY_MODEL GxEPD2_213_BN
|
|
#elif DISPLAY == EINK_3C
|
|
#define DISP_W 250
|
|
#define DISP_H 122
|
|
#define DISP_ADDR -1
|
|
#define DISPLAY_MODEL GxEPD2_213_Z98c
|
|
#endif
|
|
#elif BOARD_MODEL == BOARD_TECHO
|
|
SPIClass displaySPI = SPIClass(NRF_SPIM0, pin_disp_miso, pin_disp_sck, pin_disp_mosi);
|
|
#define DISP_W 128
|
|
#define DISP_H 64
|
|
#define DISP_ADDR -1
|
|
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
|
#define DISP_RST -1
|
|
#define DISP_ADDR 0x3C
|
|
#define SCL_OLED 18
|
|
#define SDA_OLED 17
|
|
#define DISP_CUSTOM_ADDR false
|
|
#else
|
|
#define DISP_RST -1
|
|
#define DISP_ADDR 0x3C
|
|
#define DISP_CUSTOM_ADDR true
|
|
#endif
|
|
|
|
#define SMALL_FONT &Org_01
|
|
|
|
#include "Graphics.h"
|
|
|
|
#if BOARD_MODEL == BOARD_OPENCOM_XL
|
|
#if DISPLAY == EINK_BW
|
|
GxEPD2_BW<DISPLAY_MODEL, DISPLAY_MODEL::HEIGHT> display(DISPLAY_MODEL(pin_disp_cs, pin_disp_dc, pin_disp_reset, pin_disp_busy));
|
|
float disp_target_fps = 0.2;
|
|
uint32_t last_epd_refresh = 0;
|
|
#define REFRESH_PERIOD 300000 // 5 minutes in ms
|
|
#elif DISPLAY == EINK_3C
|
|
GxEPD2_3C<DISPLAY_MODEL, DISPLAY_MODEL::HEIGHT> display(DISPLAY_MODEL(pin_disp_cs, pin_disp_dc, pin_disp_reset, pin_disp_busy));
|
|
float disp_target_fps = 0.05; // refresh usually takes longer on 3C, hence this is 4x the BW refresh period
|
|
uint32_t last_epd_refresh = 0;
|
|
#define REFRESH_PERIOD 600000 // 10 minutes in ms
|
|
#endif
|
|
#elif BOARD_MODEL == BOARD_TECHO
|
|
GxEPD2_BW<DISPLAY_MODEL, DISPLAY_MODEL::HEIGHT> display(DISPLAY_MODEL(pin_disp_cs, pin_disp_dc, pin_disp_reset, pin_disp_busy));
|
|
float disp_target_fps = 0.2;
|
|
uint32_t last_epd_refresh = 0;
|
|
#define REFRESH_PERIOD 300000 // 5 minutes in ms
|
|
#else
|
|
#if DISPLAY == OLED
|
|
#if BOARD_MODEL == BOARD_TDECK
|
|
Adafruit_ST7789 display = Adafruit_ST7789(DISPLAY_CS, DISPLAY_DC, -1);
|
|
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
|
Adafruit_SH1106G display = Adafruit_SH1106G(DISP_W, DISP_H, &Wire, -1);
|
|
#else
|
|
Adafruit_SSD1306 display(DISP_W, DISP_H, &Wire, DISP_RST);
|
|
#endif
|
|
float disp_target_fps = 7;
|
|
#define SCREENSAVER_TIME 500 // ms
|
|
uint32_t last_screensaver = 0;
|
|
#define SCREENSAVER_INTERVAL 600000 // 10 minutes in ms
|
|
bool screensaver_enabled = false;
|
|
#endif
|
|
#endif
|
|
|
|
#define DISP_MODE_UNKNOWN 0x00
|
|
#define DISP_MODE_LANDSCAPE 0x01
|
|
#define DISP_MODE_PORTRAIT 0x02
|
|
#define DISP_PIN_SIZE 6
|
|
#define DISPLAY_BLANKING_TIMEOUT 15*1000
|
|
uint8_t disp_mode = DISP_MODE_UNKNOWN;
|
|
uint8_t disp_ext_fb = false;
|
|
unsigned char fb[512];
|
|
uint32_t last_disp_update = 0;
|
|
bool display_tx = false;
|
|
int disp_update_interval = 1000/disp_target_fps;
|
|
|
|
uint32_t last_page_flip = 0;
|
|
uint32_t last_interface_page_flip = 0;
|
|
int page_interval = 4000;
|
|
bool device_signatures_ok();
|
|
bool device_firmware_ok();
|
|
|
|
bool stat_area_initialised = false;
|
|
bool radio_online = false;
|
|
|
|
#define START_PAGE 0
|
|
const uint8_t pages = 3;
|
|
uint8_t disp_page = START_PAGE;
|
|
uint8_t interface_page = START_PAGE;
|
|
|
|
uint8_t online_interface_list[INTERFACE_COUNT] = {0};
|
|
|
|
uint8_t online_interfaces = 0;
|
|
|
|
#if DISP_H == 64
|
|
#define WATERFALL_SIZE 46
|
|
#elif DISP_H == 122
|
|
#define WATERFALL_SIZE 92
|
|
#else
|
|
#define WATERFALL_SIZE int(DISP_H * 0.75) // default to 75% of the display height
|
|
#endif
|
|
|
|
int waterfall[INTERFACE_COUNT][WATERFALL_SIZE] = {0};
|
|
int waterfall_head[INTERFACE_COUNT] = {0};
|
|
|
|
int p_ad_x = 0;
|
|
int p_ad_y = 0;
|
|
int p_as_x = 0;
|
|
int p_as_y = 0;
|
|
|
|
#if DISP_H == 122
|
|
GFXcanvas1 stat_area(DISP_H, DISP_W/2);
|
|
GFXcanvas1 disp_area(DISP_H, DISP_W/2);
|
|
#else
|
|
GFXcanvas1 stat_area(64, 64);
|
|
GFXcanvas1 disp_area(64, 64);
|
|
#endif
|
|
|
|
void update_area_positions() {
|
|
if (disp_mode == DISP_MODE_PORTRAIT) {
|
|
p_ad_x = 0;
|
|
p_ad_y = 0;
|
|
p_as_x = 0;
|
|
p_as_y = DISP_H;
|
|
} else if (disp_mode == DISP_MODE_LANDSCAPE) {
|
|
p_ad_x = 0;
|
|
p_ad_y = 0;
|
|
p_as_x = DISP_H;
|
|
p_as_y = 0;
|
|
}
|
|
}
|
|
|
|
uint8_t display_contrast = 0x00;
|
|
#if DISPLAY == OLED
|
|
#if BOARD_MODEL == BOARD_TBEAM_S_V1
|
|
void set_contrast(Adafruit_SH1106G *display, uint8_t value) {
|
|
}
|
|
#elif BOARD_MODEL == BOARD_TDECK
|
|
void set_contrast(Adafruit_ST7789 *display, uint8_t value) {
|
|
static uint8_t level = 0;
|
|
static uint8_t steps = 16;
|
|
if (value > 15) value = 15;
|
|
if (value == 0) {
|
|
digitalWrite(DISPLAY_BL_PIN, 0);
|
|
delay(3);
|
|
level = 0;
|
|
return;
|
|
}
|
|
if (level == 0) {
|
|
digitalWrite(DISPLAY_BL_PIN, 1);
|
|
level = steps;
|
|
delayMicroseconds(30);
|
|
}
|
|
int from = steps - level;
|
|
int to = steps - value;
|
|
int num = (steps + to - from) % steps;
|
|
for (int i = 0; i < num; i++) {
|
|
digitalWrite(DISPLAY_BL_PIN, 0);
|
|
digitalWrite(DISPLAY_BL_PIN, 1);
|
|
}
|
|
level = value;
|
|
}
|
|
#else
|
|
void set_contrast(Adafruit_SSD1306 *display, uint8_t contrast) {
|
|
display->ssd1306_command(SSD1306_SETCONTRAST);
|
|
display->ssd1306_command(contrast);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
bool display_init() {
|
|
#if HAS_DISPLAY
|
|
#if BOARD_MODEL == BOARD_RNODE_NG_20 || BOARD_MODEL == BOARD_LORA32_V2_0
|
|
int pin_display_en = 16;
|
|
digitalWrite(pin_display_en, LOW);
|
|
delay(50);
|
|
digitalWrite(pin_display_en, HIGH);
|
|
#elif BOARD_MODEL == BOARD_T3S3
|
|
Wire.begin(SDA_OLED, SCL_OLED);
|
|
#elif BOARD_MODEL == BOARD_HELTEC32_V2
|
|
Wire.begin(SDA_OLED, SCL_OLED);
|
|
#elif BOARD_MODEL == BOARD_HELTEC32_V3
|
|
// enable vext / pin 36
|
|
pinMode(Vext, OUTPUT);
|
|
digitalWrite(Vext, LOW);
|
|
delay(50);
|
|
int pin_display_en = 21;
|
|
pinMode(pin_display_en, OUTPUT);
|
|
digitalWrite(pin_display_en, LOW);
|
|
delay(50);
|
|
digitalWrite(pin_display_en, HIGH);
|
|
delay(50);
|
|
Wire.begin(SDA_OLED, SCL_OLED);
|
|
#elif BOARD_MODEL == BOARD_LORA32_V1_0
|
|
int pin_display_en = 16;
|
|
digitalWrite(pin_display_en, LOW);
|
|
delay(50);
|
|
digitalWrite(pin_display_en, HIGH);
|
|
Wire.begin(SDA_OLED, SCL_OLED);
|
|
#elif BOARD_MODEL == BOARD_TECHO
|
|
pinMode(pin_disp_en, INPUT_PULLUP);
|
|
digitalWrite(pin_disp_en, HIGH);
|
|
|
|
display.init(0, true, 10, false, displaySPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
|
display.setPartialWindow(0, 0, DISP_W, DISP_H);
|
|
|
|
// Because refreshing this display can take some time, sometimes serial
|
|
// commands will be missed. Therefore, during periods where the device is
|
|
// waiting for the display to update, it will poll the serial buffer to
|
|
// check for any commands from the host.
|
|
display.epd2.setBusyCallback(busyCallback);
|
|
#elif BOARD_MODEL == BOARD_OPENCOM_XL
|
|
#if DISPLAY == OLED
|
|
#elif DISPLAY == EINK_BW || DISPLAY == EINK_3C
|
|
pinMode(pin_disp_en, INPUT_PULLUP);
|
|
digitalWrite(pin_disp_en, HIGH);
|
|
display.init(0, true, 10, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
|
display.setPartialWindow(0, 0, DISP_W, DISP_H);
|
|
|
|
// Because refreshing this display can take some time, sometimes serial
|
|
// commands will be missed. Therefore, during periods where the device is
|
|
// waiting for the display to update, it will poll the serial buffer to
|
|
// check for any commands from the host.
|
|
display.epd2.setBusyCallback(busyCallback);
|
|
#endif
|
|
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
|
Wire.begin(SDA_OLED, SCL_OLED);
|
|
#endif
|
|
|
|
#if DISP_CUSTOM_ADDR == true
|
|
#if HAS_EEPROM
|
|
uint8_t display_address = EEPROM.read(eeprom_addr(ADDR_CONF_DADR));
|
|
#elif MCU_VARIANT == MCU_NRF52
|
|
uint8_t display_address = eeprom_read(eeprom_addr(ADDR_CONF_DADR));
|
|
#endif
|
|
if (display_address == 0xFF) display_address = DISP_ADDR;
|
|
#else
|
|
uint8_t display_address = DISP_ADDR;
|
|
#endif
|
|
|
|
#if DISPLAY == EINK_BW || DISPLAY == EINK_3C
|
|
// don't check if display is actually connected
|
|
if(false) {
|
|
#elif BOARD_MODEL == BOARD_TDECK
|
|
display.init(240, 320);
|
|
display.setSPISpeed(80e6);
|
|
if (false) {
|
|
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
|
if (!display.begin(display_address, true)) {
|
|
#else
|
|
if (!display.begin(SSD1306_SWITCHCAPVCC, display_address)) {
|
|
#endif
|
|
return false;
|
|
} else {
|
|
#if DISPLAY == OLED
|
|
set_contrast(&display, display_contrast);
|
|
#endif
|
|
#if BOARD_MODEL == BOARD_RNODE_NG_20
|
|
disp_mode = DISP_MODE_PORTRAIT;
|
|
display.setRotation(3);
|
|
#elif BOARD_MODEL == BOARD_RNODE_NG_21
|
|
disp_mode = DISP_MODE_PORTRAIT;
|
|
display.setRotation(3);
|
|
#elif BOARD_MODEL == BOARD_LORA32_V1_0
|
|
disp_mode = DISP_MODE_PORTRAIT;
|
|
display.setRotation(3);
|
|
#elif BOARD_MODEL == BOARD_LORA32_V2_0
|
|
disp_mode = DISP_MODE_PORTRAIT;
|
|
display.setRotation(3);
|
|
#elif BOARD_MODEL == BOARD_LORA32_V2_1
|
|
disp_mode = DISP_MODE_LANDSCAPE;
|
|
display.setRotation(0);
|
|
#elif BOARD_MODEL == BOARD_TBEAM
|
|
disp_mode = DISP_MODE_LANDSCAPE;
|
|
display.setRotation(0);
|
|
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
|
disp_mode = DISP_MODE_PORTRAIT;
|
|
display.setRotation(1);
|
|
#elif BOARD_MODEL == BOARD_HELTEC32_V2
|
|
disp_mode = DISP_MODE_PORTRAIT;
|
|
display.setRotation(1);
|
|
#elif BOARD_MODEL == BOARD_OPENCOM_XL
|
|
#if DISPLAY == OLED
|
|
#elif DISPLAY == EINK_BW || DISPLAY == EINK_3C
|
|
disp_mode = DISP_MODE_PORTRAIT;
|
|
#endif
|
|
#elif BOARD_MODEL == BOARD_TECHO
|
|
disp_mode = DISP_MODE_LANDSCAPE;
|
|
display.setRotation(3);
|
|
#elif BOARD_MODEL == BOARD_HELTEC32_V3
|
|
disp_mode = DISP_MODE_PORTRAIT;
|
|
display.setRotation(1);
|
|
#elif BOARD_MODEL == BOARD_RAK4631
|
|
disp_mode = DISP_MODE_LANDSCAPE;
|
|
display.setRotation(0);
|
|
#elif BOARD_MODEL == BOARD_TDECK
|
|
disp_mode = DISP_MODE_PORTRAIT;
|
|
display.setRotation(3);
|
|
#else
|
|
disp_mode = DISP_MODE_PORTRAIT;
|
|
display.setRotation(3);
|
|
#endif
|
|
|
|
update_area_positions();
|
|
|
|
last_page_flip = millis();
|
|
|
|
stat_area.cp437(true);
|
|
disp_area.cp437(true);
|
|
display.cp437(true);
|
|
|
|
#if MCU_VARIANT != MCU_NRF52
|
|
display_intensity = EEPROM.read(eeprom_addr(ADDR_CONF_DINT));
|
|
#else
|
|
display_intensity = eeprom_read(eeprom_addr(ADDR_CONF_DINT));
|
|
#endif
|
|
|
|
#if BOARD_MODEL == BOARD_TDECK
|
|
display.fillScreen(DISPLAY_BLACK);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void draw_cable_icon(int px, int py) {
|
|
if (cable_state == CABLE_STATE_DISCONNECTED) {
|
|
#if DISP_H == 122
|
|
stat_area.drawBitmap(px, py, bm_cable+0*128, 30, 32, DISPLAY_WHITE, DISPLAY_BLACK);
|
|
#else
|
|
stat_area.drawBitmap(px, py, bm_cable+0*32, 16, 16, DISPLAY_WHITE, DISPLAY_BLACK);
|
|
#endif
|
|
} else if (cable_state == CABLE_STATE_CONNECTED) {
|
|
#if DISP_H == 122
|
|
stat_area.drawBitmap(px, py, bm_cable+1*128, 30, 32, DISPLAY_WHITE, DISPLAY_BLACK);
|
|
#else
|
|
stat_area.drawBitmap(px, py, bm_cable+1*32, 16, 16, DISPLAY_WHITE, DISPLAY_BLACK);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void draw_bt_icon(int px, int py) {
|
|
if (bt_state == BT_STATE_OFF) {
|
|
#if DISP_H == 122
|
|
stat_area.drawBitmap(px, py, bm_bt+0*128, 30, 32, DISPLAY_WHITE, DISPLAY_BLACK);
|
|
#else
|
|
stat_area.drawBitmap(px, py, bm_bt+0*32, 16, 16, DISPLAY_WHITE, DISPLAY_BLACK);
|
|
#endif
|
|
} else if (bt_state == BT_STATE_ON) {
|
|
#if DISP_H == 122
|
|
stat_area.drawBitmap(px, py, bm_bt+1*128, 30, 32, DISPLAY_WHITE, DISPLAY_BLACK);
|
|
#else
|
|
stat_area.drawBitmap(px, py, bm_bt+1*32, 16, 16, DISPLAY_WHITE, DISPLAY_BLACK);
|
|
#endif
|
|
} else if (bt_state == BT_STATE_PAIRING) {
|
|
#if DISP_H == 122
|
|
stat_area.drawBitmap(px, py, bm_bt+2*128, 30, 32, DISPLAY_WHITE, DISPLAY_BLACK);
|
|
#else
|
|
stat_area.drawBitmap(px, py, bm_bt+2*32, 16, 16, DISPLAY_WHITE, DISPLAY_BLACK);
|
|
#endif
|
|
} else if (bt_state == BT_STATE_CONNECTED) {
|
|
#if DISP_H == 122
|
|
stat_area.drawBitmap(px, py, bm_bt+3*128, 30, 32, DISPLAY_WHITE, DISPLAY_BLACK);
|
|
#else
|
|
stat_area.drawBitmap(px, py, bm_bt+3*32, 16, 16, DISPLAY_WHITE, DISPLAY_BLACK);
|
|
#endif
|
|
} else {
|
|
#if DISP_H == 122
|
|
stat_area.drawBitmap(px, py, bm_bt+0*128, 30, 32, DISPLAY_WHITE, DISPLAY_BLACK);
|
|
#else
|
|
stat_area.drawBitmap(px, py, bm_bt+0*32, 16, 16, DISPLAY_WHITE, DISPLAY_BLACK);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void draw_lora_icon(RadioInterface* radio, int px |