ESP32-PaxCounter/src/display.cpp

735 lines
19 KiB
C++
Raw Normal View History

2019-03-13 22:08:05 +01:00
#ifdef HAS_DISPLAY
2018-07-15 14:28:05 +02:00
/*
Display-Mask (128 x 64 pixel):
2019-09-27 17:37:16 +02:00
| | |
| 11111111112
|012345678901234567890 Font
----------------------- ---------
0|PAX:aabbccdd STRETCHED
1|PAX:aabbccdd STRETCHED
2|
2020-04-05 23:01:39 +02:00
3|WIFI:abcde BLTH:abcde SMALL
4|B:a.bcV Sats:ab ch:ab SMALL
2019-09-27 17:37:16 +02:00
5|RLIM:abcd Mem:abcdKB SMALL
6|27.Feb 2019 20:27:00* SMALL
2019-10-12 14:07:55 +02:00
7|yyyyyyyyyyyyy xx SFab SMALL
2019-09-27 17:37:16 +02:00
* = char {L|G|R|?} indicates time source,
inverse = clock controller is active,
pulsed = pps input signal is active
2019-10-12 14:07:55 +02:00
y = LMIC event message
xx = payload sendqueue length
ab = LMIC spread factor
2019-09-27 17:37:16 +02:00
MY_FONT_SMALL: 6x8px = 21 chars / line
MY_FONT_NORMAL: 8x8px = 16 chars / line
MY_FONT_STRETCHED: 16x32px = 8 chars / line
*/
2018-07-15 14:28:05 +02:00
// Basic Config
#include <esp_spi_flash.h> // needed for reading ESP32 chip attributes
#include "globals.h"
#include "display.h"
2018-07-15 14:28:05 +02:00
2019-09-29 16:46:48 +02:00
// local Tag for logging
static const char TAG[] = __FILE__;
2019-10-01 13:02:30 +02:00
// helper array for converting month values to text
const char *printmonth[] = {"xxx", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
uint8_t DisplayIsOn = 0;
uint8_t displaybuf[MY_DISPLAY_WIDTH * MY_DISPLAY_HEIGHT / 8] = {0};
static uint8_t plotbuf[MY_DISPLAY_WIDTH * MY_DISPLAY_HEIGHT / 8] = {0};
2020-04-06 17:40:45 +02:00
static int dp_row = 0, dp_col = 0, dp_font = 0;
2018-07-19 21:53:56 +02:00
2019-09-29 16:46:48 +02:00
QRCode qrcode;
#if (HAS_DISPLAY) == 1
OBDISP ssoled;
#elif (HAS_DISPLAY) == 2
2020-03-29 23:05:42 +02:00
TFT_eSPI tft = TFT_eSPI();
#endif
2020-03-11 23:47:16 +01:00
void dp_setup(int contrast) {
2020-03-11 23:47:16 +01:00
#if (HAS_DISPLAY) == 1 // I2C OLED
int rc = obdI2CInit(&ssoled, OLED_TYPE, OLED_ADDR, MY_DISPLAY_FLIP,
MY_DISPLAY_INVERT, USE_HW_I2C, MY_DISPLAY_SDA,
MY_DISPLAY_SCL, MY_DISPLAY_RST,
OLED_FREQUENCY); // use standard I2C bus at 400Khz
2020-03-11 23:47:16 +01:00
assert(rc != OLED_NOT_FOUND);
// set display buffer
obdSetBackBuffer(&ssoled, displaybuf);
obdSetTextWrap(&ssoled, true);
2020-04-06 17:40:45 +02:00
dp_font = MY_FONT_NORMAL;
#elif (HAS_DISPLAY) == 2 // SPI TFT
tft.init();
2020-04-05 18:19:39 +02:00
tft.setRotation(MY_DISPLAY_FLIP ? 3 : 1);
2020-03-29 23:05:42 +02:00
tft.invertDisplay(MY_DISPLAY_INVERT ? true : false);
tft.setTextColor(MY_DISPLAY_FGCOLOR, MY_DISPLAY_BGCOLOR);
#endif
2020-03-11 23:47:16 +01:00
// clear display
dp_clear();
if (contrast)
dp_contrast(contrast);
2020-03-11 23:47:16 +01:00
}
2019-09-29 16:46:48 +02:00
void dp_init(bool verbose) {
#if (HAS_DISPLAY) == 1 // i2c
2019-02-02 10:35:20 +01:00
// block i2c bus access
2019-07-23 17:53:20 +02:00
if (!I2C_MUTEX_LOCK())
2019-07-27 11:59:24 +02:00
ESP_LOGV(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0);
2019-07-23 17:53:20 +02:00
else {
#endif
2019-09-27 12:47:00 +02:00
dp_setup(DISPLAYCONTRAST);
2019-09-27 12:47:00 +02:00
2019-10-01 13:02:30 +02:00
if (verbose) {
// show startup screen
// to come -> display .bmp file with logo
2019-09-27 12:47:00 +02:00
// show chip information
2019-03-24 01:05:13 +01:00
#if (VERBOSE)
2019-10-01 13:02:30 +02:00
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
2020-04-06 17:40:45 +02:00
dp_setFont(MY_FONT_NORMAL);
dp_printf("** PAXCOUNTER **");
dp_println();
dp_printf("Software v%s", PROGVERSION);
dp_println();
dp_printf("ESP32 %d cores", chip_info.cores);
dp_println();
dp_printf("Chip Rev.%d", chip_info.revision);
dp_println();
dp_printf("WiFi%s%s", (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
2019-10-01 13:02:30 +02:00
(chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
2020-04-06 17:40:45 +02:00
dp_println();
dp_printf("%dMB %s Flash", spi_flash_get_chip_size() / (1024 * 1024),
2019-10-01 13:02:30 +02:00
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "int."
: "ext.");
// give user some time to read or take picture
2020-03-11 23:47:16 +01:00
dp_dump(displaybuf);
2019-10-01 13:02:30 +02:00
delay(2000);
dp_clear();
2018-07-15 14:28:05 +02:00
#endif // VERBOSE
2019-03-24 01:05:13 +01:00
#if (HAS_LORA)
2019-10-01 13:02:30 +02:00
// generate DEVEUI as QR code and text
uint8_t buf[8], *p = buf;
2019-10-01 13:02:30 +02:00
char deveui[17];
os_getDevEui((u1_t *)buf);
snprintf(deveui, 17, "%016llX", (*(uint64_t *)(p)));
2019-10-01 13:02:30 +02:00
// display DEVEUI as QR code on the left
dp_contrast(30);
2019-10-01 13:02:30 +02:00
dp_printqr(3, 3, deveui);
// display DEVEUI as plain text on the right
2020-04-10 18:40:40 +02:00
const int x_offset = QR_SCALEFACTOR * 29 + 14;
2020-04-06 17:40:45 +02:00
dp_setTextCursor(x_offset, 0);
dp_setFont(MY_FONT_NORMAL);
dp_printf("DEVEUI");
dp_println();
2020-04-05 23:01:39 +02:00
for (uint8_t i = 0; i <= 3; i++) {
2020-04-06 17:40:45 +02:00
dp_setTextCursor(x_offset, i + 3);
dp_printf("%4.4s", deveui + i * 4);
2020-04-05 23:01:39 +02:00
}
2019-10-01 13:02:30 +02:00
// give user some time to read or take picture
2020-03-11 23:47:16 +01:00
dp_dump(displaybuf);
2019-10-01 13:02:30 +02:00
delay(8000);
dp_contrast(DISPLAYCONTRAST);
dp_clear();
2019-09-29 17:38:16 +02:00
#endif // HAS_LORA
2019-10-01 13:02:30 +02:00
} // verbose
dp_power(cfg.screenon); // set display off if disabled
2019-02-02 10:35:20 +01:00
#if (HAS_DISPLAY) == 1 // i2c
2019-02-02 10:35:20 +01:00
I2C_MUTEX_UNLOCK(); // release i2c bus access
2019-02-22 22:28:35 +01:00
} // mutex
#endif
} // dp_init
2018-07-15 14:28:05 +02:00
void dp_refresh(bool nextPage) {
2018-07-15 14:28:05 +02:00
2020-01-20 11:45:11 +01:00
#ifndef HAS_BUTTON
2020-01-20 11:41:31 +01:00
static uint32_t framecounter = 0;
2020-01-20 11:45:11 +01:00
#endif
2019-02-18 21:51:01 +01:00
// update histogram
dp_plotCurve(macs.size(), false);
// if display is switched off we don't refresh it to relax cpu
if (!DisplayIsOn && (DisplayIsOn == cfg.screenon))
return;
2019-05-31 13:20:11 +02:00
const time_t t =
myTZ.toLocal(now()); // note: call now() here *before* locking mutex!
// block i2c bus access
2019-07-23 17:53:20 +02:00
if (!I2C_MUTEX_LOCK())
2019-07-27 11:59:24 +02:00
ESP_LOGV(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0);
2019-07-23 17:53:20 +02:00
else {
// set display on/off according to current device configuration
if (DisplayIsOn != cfg.screenon) {
DisplayIsOn = cfg.screenon;
dp_power(cfg.screenon);
}
2020-01-20 11:41:31 +01:00
#ifndef HAS_BUTTON
// auto flip page if we are in unattended mode
if ((++framecounter) > (DISPLAYCYCLE * 1000 / DISPLAYREFRESH_MS)) {
framecounter = 0;
nextPage = true;
}
#endif
dp_drawPage(t, nextPage);
2020-03-11 23:47:16 +01:00
dp_dump(displaybuf);
I2C_MUTEX_UNLOCK(); // release i2c bus access
} // mutex
} // refreshDisplay()
void dp_drawPage(time_t t, bool nextpage) {
2020-01-20 11:12:00 +01:00
// write display content to display buffer
// nextpage = true -> flip 1 page
static uint8_t DisplayPage = 0;
char timeState;
2019-07-22 22:00:39 +02:00
#if (HAS_GPS)
2019-04-01 18:34:57 +02:00
static bool wasnofix = true;
2019-07-22 22:00:39 +02:00
#endif
if (nextpage) {
DisplayPage = (DisplayPage >= DISPLAY_PAGES - 1) ? 0 : (DisplayPage + 1);
dp_clear();
}
2020-04-05 23:01:39 +02:00
// cursor home
2020-04-06 17:40:45 +02:00
dp_setTextCursor(0, 0);
2020-04-05 23:01:39 +02:00
// line 1/2: pax counter
2020-04-06 17:40:45 +02:00
// display number of unique macs total Wifi + BLE
2020-04-10 22:27:37 +02:00
if (DisplayPage < 5) {
2020-04-06 17:40:45 +02:00
dp_setFont(MY_FONT_STRETCHED);
dp_printf("PAX:%-4d", macs.size());
2020-04-05 23:01:39 +02:00
}
switch (DisplayPage) {
// page 0: parameters overview
2020-04-10 22:27:37 +02:00
// page 1: lorawan parameters
// page 2: GPS
// page 3: BME280/680
2019-10-01 13:02:30 +02:00
// page 4: time
2020-04-10 22:27:37 +02:00
// page 5: pax graph
// page 6: blank screen
2020-04-10 22:27:37 +02:00
// ---------- page 0: parameters overview ----------
case 0:
2020-04-10 22:27:37 +02:00
dp_setTextCursor(0, 3);
2020-04-06 17:40:45 +02:00
dp_setFont(MY_FONT_SMALL);
2018-07-15 14:28:05 +02:00
2019-09-27 17:37:16 +02:00
// line 3: wifi + bluetooth counters
2020-04-05 23:01:39 +02:00
// WIFI:abcde BLTH:abcde
#if ((WIFICOUNTER) && (BLECOUNTER))
if (cfg.wifiscan)
2020-04-06 17:40:45 +02:00
dp_printf("WIFI:%-5d", macs_wifi);
else
2020-04-06 17:40:45 +02:00
dp_printf("WIFI:off");
2019-09-27 17:37:16 +02:00
if (cfg.blescan)
2020-04-06 17:40:45 +02:00
dp_printf(" BLTH:%-5d", macs_ble);
2019-09-27 17:37:16 +02:00
else
2020-04-06 17:40:45 +02:00
dp_printf(" BLTH:off");
#elif ((WIFICOUNTER) && (!BLECOUNTER))
if (cfg.wifiscan)
2020-04-06 17:40:45 +02:00
dp_printf("WIFI:%-5d", macs_wifi);
else
2020-04-06 17:40:45 +02:00
dp_printf("WIFI:off");
#elif ((!WIFICOUNTER) && (BLECOUNTER))
if (cfg.blescan)
2020-04-06 17:40:45 +02:00
dp_printf("BLTH:%-5d", macs_ble);
else
2020-04-06 17:40:45 +02:00
dp_printf("BLTH:off");
#else
2020-04-06 17:40:45 +02:00
dp_printf("Sniffer disabled");
2019-09-27 17:37:16 +02:00
#endif
2020-04-05 23:01:39 +02:00
dp_println();
2019-09-27 17:37:16 +02:00
2020-04-06 17:40:45 +02:00
// line 4: Battery + GPS status + Wifi channel
// B:a.bcV Sats:ab ch:ab
#if (defined BAT_MEASURE_ADC || defined HAS_PMU || defined HAS_IP5306)
2020-04-13 15:07:27 +02:00
if (batt_level == 0)
2020-04-06 17:40:45 +02:00
dp_printf("No batt ");
2020-04-13 15:07:27 +02:00
else
2020-05-02 17:24:52 +02:00
dp_printf("B:%3d%% ", batt_level);
2020-04-05 23:01:39 +02:00
#else
2020-04-06 17:40:45 +02:00
dp_printf(" ");
2018-07-21 21:50:39 +02:00
#endif
2019-03-24 01:05:13 +01:00
#if (HAS_GPS)
2020-04-06 17:40:45 +02:00
dp_setFont(MY_FONT_SMALL, !gps_hasfix());
dp_printf("Sats:%.2d", gps.satellites.value());
dp_setFont(MY_FONT_SMALL);
2020-04-05 23:01:39 +02:00
#else
2020-04-06 17:40:45 +02:00
dp_printf(" ");
2018-07-15 14:28:05 +02:00
#endif
2020-04-06 17:40:45 +02:00
dp_printf(" ch:%02d", channel);
2020-04-05 23:01:39 +02:00
dp_println();
2018-07-15 14:28:05 +02:00
2019-09-27 17:37:16 +02:00
// line 5: RSSI limiter + free memory
2020-04-05 23:01:39 +02:00
// RLIM:abcd Mem:abcdKB
2020-04-06 17:40:45 +02:00
dp_printf(!cfg.rssilimit ? "RLIM:off " : "RLIM:%-4d", cfg.rssilimit);
dp_printf(" Mem:%4dKB", getFreeRAM() / 1024);
2020-04-05 23:01:39 +02:00
dp_println();
2018-07-15 14:28:05 +02:00
2019-09-27 17:37:16 +02:00
// line 6: time + date
2020-04-05 23:01:39 +02:00
// 27.Feb 2019 20:27:00*
#if (TIME_SYNC_INTERVAL)
2019-02-25 00:26:46 +01:00
timeState = TimePulseTick ? ' ' : timeSetSymbols[timeSource];
2019-02-18 21:51:01 +01:00
TimePulseTick = false;
2019-09-27 17:37:16 +02:00
2020-04-06 17:40:45 +02:00
dp_printf("%02d.%3s %4d", day(t), printmonth[month(t)], year(t));
dp_printf(" %02d:%02d:%02d", hour(t), minute(t), second(t));
2019-09-27 17:37:16 +02:00
// display inverse timeState if clock controller is enabled
#if (defined HAS_DCF77) || (defined HAS_IF482)
2020-04-06 17:40:45 +02:00
dp_setFont(MY_FONT_SMALL, 1);
dp_printf("%c", timeState);
dp_setFont(MY_FONT_SMALL, 0);
#else
2020-04-06 17:40:45 +02:00
dp_printf("%c", timeState);
2019-09-27 17:37:16 +02:00
#endif
2020-04-05 23:01:39 +02:00
dp_println();
#endif // TIME_SYNC_INTERVAL
2019-09-27 17:37:16 +02:00
// line 7: LORA network status
2020-04-05 23:01:39 +02:00
// yyyyyyyyyyyyy xx SFab
#if (HAS_LORA)
2019-10-12 14:07:55 +02:00
// LMiC event display
2020-04-06 17:40:45 +02:00
dp_printf("%-16s", lmic_event_msg);
2019-09-27 17:37:16 +02:00
// LORA datarate, display inverse if ADR disabled
2020-04-06 17:40:45 +02:00
dp_setFont(MY_FONT_SMALL, !cfg.adrmode);
dp_printf(" %-4s", getSfName(updr2rps(LMIC.datarate)));
dp_setFont(MY_FONT_SMALL, 0);
2020-04-05 23:01:39 +02:00
dp_println();
2020-04-10 22:27:37 +02:00
#endif // HAS_LORA
break;
2020-04-10 22:27:37 +02:00
// ---------- page 1: lorawan parameters ----------
case 1:
2020-04-10 22:27:37 +02:00
#if (HAS_LORA)
// 3|NtwkID:000000 TXpw:aa
// 4|DevAdd:00000000 DR:0
// 5|CHMsk:0000 Nonce:0000
// 6|CUp:000000 CDn:000000
// 7|SNR:-0000 RSSI:-0000
dp_setFont(MY_FONT_SMALL);
dp_setTextCursor(0, 3);
dp_printf("NetwID:%06X TXpw:%-2d", LMIC.netid & 0x001FFFFF,
LMIC.radio_txpow);
dp_println();
dp_printf("DevAdd:%08X DR:%1d", LMIC.devaddr, LMIC.datarate);
dp_println();
dp_printf("ChMsk:%04X Nonce:%04X", LMIC.channelMap, LMIC.devNonce);
dp_println();
dp_printf("fUp:%-6d fDn:%-6d", LMIC.seqnoUp ? LMIC.seqnoUp - 1 : 0,
LMIC.seqnoDn ? LMIC.seqnoDn - 1 : 0);
dp_println();
dp_printf("SNR:%-5d RSSI:%-5d", (LMIC.snr + 2) / 4, LMIC.rssi);
break;
#else // flip page if we are unattended
DisplayPage++;
#endif // HAS_LORA
// ---------- page 2: GPS ----------
case 2:
2020-04-10 22:27:37 +02:00
#if (HAS_GPS)
2020-04-06 17:40:45 +02:00
dp_setFont(MY_FONT_STRETCHED);
2020-04-10 22:27:37 +02:00
dp_setTextCursor(0, 3);
2020-01-03 09:59:10 +01:00
if (gps_hasfix()) {
2019-04-01 18:34:57 +02:00
// line 5: clear "No fix"
if (wasnofix) {
2020-04-06 17:40:45 +02:00
dp_setTextCursor(2, 4);
dp_printf(" ");
2019-04-01 18:34:57 +02:00
wasnofix = false;
}
// line 3-4: GPS latitude
2020-04-06 17:40:45 +02:00
dp_printf("%c%07.4f", gps.location.rawLat().negative ? 'S' : 'N',
gps.location.lat());
// line 6-7: GPS longitude
2020-04-06 17:40:45 +02:00
dp_printf("%c%07.4f", gps.location.rawLat().negative ? 'W' : 'E',
gps.location.lng());
2019-04-01 18:34:57 +02:00
} else {
2020-04-06 17:40:45 +02:00
dp_setTextCursor(2, 4);
dp_setFont(MY_FONT_STRETCHED, 1);
dp_printf("No fix");
2019-04-01 18:34:57 +02:00
wasnofix = true;
}
2020-04-10 22:27:37 +02:00
break;
#else // flip page if we are unattended
DisplayPage++;
#endif
2020-04-10 22:27:37 +02:00
// ---------- page 3: BME280/680 ----------
2019-04-02 22:45:16 +02:00
case 3:
2020-04-10 22:27:37 +02:00
2019-04-02 22:45:16 +02:00
#if (HAS_BME)
2020-04-10 22:27:37 +02:00
dp_setFont(MY_FONT_STRETCHED);
dp_setTextCursor(0, 2);
2019-04-02 22:45:16 +02:00
// line 2-3: Temp
2020-04-06 17:40:45 +02:00
dp_printf("TMP:%-2.1f", bme_status.temperature);
dp_println(2);
2019-04-02 22:45:16 +02:00
// line 4-5: Hum
2020-04-06 17:40:45 +02:00
dp_printf("HUM:%-2.1f", bme_status.humidity);
dp_println(2);
2019-04-02 22:45:16 +02:00
#ifdef HAS_BME680
// line 6-7: IAQ
2020-04-06 17:40:45 +02:00
dp_printf("IAQ:%-3.0f", bme_status.iaq);
#else // is BME280 or BMP180
// line 6-7: Pre
2020-04-06 17:40:45 +02:00
dp_printf("PRE:%-2.1f", bme_status.pressure);
#endif // HAS_BME680
break; // page 3
2020-04-10 22:27:37 +02:00
#else // flip page if we are unattended
DisplayPage++;
#endif // HAS_BME
2019-04-02 22:45:16 +02:00
2020-04-10 22:27:37 +02:00
// ---------- page 4: time ----------
2019-10-01 13:02:30 +02:00
case 4:
2020-04-10 22:27:37 +02:00
2020-04-06 17:40:45 +02:00
dp_setFont(MY_FONT_LARGE);
2020-04-10 22:27:37 +02:00
dp_setTextCursor(0, 4);
2020-04-06 17:40:45 +02:00
dp_printf("%02d:%02d:%02d", hour(t), minute(t), second(t));
2019-10-01 13:02:30 +02:00
break;
2020-04-10 22:27:37 +02:00
// ---------- page 5: pax graph ----------
2019-10-06 14:25:29 +02:00
case 5:
2020-04-10 22:27:37 +02:00
dp_setFont(MY_FONT_NORMAL);
dp_setTextCursor(0, 0);
dp_printf("Pax graph");
dp_dump(plotbuf);
break;
2020-04-10 22:27:37 +02:00
// ---------- page 6: blank screen ----------
case 6:
2020-04-10 22:27:37 +02:00
2020-01-20 11:41:31 +01:00
#ifdef HAS_BUTTON
dp_clear();
2020-01-20 11:41:31 +01:00
break;
2020-04-10 22:27:37 +02:00
#else // flip page if we are unattended
DisplayPage++;
2020-01-20 11:41:31 +01:00
#endif
2019-10-06 14:25:29 +02:00
2020-04-10 22:27:37 +02:00
} // switch (page)
} // dp_drawPage
2018-07-15 14:28:05 +02:00
2020-04-06 17:40:45 +02:00
// ------------- display helper functions -----------------
2020-04-10 18:40:40 +02:00
void dp_setTextCursor(int x, int y) {
// x represents the pixel column
// y represents the text row
dp_col = x;
2020-04-05 23:01:39 +02:00
#if (HAS_DISPLAY) == 1
2020-04-10 22:27:37 +02:00
dp_row = y;
obdSetCursor(&ssoled, dp_col, dp_row);
2020-04-10 22:27:37 +02:00
2020-04-05 23:01:39 +02:00
#elif (HAS_DISPLAY) == 2
2020-04-10 18:40:40 +02:00
switch (dp_font >> 1) {
case MY_FONT_STRETCHED:
case MY_FONT_LARGE:
dp_row = y * 26;
break;
case MY_FONT_SMALL:
case MY_FONT_NORMAL:
default:
dp_row = y * 16;
break;
}
tft.setCursor(dp_col, dp_row);
2020-04-05 23:01:39 +02:00
#endif
}
2020-04-06 17:40:45 +02:00
void dp_setFont(int font, int inv) {
2020-04-05 23:01:39 +02:00
#if (HAS_DISPLAY) == 1
2020-04-06 17:40:45 +02:00
dp_font = (font << 1) | (inv & 0x01);
#elif (HAS_DISPLAY) == 2
// map font oled -> tft
switch (font) {
case MY_FONT_STRETCHED: // 16x16 on OLED
case MY_FONT_LARGE: // 16x32 on OLED
tft.setTextFont(4); // 26px
break;
case MY_FONT_SMALL: // 6x8 on OLED
case MY_FONT_NORMAL: // 8x8 on OLED
default:
tft.setTextFont(2); // 16px
break;
}
// to do: invers printing
#endif
}
void dp_println(int lines) {
dp_col = 0;
dp_row += lines;
#if (HAS_DISPLAY) == 1
dp_setTextCursor(dp_col, dp_row);
2020-04-05 23:01:39 +02:00
#elif (HAS_DISPLAY) == 2
2020-04-10 18:40:40 +02:00
for (int i = 1; i <= lines; i++)
tft.println();
2020-04-05 23:01:39 +02:00
#endif
};
2020-04-06 17:40:45 +02:00
void dp_printf(const char *format, ...) {
2019-09-27 12:47:00 +02:00
char loc_buf[64];
char *temp = loc_buf;
va_list arg;
va_list copy;
va_start(arg, format);
va_copy(copy, arg);
int len = vsnprintf(temp, sizeof(loc_buf), format, copy);
va_end(copy);
if (len < 0) {
va_end(arg);
return;
};
if (len >= sizeof(loc_buf)) {
temp = (char *)malloc(len + 1);
if (temp == NULL) {
va_end(arg);
return;
}
2020-04-05 23:01:39 +02:00
vsnprintf(temp, len + 1, format, arg);
2019-09-27 12:47:00 +02:00
}
va_end(arg);
#if (HAS_DISPLAY) == 1
obdWriteString(&ssoled, 0, -1, dp_row, temp, dp_font >> 1, dp_font & 0x01,
false);
#elif (HAS_DISPLAY) == 2
2020-04-05 18:19:39 +02:00
tft.printf(temp);
#endif
2019-09-27 12:47:00 +02:00
if (temp != loc_buf) {
free(temp);
}
}
void dp_dump(uint8_t *pBuffer) {
#if (HAS_DISPLAY) == 1
obdDumpBuffer(&ssoled, pBuffer);
#elif (HAS_DISPLAY) == 2
2020-04-10 22:27:37 +02:00
// probably oled buffer stucture is not suitable for tft -> to be checked
tft.drawBitmap(0, 0, pBuffer, MY_DISPLAY_WIDTH, MY_DISPLAY_HEIGHT,
MY_DISPLAY_FGCOLOR);
#endif
}
2020-04-05 23:01:39 +02:00
void dp_clear(void) {
2020-04-06 17:40:45 +02:00
dp_setTextCursor(0, 0);
#if (HAS_DISPLAY) == 1
obdFill(&ssoled, 0, 1);
#elif (HAS_DISPLAY) == 2
2020-03-29 23:05:42 +02:00
tft.fillScreen(MY_DISPLAY_BGCOLOR);
#endif
}
void dp_contrast(uint8_t contrast) {
#if (HAS_DISPLAY) == 1
obdSetContrast(&ssoled, contrast);
#elif (HAS_DISPLAY) == 2
2020-04-05 23:01:39 +02:00
// to do: gamma correction for TFT
#endif
}
void dp_power(uint8_t screenon) {
#if (HAS_DISPLAY) == 1
obdPower(&ssoled, screenon);
#elif (HAS_DISPLAY) == 2
// to come
#endif
}
void dp_shutdown(void) {
#if (HAS_DISPLAY) == 1
// block i2c bus access
if (!I2C_MUTEX_LOCK())
ESP_LOGV(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0);
else {
cfg.screenon = 0;
obdPower(&ssoled, false);
delay(DISPLAYREFRESH_MS / 1000 * 1.1);
I2C_MUTEX_UNLOCK(); // release i2c bus access
}
#elif (HAS_DISPLAY) == 2
// to come
#endif
}
2020-03-11 23:47:16 +01:00
2020-04-06 17:40:45 +02:00
// ------------- QR code plotter -----------------
2019-10-01 13:02:30 +02:00
void dp_printqr(uint16_t offset_x, uint16_t offset_y, const char *Message) {
2019-09-29 17:38:16 +02:00
uint8_t qrcodeData[qrcode_getBufferSize(QR_VERSION)];
2019-09-29 16:46:48 +02:00
qrcode_initText(&qrcode, qrcodeData, QR_VERSION, ECC_HIGH, Message);
2019-09-29 23:16:36 +02:00
// draw QR code
2020-04-05 23:01:39 +02:00
for (uint8_t y = 0; y < qrcode.size; y++)
for (uint8_t x = 0; x < qrcode.size; x++)
2019-09-29 23:16:36 +02:00
if (!qrcode_getModule(&qrcode, x, y)) // "black"
dp_fillRect(x * QR_SCALEFACTOR + offset_x,
y * QR_SCALEFACTOR + offset_y, QR_SCALEFACTOR,
QR_SCALEFACTOR, false);
2019-09-29 23:16:36 +02:00
// draw horizontal frame lines
dp_fillRect(0, 0, qrcode.size * QR_SCALEFACTOR + 2 * offset_x, offset_y,
false);
dp_fillRect(0, qrcode.size * QR_SCALEFACTOR + offset_y,
qrcode.size * QR_SCALEFACTOR + 2 * offset_x, offset_y, false);
2019-09-29 23:16:36 +02:00
// draw vertical frame lines
dp_fillRect(0, 0, offset_x, qrcode.size * QR_SCALEFACTOR + 2 * offset_y,
false);
dp_fillRect(qrcode.size * QR_SCALEFACTOR + offset_x, 0, offset_x,
qrcode.size * QR_SCALEFACTOR + 2 * offset_y, false);
2019-09-29 16:46:48 +02:00
}
2020-04-06 17:40:45 +02:00
// ------------- graphics primitives -----------------
void dp_fillRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height,
uint8_t bRender) {
#if (HAS_DISPLAY) == 1
2019-10-01 13:02:30 +02:00
for (uint16_t xi = x; xi < x + width; xi++)
obdDrawLine(&ssoled, xi, y, xi, y + height - 1, 1, bRender);
#elif (HAS_DISPLAY) == 2
2020-04-05 23:01:39 +02:00
tft.fillRect(x, y, width, height, MY_DISPLAY_FGCOLOR);
#endif
2019-09-29 16:46:48 +02:00
}
int dp_drawPixel(uint8_t *buf, const uint16_t x, const uint16_t y,
const uint8_t dot) {
2019-10-01 13:02:30 +02:00
if (x > MY_DISPLAY_WIDTH || y > MY_DISPLAY_HEIGHT)
2019-10-01 13:02:30 +02:00
return -1;
2019-10-01 13:35:05 +02:00
uint8_t bit = y & 7;
uint16_t idx = y / 8 * MY_DISPLAY_WIDTH + x;
2019-10-01 13:02:30 +02:00
2019-10-01 13:35:05 +02:00
buf[idx] &= ~(1 << bit); // clear pixel
2019-10-01 13:02:30 +02:00
if (dot)
2019-10-01 13:35:05 +02:00
buf[idx] |= (1 << bit); // set pixel
2019-10-01 13:02:30 +02:00
return 0;
}
2020-04-06 17:40:45 +02:00
// ------------- buffer scroll functions -----------------
void dp_scrollHorizontal(uint8_t *buf, const uint16_t width,
const uint16_t height, bool left) {
2019-10-01 13:02:30 +02:00
2019-10-13 17:21:50 +02:00
uint16_t col, page, idx = 0;
2019-10-01 13:02:30 +02:00
for (page = 0; page < height / 8; page++) {
2019-10-05 13:12:58 +02:00
if (left) { // scroll left
for (col = 0; col < width - 1; col++) {
idx = page * width + col;
buf[idx] = buf[idx + 1];
}
buf[idx + 1] = 0;
} else // scroll right
{
for (col = width - 1; col > 0; col--) {
idx = page * width + col;
buf[idx] = buf[idx - 1];
}
buf[idx - 1] = 0;
2019-10-01 13:02:30 +02:00
}
2019-10-05 13:12:58 +02:00
}
}
void dp_scrollVertical(uint8_t *buf, const uint16_t width,
const uint16_t height, int offset) {
2019-10-05 13:12:58 +02:00
uint64_t buf_col;
if (!offset)
return; // nothing to do
for (uint16_t col = 0; col < MY_DISPLAY_WIDTH; col++) {
2019-10-05 13:12:58 +02:00
// convert column bytes from display buffer to uint64_t
buf_col = *(uint64_t *)&buf[col * MY_DISPLAY_HEIGHT / 8];
2019-10-05 13:12:58 +02:00
2019-10-06 13:14:23 +02:00
if (offset > 0) // scroll down
2019-10-13 17:21:50 +02:00
buf_col <<= offset;
2019-10-06 13:14:23 +02:00
else // scroll up
2019-10-13 17:21:50 +02:00
buf_col >>= abs(offset);
2019-10-05 13:12:58 +02:00
// write back uint64_t to uint8_t display buffer
*(uint64_t *)&buf[col * MY_DISPLAY_HEIGHT / 8] = buf_col;
2019-10-01 13:02:30 +02:00
}
}
2020-04-06 17:40:45 +02:00
// ------------- curve plotter -----------------
void dp_plotCurve(uint16_t count, bool reset) {
2019-10-01 13:02:30 +02:00
2019-10-04 15:47:33 +02:00
static uint16_t last_count = 0, col = 0, row = 0;
2019-10-05 13:12:58 +02:00
uint16_t v_scroll = 0;
2019-10-01 13:02:30 +02:00
2019-10-01 18:06:49 +02:00
if ((last_count == count) && !reset)
2019-10-01 13:02:30 +02:00
return;
if (reset) { // next count cycle?
if (col < MY_DISPLAY_WIDTH - 1) // matrix not full -> increment column
2019-10-01 13:02:30 +02:00
col++;
2019-10-01 18:06:49 +02:00
else // matrix full -> scroll left 1 dot
dp_scrollHorizontal(plotbuf, MY_DISPLAY_WIDTH, MY_DISPLAY_HEIGHT, true);
2019-10-01 13:02:30 +02:00
2019-10-01 18:06:49 +02:00
} else // clear current dot
dp_drawPixel(plotbuf, col, row, 0);
2019-10-01 18:06:49 +02:00
2019-10-06 13:14:23 +02:00
// scroll down, if necessary
while ((count - v_scroll) > MY_DISPLAY_HEIGHT - 1)
2019-10-05 13:12:58 +02:00
v_scroll++;
if (v_scroll)
dp_scrollVertical(plotbuf, MY_DISPLAY_WIDTH, MY_DISPLAY_HEIGHT, v_scroll);
2019-10-01 13:02:30 +02:00
2019-10-01 18:06:49 +02:00
// set new dot
// row = MY_DISPLAY_HEIGHT - 1 - (count - v_scroll) % MY_DISPLAY_HEIGHT;
row = MY_DISPLAY_HEIGHT - 1 - count - v_scroll;
2019-10-01 13:02:30 +02:00
last_count = count;
dp_drawPixel(plotbuf, col, row, 1);
2019-10-01 13:02:30 +02:00
}
2019-03-07 15:05:52 +01:00
#endif // HAS_DISPLAY