ESP32-PaxCounter/src/display.cpp

455 lines
13 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|
3|B:a.bcV Sats:ab ch:ab SMALL
4|WIFI:abcde BLTH:abcde SMALL
5|RLIM:abcd Mem:abcdKB SMALL
6|27.Feb 2019 20:27:00* SMALL
7|yyyyyyyyyyyyyyyy SFab SMALL
* = char {L|G|R|?} indicates time source,
inverse = clock controller is active,
pulsed = pps input signal is active
y = LMIC event message; ab = payload queue length
2019-09-29 16:46:48 +02:00
FONT_SMALL: 6x8px = 21 chars / line
FONT_NORMAL: 8x8px = 16 chars / line
FONT_STRETCHED: 16x32px = 8 chars / line
*/
2018-07-15 14:28:05 +02:00
// Basic Config
#include "globals.h"
2019-09-27 12:47:00 +02:00
#include <ss_oled.h>
2018-07-15 14:28:05 +02:00
#include <esp_spi_flash.h> // needed for reading ESP32 chip attributes
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
#define DISPLAY_PAGES (5) // number of paxcounter display pages
2019-09-29 16:46:48 +02:00
// settings for oled display library
2019-10-01 13:02:30 +02:00
#define USE_BACKBUFFER
2018-07-15 14:28:05 +02:00
2019-09-29 16:46:48 +02:00
// settings for qr code generator
2019-09-29 17:38:16 +02:00
#define QR_VERSION 3 // 29 x 29px
#define QR_SCALEFACTOR 2 // 29 -> 58x < 64px
2019-09-29 16:46:48 +02:00
2019-10-01 13:02:30 +02:00
// settings for curve plotter
#define PLOT_SCALEFACTOR 1 // downscales pax numbers to display rows
#define DISPLAY_WIDTH 128 // Width in pixels of OLED-display, must be 32X
#define DISPLAY_HEIGHT 64 // Height in pixels of OLED-display, must be 16X
// 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;
2019-10-01 13:02:30 +02:00
uint8_t displaybuf[DISPLAY_WIDTH * DISPLAY_HEIGHT / 8] = {0};
2018-07-19 21:53:56 +02:00
2019-09-29 16:46:48 +02:00
QRCode qrcode;
2019-10-01 13:02:30 +02:00
void init_display(uint8_t verbose) {
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 {
2019-09-27 12:47:00 +02:00
// init display
#ifndef DISPLAY_FLIP
oledInit(OLED_128x64, ANGLE_0, false, -1, -1, 400000L);
#else
oledInit(OLED_128x64, ANGLE_FLIPY, false, -1, -1, 400000L);
2018-07-15 14:28:05 +02:00
#endif
2019-09-27 12:47:00 +02:00
// clear display
oledSetContrast(DISPLAYCONTRAST);
2019-09-27 12:47:00 +02:00
oledFill(0, 1);
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);
dp_printf(0, 0, 0, 0, "** PAXCOUNTER **");
dp_printf(0, 1, 0, 0, "Software v%s", PROGVERSION);
dp_printf(0, 3, 0, 0, "ESP32 %d cores", chip_info.cores);
dp_printf(0, 4, 0, 0, "Chip Rev.%d", chip_info.revision);
dp_printf(0, 5, 0, 0, "WiFi%s%s",
(chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
(chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
dp_printf(0, 6, 0, 0, "%dMB %s Flash",
spi_flash_get_chip_size() / (1024 * 1024),
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "int."
: "ext.");
// give user some time to read or take picture
oledDumpBuffer(NULL);
delay(2000);
oledFill(0x00, 1);
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];
char deveui[17];
os_getDevEui((u1_t *)buf);
sprintf(deveui, "%016llX", *((uint64_t *)&buf));
// display DEVEUI as QR code on the left
oledSetContrast(30);
dp_printqr(3, 3, deveui);
// display DEVEUI as plain text on the right
dp_printf(72, 0, FONT_NORMAL, 0, "LORAWAN");
dp_printf(72, 1, FONT_NORMAL, 0, "DEVEUI:");
dp_printf(80, 3, FONT_NORMAL, 0, "%4.4s", deveui);
dp_printf(80, 4, FONT_NORMAL, 0, "%4.4s", deveui + 4);
dp_printf(80, 5, FONT_NORMAL, 0, "%4.4s", deveui + 8);
dp_printf(80, 6, FONT_NORMAL, 0, "%4.4s", deveui + 12);
// give user some time to read or take picture
oledDumpBuffer(NULL);
delay(8000);
oledSetContrast(DISPLAYCONTRAST);
oledFill(0x00, 1);
2019-09-29 17:38:16 +02:00
#endif // HAS_LORA
2019-10-01 13:02:30 +02:00
} // verbose
2019-09-27 12:47:00 +02:00
oledPower(cfg.screenon); // set display off if disabled
2019-02-02 10:35:20 +01:00
I2C_MUTEX_UNLOCK(); // release i2c bus access
2019-02-22 22:28:35 +01:00
} // mutex
2018-07-15 14:28:05 +02:00
} // init_display
2019-05-31 13:20:11 +02:00
void refreshTheDisplay(bool nextPage) {
2018-07-15 14:28:05 +02:00
static uint8_t DisplayPage = 0;
2019-02-18 21:51:01 +01:00
// 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;
2019-09-27 12:47:00 +02:00
oledPower(cfg.screenon);
}
if (nextPage) {
DisplayPage = (DisplayPage >= DISPLAY_PAGES - 1) ? 0 : (DisplayPage + 1);
2019-09-27 12:47:00 +02:00
oledFill(0, 1);
2019-02-22 22:28:35 +01:00
}
draw_page(t, DisplayPage);
oledDumpBuffer(NULL);
I2C_MUTEX_UNLOCK(); // release i2c bus access
} // mutex
} // refreshDisplay()
void draw_page(time_t t, uint8_t page) {
char timeState;
uint8_t msgWaiting;
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
2019-09-27 17:37:16 +02:00
// line 1/2: pax counter
2019-09-27 12:47:00 +02:00
dp_printf(0, 0, FONT_STRETCHED, 0, "PAX:%-4d",
2019-10-01 13:02:30 +02:00
macs.size()); // display number of unique macs total Wifi + BLE
// update histogram if we have a display
oledPlotCurve(macs.size());
2019-04-02 22:45:16 +02:00
switch (page % DISPLAY_PAGES) {
// page 0: parameters overview
2019-10-01 13:02:30 +02:00
// page 1: pax graph
// page 2: GPS
// page 3: BME280/680
2019-10-01 13:02:30 +02:00
// page 4: time
2019-10-01 13:02:30 +02:00
// page 0: parameters overview
case 0:
2018-07-15 14:28:05 +02:00
2019-09-27 17:37:16 +02:00
// line 3: wifi + bluetooth counters
dp_printf(0, 3, FONT_SMALL, 0, "WIFI:%-5d", macs_wifi);
#if (BLECOUNTER)
if (cfg.blescan)
dp_printf(66, 3, FONT_SMALL, 0, "BLTH:%-5d", macs_ble);
else
dp_printf(66, 3, FONT_SMALL, 0, "%s", "BLTH:off");
#endif
// line 4: Battery + GPS status + Wifi channel
2019-09-07 23:10:53 +02:00
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
if (batt_voltage == 0xffff)
2019-10-01 13:02:30 +02:00
dp_printf(0, 4, FONT_SMALL, 0, "%s", "USB ");
else if (batt_voltage == 0)
dp_printf(0, 4, FONT_SMALL, 0, "%s", "No batt");
else
2019-09-27 17:37:16 +02:00
dp_printf(0, 4, FONT_SMALL, 0, "B:%.2fV", batt_voltage / 1000.0);
2018-07-21 21:50:39 +02:00
#endif
2019-03-24 01:05:13 +01:00
#if (HAS_GPS)
2019-07-28 23:51:24 +02:00
if (gps.location.age() < 1500) // if no fix then display Sats value inverse
2019-09-27 17:37:16 +02:00
dp_printf(48, 4, FONT_SMALL, 0, "Sats:%.2d", gps.satellites.value());
2019-09-27 12:47:00 +02:00
else
2019-09-27 17:37:16 +02:00
dp_printf(48, 4, FONT_SMALL, 1, "Sats:%.2d", gps.satellites.value());
2018-07-15 14:28:05 +02:00
#endif
2019-09-27 17:37:16 +02:00
dp_printf(96, 4, FONT_SMALL, 0, "ch:%02d", channel);
2018-07-15 14:28:05 +02:00
2019-09-27 17:37:16 +02:00
// line 5: RSSI limiter + free memory
dp_printf(0, 5, FONT_SMALL, 0, !cfg.rssilimit ? "RLIM:off " : "RLIM:%-4d",
2019-09-27 12:47:00 +02:00
cfg.rssilimit);
2019-09-27 17:37:16 +02:00
dp_printf(66, 5, FONT_SMALL, 0, "Mem:%4dKB", getFreeRAM() / 1024);
2018-07-15 14:28:05 +02:00
2019-09-27 17:37:16 +02:00
// line 6: time + date
#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
dp_printf(0, 6, FONT_SMALL, 0, "%02d.%3s %4d", day(t), printmonth[month(t)],
year(t));
dp_printf(72, 6, FONT_SMALL, 0, "%02d:%02d:%02d", hour(t), minute(t),
second(t));
// display inverse timeState if clock controller is enabled
#if (defined HAS_DCF77) || (defined HAS_IF482)
2019-09-27 17:37:16 +02:00
dp_printf(120, 6, FONT_SMALL, 1, "%c", timeState);
#else
2019-09-27 17:37:16 +02:00
dp_printf(120, 6, FONT_SMALL, 0, "%c", timeState);
#endif
#endif // TIME_SYNC_INTERVAL
2019-09-27 17:37:16 +02:00
// line 7: LORA network status
#if (HAS_LORA)
2019-09-27 17:37:16 +02:00
// LMiC event display, display inverse if sendqueue not empty
msgWaiting = uxQueueMessagesWaiting(LoraSendQueue);
2019-09-27 17:37:16 +02:00
if (msgWaiting)
dp_printf(0, 7, FONT_SMALL, 1, "%-17s", lmic_event_msg);
else
dp_printf(0, 7, FONT_SMALL, 0, "%-17s", lmic_event_msg);
// LORA datarate, display inverse if ADR disabled
if (cfg.adrmode)
dp_printf(108, 7, FONT_SMALL, 0, "%-4s",
getSfName(updr2rps(LMIC.datarate)));
else
dp_printf(108, 7, FONT_SMALL, 1, "%-4s",
getSfName(updr2rps(LMIC.datarate)));
2018-07-15 14:28:05 +02:00
#endif // HAS_LORA
2018-08-04 15:27:58 +02:00
break; // page0
2019-10-01 13:02:30 +02:00
// page 1: pax graph
case 1:
2019-10-01 13:02:30 +02:00
oledDumpBuffer(displaybuf);
break; // page1
2019-10-01 13:02:30 +02:00
// page 2: GPS
case 2:
#if (HAS_GPS)
if (gps.location.age() < 1500) {
2019-04-01 18:34:57 +02:00
// line 5: clear "No fix"
if (wasnofix) {
2019-09-27 12:47:00 +02:00
dp_printf(16, 5, FONT_STRETCHED, 0, " ");
2019-04-01 18:34:57 +02:00
wasnofix = false;
}
// line 3-4: GPS latitude
2019-09-27 12:47:00 +02:00
dp_printf(0, 3, FONT_STRETCHED, 0, "%c%07.4f",
gps.location.rawLat().negative ? 'S' : 'N', gps.location.lat());
// line 6-7: GPS longitude
2019-09-27 12:47:00 +02:00
dp_printf(0, 6, FONT_STRETCHED, 0, "%c%07.4f",
gps.location.rawLat().negative ? 'W' : 'E', gps.location.lng());
2019-04-01 18:34:57 +02:00
} else {
2019-09-27 12:47:00 +02:00
dp_printf(16, 5, FONT_STRETCHED, 1, "No fix");
2019-04-01 18:34:57 +02:00
wasnofix = true;
}
#else
2019-09-27 12:47:00 +02:00
dp_printf(16, 5, FONT_STRETCHED, 1, "No GPS");
#endif
break; // page2
2019-10-01 13:02:30 +02:00
// page 3: BME280/680
2019-04-02 22:45:16 +02:00
case 3:
#if (HAS_BME)
// line 2-3: Temp
2019-09-27 12:47:00 +02:00
dp_printf(0, 2, FONT_STRETCHED, 0, "TMP:%-2.1f", bme_status.temperature);
2019-04-02 22:45:16 +02:00
// line 4-5: Hum
2019-09-27 12:47:00 +02:00
dp_printf(0, 4, FONT_STRETCHED, 0, "HUM:%-2.1f", bme_status.humidity);
2019-04-02 22:45:16 +02:00
#ifdef HAS_BME680
// line 6-7: IAQ
2019-09-27 12:47:00 +02:00
dp_printf(0, 6, FONT_STRETCHED, 0, "IAQ:%-3.0f", bme_status.iaq);
2019-04-02 22:45:16 +02:00
#endif
#else
2019-09-27 12:47:00 +02:00
dp_printf(16, 5, FONT_STRETCHED, 1, "No BME");
2019-04-02 22:45:16 +02:00
#endif
break; // page3
2019-10-01 13:02:30 +02:00
// page 4: time
case 4:
dp_printf(0, 4, FONT_LARGE, 0, "%02d:%02d:%02d", hour(t), minute(t),
second(t));
break;
default:
break; // default
} // switch
} // draw_page
2018-07-15 14:28:05 +02:00
2019-10-01 13:02:30 +02:00
// display helper functions
void dp_printf(uint16_t x, uint16_t y, uint8_t font, uint8_t inv,
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;
}
len = vsnprintf(temp, len + 1, format, arg);
}
va_end(arg);
oledWriteString(0, x, y, temp, font, inv, false);
2019-09-27 12:47:00 +02:00
if (temp != loc_buf) {
free(temp);
}
}
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
2019-10-01 13:02:30 +02:00
for (uint16_t y = 0; y < qrcode.size; y++)
for (uint16_t x = 0; x < qrcode.size; x++)
2019-09-29 23:16:36 +02:00
if (!qrcode_getModule(&qrcode, x, y)) // "black"
2019-09-29 17:38:16 +02:00
oledfillRect(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
oledfillRect(0, 0, qrcode.size * QR_SCALEFACTOR + 2 * offset_x, offset_y,
false);
2019-09-29 23:16:36 +02:00
oledfillRect(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
oledfillRect(0, 0, offset_x, qrcode.size * QR_SCALEFACTOR + 2 * offset_y,
false);
2019-09-29 23:16:36 +02:00
oledfillRect(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
}
2019-10-01 13:02:30 +02:00
void oledfillRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height,
uint8_t bRender) {
for (uint16_t xi = x; xi < x + width; xi++)
2019-09-29 16:46:48 +02:00
oledDrawLine(xi, y, xi, y + height - 1, bRender);
}
2019-10-01 13:02:30 +02:00
int oledDrawPixel(uint8_t *buf, const uint16_t x, const uint16_t y,
const uint8_t dot) {
if (x > DISPLAY_WIDTH || y > DISPLAY_HEIGHT)
return -1;
2019-10-01 13:35:05 +02:00
uint8_t bit = y & 7;
uint16_t idx = y / 8 * 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;
}
void oledScrollBufferLeft(uint8_t *buf, const uint16_t width,
const uint16_t height) {
uint16_t col, page, idx;
for (page = 0; page < height / 8; page++) {
for (col = 0; col < width - 1; col++) {
idx = page * width + col;
buf[idx] = buf[idx + 1];
}
buf[idx + 1] = 0;
}
}
void oledPlotCurve(uint16_t count) {
uint8_t level;
static uint16_t last_count = 0, col = 0, row = 0;
if (last_count == count)
return;
// next count cycle?
if (count == 0) {
// matrix full? then scroll left 1 dot, else increment column
if (col < DISPLAY_WIDTH - 1)
col++;
else
oledScrollBufferLeft(displaybuf, DISPLAY_WIDTH, DISPLAY_HEIGHT);
} else
oledDrawPixel(displaybuf, col, row, 0); // clear current dot
// scale and set new dot
last_count = count;
level = count / PLOT_SCALEFACTOR;
row =
level <= DISPLAY_HEIGHT ? DISPLAY_HEIGHT - 1 - level % DISPLAY_HEIGHT : 0;
oledDrawPixel(displaybuf, col, row, 1);
}
2019-03-07 15:05:52 +01:00
#endif // HAS_DISPLAY