mirror of https://github.com/meshcore-dev/MeshCore
6 changed files with 1163 additions and 8 deletions
@ -0,0 +1,132 @@ |
|||
#pragma once |
|||
|
|||
#include <helpers/ui/DisplayDriver.h> |
|||
|
|||
|
|||
#ifndef STATUS_BAR_SCROLL_MS |
|||
#define STATUS_BAR_SCROLL_MS 80 |
|||
#endif |
|||
|
|||
#ifndef STATUS_BAR_SEPARATOR |
|||
#define STATUS_BAR_SEPARATOR " | " |
|||
#endif |
|||
|
|||
#ifndef STATUS_BAR_UPDATE_MS |
|||
#define STATUS_BAR_UPDATE_MS 2000 // rebuild status string every 2s
|
|||
#endif |
|||
|
|||
class ScrollingStatusBar { |
|||
char _status[160]; |
|||
int _text_width; |
|||
int _scroll_x; |
|||
int _display_width; |
|||
unsigned long _next_scroll; |
|||
unsigned long _next_update; |
|||
bool _needs_redraw; |
|||
|
|||
// cached state for change detection
|
|||
char _last_name[32]; |
|||
uint16_t _last_batt_mv; |
|||
bool _last_buzzer_quiet; |
|||
bool _last_gps_on; |
|||
bool _last_ble_on; |
|||
|
|||
public: |
|||
ScrollingStatusBar() : _text_width(0), _scroll_x(0), _display_width(72), |
|||
_next_scroll(0), _next_update(0), _needs_redraw(true), |
|||
_last_batt_mv(0), _last_buzzer_quiet(false), |
|||
_last_gps_on(false), _last_ble_on(false) { |
|||
_status[0] = 0; |
|||
_last_name[0] = 0; |
|||
} |
|||
|
|||
void begin(int display_width) { |
|||
_display_width = display_width; |
|||
_scroll_x = 0; |
|||
_next_scroll = 0; |
|||
_next_update = 0; |
|||
} |
|||
|
|||
// Call periodically to update the status string content.
|
|||
// Only rebuilds if values have changed or update interval has elapsed.
|
|||
void update(DisplayDriver& display, const char* node_name, uint16_t batt_millivolts, |
|||
bool buzzer_quiet, bool gps_on, bool ble_on) { |
|||
|
|||
bool changed = (batt_millivolts != _last_batt_mv) |
|||
|| (buzzer_quiet != _last_buzzer_quiet) |
|||
|| (gps_on != _last_gps_on) |
|||
|| (ble_on != _last_ble_on) |
|||
|| (strcmp(node_name, _last_name) != 0); |
|||
|
|||
if (!changed) return; |
|||
|
|||
// cache current values
|
|||
strncpy(_last_name, node_name, sizeof(_last_name) - 1); |
|||
_last_name[sizeof(_last_name) - 1] = 0; |
|||
_last_batt_mv = batt_millivolts; |
|||
_last_buzzer_quiet = buzzer_quiet; |
|||
_last_gps_on = gps_on; |
|||
_last_ble_on = ble_on; |
|||
|
|||
float volts = batt_millivolts / 1000.0f; |
|||
|
|||
snprintf(_status, sizeof(_status), |
|||
"%s" STATUS_BAR_SEPARATOR |
|||
"%.2fV" STATUS_BAR_SEPARATOR |
|||
"BUZ:%s" STATUS_BAR_SEPARATOR |
|||
"GPS:%s" STATUS_BAR_SEPARATOR |
|||
"BLE:%s" |
|||
" - ", // trailing gap before the text loops
|
|||
node_name, |
|||
volts, |
|||
buzzer_quiet ? "OFF" : "ON", |
|||
gps_on ? "ON" : "OFF", |
|||
ble_on ? "ON" : "OFF" |
|||
); |
|||
|
|||
_text_width = display.getTextWidth(_status); |
|||
_next_update = millis() + STATUS_BAR_UPDATE_MS; |
|||
_needs_redraw = true; |
|||
} |
|||
|
|||
// Returns true if the status bar needs a redraw this frame.
|
|||
bool needsRedraw() { |
|||
if (_text_width <= _display_width) return _needs_redraw; // static, no scrolling
|
|||
return millis() >= _next_scroll; |
|||
} |
|||
|
|||
// Render the status bar via DisplayDriver.
|
|||
// U8g2 full-buffer mode clips to display bounds automatically,
|
|||
// and the font height stays within STATUS_BAR_HEIGHT, so no
|
|||
// explicit clip window is needed.
|
|||
void render(DisplayDriver& display) { |
|||
if (_status[0] == 0) return; |
|||
|
|||
display.setTextSize(1); |
|||
display.setColor(DisplayDriver::GREEN); |
|||
|
|||
// static text: no scrolling needed
|
|||
if (_text_width <= _display_width) { |
|||
display.setCursor(0, 0); |
|||
display.print(_status); |
|||
_needs_redraw = false; |
|||
return; |
|||
} |
|||
|
|||
int x = _scroll_x; |
|||
do { |
|||
display.setCursor(x, 0); |
|||
display.print(_status); |
|||
x += _text_width; |
|||
} while (x < _display_width); |
|||
|
|||
|
|||
// advance scroll position
|
|||
_scroll_x--; |
|||
if (_scroll_x <= -_text_width) _scroll_x = 0; |
|||
|
|||
_next_scroll = millis() + STATUS_BAR_SCROLL_MS; |
|||
_needs_redraw = false; |
|||
} |
|||
|
|||
}; |
|||
@ -0,0 +1,810 @@ |
|||
#include "UITask.h" |
|||
#include <helpers/TxtDataHelpers.h> |
|||
#include "../MyMesh.h" |
|||
#include "target.h" |
|||
#include "u8g2_icons.h" |
|||
|
|||
#ifdef WIFI_SSID |
|||
#include <WiFi.h> |
|||
#endif |
|||
|
|||
#ifndef AUTO_OFF_MILLIS |
|||
#define AUTO_OFF_MILLIS 15000 // 15 seconds
|
|||
#endif |
|||
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds
|
|||
|
|||
#ifdef PIN_STATUS_LED |
|||
#define LED_ON_MILLIS 20 |
|||
#define LED_ON_MSG_MILLIS 200 |
|||
#define LED_CYCLE_MILLIS 4000 |
|||
#endif |
|||
|
|||
#define LONG_PRESS_MILLIS 1200 |
|||
|
|||
#ifndef UI_RECENT_LIST_SIZE |
|||
#define UI_RECENT_LIST_SIZE 4 |
|||
#endif |
|||
|
|||
#if UI_HAS_JOYSTICK |
|||
#define PRESS_LABEL "press Enter" |
|||
#else |
|||
#define PRESS_LABEL "long press" |
|||
#endif |
|||
|
|||
class SplashScreen : public UIScreen { |
|||
UITask* _task; |
|||
unsigned long dismiss_after; |
|||
unsigned long version_after; |
|||
char _version_info[12]; |
|||
|
|||
public: |
|||
SplashScreen(UITask* task) : _task(task) { |
|||
// strip off dash and commit hash by changing dash to null terminator
|
|||
// e.g: v1.2.3-abcdef -> v1.2.3
|
|||
const char *ver = FIRMWARE_VERSION; |
|||
const char *dash = strchr(ver, '-'); |
|||
|
|||
int len = dash ? dash - ver : strlen(ver); |
|||
if (len >= sizeof(_version_info)) len = sizeof(_version_info) - 1; |
|||
memcpy(_version_info, ver, len); |
|||
_version_info[len] = 0; |
|||
|
|||
version_after = millis() + BOOT_SCREEN_MILLIS / 2; |
|||
dismiss_after = millis() + BOOT_SCREEN_MILLIS; |
|||
} |
|||
|
|||
int render(DisplayDriver& display) override { |
|||
if (millis() < version_after) { |
|||
// meshcore logo
|
|||
display.setColor(DisplayDriver::BLUE); |
|||
int logoWidth = 72; |
|||
display.drawXbm(0, 0, meshcore_logo, 72, 36); |
|||
} else { |
|||
|
|||
// meshcore website
|
|||
const char* website = "meshcore.io"; |
|||
display.setColor(DisplayDriver::LIGHT); |
|||
display.setTextSize(1); |
|||
uint16_t websiteWidth = display.getTextWidth(website); |
|||
display.setCursor((display.width() - websiteWidth) / 2, 9); |
|||
display.print(website); |
|||
|
|||
// version info
|
|||
display.setColor(DisplayDriver::LIGHT); |
|||
display.setTextSize(1); |
|||
display.drawTextCentered(display.width()/2, 18, _version_info); |
|||
|
|||
display.setTextSize(1); |
|||
display.drawTextCentered(display.width()/2, 27, FIRMWARE_BUILD_DATE); |
|||
} |
|||
return 1000; |
|||
} |
|||
|
|||
void poll() override { |
|||
if (millis() >= dismiss_after) { |
|||
_task->gotoHomeScreen(); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
class HomeScreen : public UIScreen { |
|||
enum HomePage { |
|||
FIRST, |
|||
RECENT, |
|||
RADIO, |
|||
BLUETOOTH, |
|||
ADVERT, |
|||
#if ENV_INCLUDE_GPS == 1 |
|||
GPS, |
|||
#endif |
|||
#if UI_SENSORS_PAGE == 1 |
|||
SENSORS, |
|||
#endif |
|||
SHUTDOWN, |
|||
Count // keep as last
|
|||
}; |
|||
|
|||
UITask* _task; |
|||
mesh::RTCClock* _rtc; |
|||
SensorManager* _sensors; |
|||
NodePrefs* _node_prefs; |
|||
uint8_t _page; |
|||
bool _shutdown_init; |
|||
AdvertPath recent[UI_RECENT_LIST_SIZE]; |
|||
|
|||
CayenneLPP sensors_lpp; |
|||
int sensors_nb = 0; |
|||
bool sensors_scroll = false; |
|||
int sensors_scroll_offset = 0; |
|||
int next_sensors_refresh = 0; |
|||
|
|||
void refresh_sensors() { |
|||
if (millis() > next_sensors_refresh) { |
|||
sensors_lpp.reset(); |
|||
sensors_nb = 0; |
|||
sensors_lpp.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); |
|||
sensors.querySensors(0xFF, sensors_lpp); |
|||
LPPReader reader (sensors_lpp.getBuffer(), sensors_lpp.getSize()); |
|||
uint8_t channel, type; |
|||
while(reader.readHeader(channel, type)) { |
|||
reader.skipData(type); |
|||
sensors_nb ++; |
|||
} |
|||
sensors_scroll = sensors_nb > UI_RECENT_LIST_SIZE; |
|||
#if AUTO_OFF_MILLIS > 0 |
|||
next_sensors_refresh = millis() + 5000; // refresh sensor values every 5 sec
|
|||
#else |
|||
next_sensors_refresh = millis() + 60000; // refresh sensor values every 1 min
|
|||
#endif |
|||
} |
|||
} |
|||
|
|||
public: |
|||
HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs) |
|||
: _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0), |
|||
_shutdown_init(false), sensors_lpp(200) { } |
|||
|
|||
void poll() override { |
|||
if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released
|
|||
_task->shutdown(); |
|||
} |
|||
} |
|||
|
|||
int render(DisplayDriver& display) override { |
|||
char tmp[80]; |
|||
|
|||
if (_page == HomePage::FIRST) { |
|||
// // node name
|
|||
// display.setTextSize(1);
|
|||
// display.setColor(DisplayDriver::GREEN);
|
|||
// char filtered_name[sizeof(_node_prefs->node_name)];
|
|||
// display.translateUTF8ToBlocks(filtered_name, _node_prefs->node_name, sizeof(filtered_name));
|
|||
// display.setCursor(0, 0);
|
|||
// display.print(filtered_name);
|
|||
|
|||
|
|||
display.setColor(DisplayDriver::YELLOW); |
|||
display.setTextSize(2); |
|||
sprintf(tmp, "MSG: %d", _task->getMsgCount()); |
|||
display.setCursor(0, 10); |
|||
display.print(tmp); |
|||
|
|||
sprintf(tmp, "BATT: %.2fV", _task->getCachedBattMV() / 1000.0f); |
|||
display.setCursor(0, 19); |
|||
display.print(tmp); |
|||
|
|||
#ifdef WIFI_SSID |
|||
IPAddress ip = WiFi.localIP(); |
|||
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); |
|||
display.setTextSize(1); |
|||
display.drawTextCentered(display.width() / 2, 54, tmp); |
|||
#endif |
|||
if (_task->hasConnection()) { |
|||
display.setColor(DisplayDriver::GREEN); |
|||
display.setTextSize(2); |
|||
display.drawTextCentered(display.width() / 2, display.height()-8, "< Connected >"); |
|||
|
|||
} else if (the_mesh.getBLEPin() != 0) { // BT pin
|
|||
display.setColor(DisplayDriver::RED); |
|||
display.setTextSize(2); |
|||
sprintf(tmp, "Pin:%d", the_mesh.getBLEPin()); |
|||
display.drawTextCentered(display.width() / 2, display.height()-8, tmp); |
|||
} |
|||
} else if (_page == HomePage::RECENT) { |
|||
the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE); |
|||
display.setColor(DisplayDriver::GREEN); |
|||
int y = 8; |
|||
for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) { |
|||
auto a = &recent[i]; |
|||
if (a->name[0] == 0) continue; // empty slot
|
|||
int secs = _rtc->getCurrentTime() - a->recv_timestamp; |
|||
if (secs < 60) { |
|||
sprintf(tmp, "%ds", secs); |
|||
} else if (secs < 60*60) { |
|||
sprintf(tmp, "%dm", secs / 60); |
|||
} else { |
|||
sprintf(tmp, "%dh", secs / (60*60)); |
|||
} |
|||
|
|||
int timestamp_width = display.getTextWidth(tmp); |
|||
int max_name_width = display.width() - timestamp_width - 1; |
|||
|
|||
char filtered_recent_name[sizeof(a->name)]; |
|||
display.translateUTF8ToBlocks(filtered_recent_name, a->name, sizeof(filtered_recent_name)); |
|||
display.drawTextEllipsized(0, y, max_name_width, filtered_recent_name); |
|||
display.setCursor(display.width() - timestamp_width - 1, y); |
|||
display.print(tmp); |
|||
} |
|||
} else if (_page == HomePage::RADIO) { |
|||
display.setColor(DisplayDriver::YELLOW); |
|||
display.setTextSize(1); |
|||
// frequency and spreading factor
|
|||
display.setCursor(0, 8); |
|||
sprintf(tmp, "FQ %06.3f", _node_prefs->freq); |
|||
display.print(tmp); |
|||
sprintf(tmp, "SF%d", _node_prefs->sf); |
|||
display.drawTextRightAlign(display.width(), 8, tmp); |
|||
// bandwidth and coding rate
|
|||
display.setCursor(0, 17); |
|||
sprintf(tmp, "BW %03.2f", _node_prefs->bw); |
|||
display.print(tmp); |
|||
sprintf(tmp, "CR%d", _node_prefs->cr); |
|||
display.drawTextRightAlign(display.width(), 17, tmp); |
|||
// tx power and noise floor
|
|||
display.setCursor(0, 26); |
|||
sprintf(tmp, "NF %ddB", radio_driver.getNoiseFloor()); |
|||
display.print(tmp); |
|||
sprintf(tmp, "TX%d", _node_prefs->tx_power_dbm); |
|||
display.drawTextRightAlign(display.width(), 26, tmp); |
|||
|
|||
} else if (_page == HomePage::BLUETOOTH) { |
|||
display.setColor(DisplayDriver::GREEN); |
|||
display.drawXbm((display.width() - 32) / 2, 8, |
|||
_task->isSerialEnabled() ? bluetooth_on : bluetooth_off, |
|||
32, 32); |
|||
display.setTextSize(1); |
|||
// display.drawTextCentered(display.width() / 2, 40 - 11, "toggle: " PRESS_LABEL);
|
|||
} else if (_page == HomePage::ADVERT) { |
|||
display.setColor(DisplayDriver::GREEN); |
|||
display.drawXbm((display.width() - 32) / 2, 8, advert_icon, 32, 32); |
|||
// display.drawTextCentered(display.width() / 2, 40 - 11, "advert: " PRESS_LABEL);
|
|||
#if ENV_INCLUDE_GPS == 1 |
|||
} else if (_page == HomePage::GPS) { |
|||
LocationProvider* nmea = sensors.getLocationProvider(); |
|||
char buf[50]; |
|||
int y = 8; |
|||
bool gps_state = _task->getGPSState(); |
|||
#ifdef PIN_GPS_SWITCH |
|||
bool hw_gps_state = digitalRead(PIN_GPS_SWITCH); |
|||
if (gps_state != hw_gps_state) { |
|||
strcpy(buf, gps_state ? "gps off(hw)" : "gps off(sw)"); |
|||
} else { |
|||
strcpy(buf, gps_state ? "gps on" : "gps off"); |
|||
} |
|||
#else |
|||
strcpy(buf, gps_state ? "gps on" : "gps off"); |
|||
#endif |
|||
display.drawTextLeftAlign(0, y, buf); |
|||
if (nmea == NULL) { |
|||
// y = y + 8;
|
|||
display.drawTextLeftAlign(0, y, "Can't access GPS"); |
|||
} else { |
|||
if (!gps_state || !nmea->isValid()) { |
|||
strcpy(buf, "no fix"); |
|||
} else { |
|||
sprintf(buf, "%d sat", nmea->satellitesCount()); |
|||
} |
|||
display.drawTextRightAlign(display.width()-1, y, buf); |
|||
y = y + 8; |
|||
sprintf(buf, "lat %.4f", |
|||
nmea->getLatitude()/1000000.); |
|||
display.drawTextLeftAlign(0, y, buf); |
|||
y = y + 8; |
|||
sprintf(buf, "lon %.4f", |
|||
nmea->getLongitude()/1000000.); |
|||
display.drawTextLeftAlign(0, y, buf); |
|||
y = y + 8; |
|||
sprintf(buf, "alt %.1f", nmea->getAltitude()/1000.); |
|||
display.drawTextLeftAlign(0, y, buf); |
|||
} |
|||
#endif |
|||
#if UI_SENSORS_PAGE == 1 |
|||
} else if (_page == HomePage::SENSORS) { |
|||
int y = 8; |
|||
refresh_sensors(); |
|||
char buf[30]; |
|||
char name[30]; |
|||
LPPReader r(sensors_lpp.getBuffer(), sensors_lpp.getSize()); |
|||
|
|||
for (int i = 0; i < sensors_scroll_offset; i++) { |
|||
uint8_t channel, type; |
|||
r.readHeader(channel, type); |
|||
r.skipData(type); |
|||
} |
|||
|
|||
for (int i = 0; i < (sensors_scroll?UI_RECENT_LIST_SIZE:sensors_nb); i++) { |
|||
uint8_t channel, type; |
|||
if (!r.readHeader(channel, type)) { // reached end, reset
|
|||
r.reset(); |
|||
r.readHeader(channel, type); |
|||
} |
|||
|
|||
display.setCursor(0, y); |
|||
float v; |
|||
switch (type) { |
|||
case LPP_GPS: // GPS
|
|||
float lat, lon, alt; |
|||
r.readGPS(lat, lon, alt); |
|||
strcpy(name, "gps"); sprintf(buf, "%.4f %.4f", lat, lon); |
|||
break; |
|||
case LPP_VOLTAGE: |
|||
r.readVoltage(v); |
|||
strcpy(name, "voltage"); sprintf(buf, "%6.2f", v); |
|||
break; |
|||
case LPP_CURRENT: |
|||
r.readCurrent(v); |
|||
strcpy(name, "current"); sprintf(buf, "%.3f", v); |
|||
break; |
|||
case LPP_TEMPERATURE: |
|||
r.readTemperature(v); |
|||
strcpy(name, "temperature"); sprintf(buf, "%.2f", v); |
|||
break; |
|||
case LPP_RELATIVE_HUMIDITY: |
|||
r.readRelativeHumidity(v); |
|||
strcpy(name, "humidity"); sprintf(buf, "%.2f", v); |
|||
break; |
|||
case LPP_BAROMETRIC_PRESSURE: |
|||
r.readPressure(v); |
|||
strcpy(name, "pressure"); sprintf(buf, "%.2f", v); |
|||
break; |
|||
case LPP_ALTITUDE: |
|||
r.readAltitude(v); |
|||
strcpy(name, "altitude"); sprintf(buf, "%.0f", v); |
|||
break; |
|||
case LPP_POWER: |
|||
r.readPower(v); |
|||
strcpy(name, "power"); sprintf(buf, "%6.2f", v); |
|||
break; |
|||
default: |
|||
r.skipData(type); |
|||
strcpy(name, "unk"); sprintf(buf, ""); |
|||
} |
|||
display.setCursor(0, y); |
|||
display.print(name); |
|||
display.setCursor( |
|||
display.width()-display.getTextWidth(buf)-1, y |
|||
); |
|||
display.print(buf); |
|||
y = y + 12; |
|||
} |
|||
if (sensors_scroll) sensors_scroll_offset = (sensors_scroll_offset+1)%sensors_nb; |
|||
else sensors_scroll_offset = 0; |
|||
#endif |
|||
} else if (_page == HomePage::SHUTDOWN) { |
|||
display.setColor(DisplayDriver::GREEN); |
|||
display.setTextSize(1); |
|||
if (_shutdown_init) { |
|||
display.drawTextCentered(display.width() / 2, 20, "hibernating..."); |
|||
} else { |
|||
display.drawXbm((display.width() - 32) / 2, 8, power_icon, 32, 32); |
|||
// display.drawTextCentered(display.width() / 2, 40 - 11, "hibernate:" PRESS_LABEL);
|
|||
} |
|||
} |
|||
return 5000; // next render after 5000 ms
|
|||
} |
|||
|
|||
bool handleInput(char c) override { |
|||
if (c == KEY_LEFT || c == KEY_PREV) { |
|||
_page = (_page + HomePage::Count - 1) % HomePage::Count; |
|||
return true; |
|||
} |
|||
if (c == KEY_NEXT || c == KEY_RIGHT) { |
|||
_page = (_page + 1) % HomePage::Count; |
|||
if (_page == HomePage::RECENT) { |
|||
_task->showAlert("Recent adverts", 800); |
|||
} |
|||
return true; |
|||
} |
|||
if (c == KEY_ENTER && _page == HomePage::BLUETOOTH) { |
|||
if (_task->isSerialEnabled()) { // toggle Bluetooth on/off
|
|||
_task->disableSerial(); |
|||
} else { |
|||
_task->enableSerial(); |
|||
} |
|||
return true; |
|||
} |
|||
if (c == KEY_ENTER && _page == HomePage::ADVERT) { |
|||
_task->notify(UIEventType::ack); |
|||
if (the_mesh.advert()) { |
|||
_task->showAlert("Advert sent!", 1000); |
|||
} else { |
|||
_task->showAlert("Advert failed..", 1000); |
|||
} |
|||
return true; |
|||
} |
|||
#if ENV_INCLUDE_GPS == 1 |
|||
if (c == KEY_ENTER && _page == HomePage::GPS) { |
|||
_task->toggleGPS(); |
|||
return true; |
|||
} |
|||
#endif |
|||
#if UI_SENSORS_PAGE == 1 |
|||
if (c == KEY_ENTER && _page == HomePage::SENSORS) { |
|||
_task->toggleGPS(); |
|||
next_sensors_refresh=0; |
|||
return true; |
|||
} |
|||
#endif |
|||
if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) { |
|||
_shutdown_init = true; // need to wait for button to be released
|
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
}; |
|||
|
|||
|
|||
void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs) { |
|||
_display = display; |
|||
_sensors = sensors; |
|||
_auto_off = millis() + AUTO_OFF_MILLIS; |
|||
_cached_batt_mv = getBattMilliVolts(); |
|||
|
|||
#if defined(PIN_USER_BTN) |
|||
user_btn.begin(); |
|||
#endif |
|||
#if defined(PIN_USER_BTN_ANA) |
|||
analog_btn.begin(); |
|||
#endif |
|||
|
|||
_node_prefs = node_prefs; |
|||
|
|||
if (_display != NULL) { |
|||
_display->turnOn(); |
|||
} |
|||
_statusBar.begin(_display->width()); |
|||
|
|||
|
|||
#ifdef PIN_BUZZER |
|||
buzzer.begin(); |
|||
buzzer.quiet(_node_prefs->buzzer_quiet); |
|||
buzzer.startup(); |
|||
#endif |
|||
|
|||
#ifdef PIN_VIBRATION |
|||
vibration.begin(); |
|||
#endif |
|||
|
|||
ui_started_at = millis(); |
|||
_alert_expiry = 0; |
|||
|
|||
splash = new SplashScreen(this); |
|||
home = new HomeScreen(this, &rtc_clock, sensors, node_prefs); |
|||
setCurrScreen(splash); |
|||
} |
|||
|
|||
void UITask::showAlert(const char* text, int duration_millis) { |
|||
strcpy(_alert, text); |
|||
_alert_expiry = millis() + duration_millis; |
|||
} |
|||
|
|||
void UITask::notify(UIEventType t) { |
|||
#if defined(PIN_BUZZER) |
|||
switch(t){ |
|||
case UIEventType::contactMessage: |
|||
// gemini's pick
|
|||
buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); |
|||
break; |
|||
case UIEventType::channelMessage: |
|||
buzzer.play("kerplop:d=16,o=6,b=120:32g#,32c#"); |
|||
break; |
|||
case UIEventType::ack: |
|||
buzzer.play("ack:d=32,o=8,b=120:c"); |
|||
break; |
|||
case UIEventType::roomMessage: |
|||
case UIEventType::newContactMessage: |
|||
case UIEventType::none: |
|||
default: |
|||
break; |
|||
} |
|||
#endif |
|||
|
|||
#ifdef PIN_VIBRATION |
|||
// Trigger vibration for all UI events except none
|
|||
if (t != UIEventType::none) { |
|||
vibration.trigger(); |
|||
} |
|||
#endif |
|||
} |
|||
|
|||
|
|||
void UITask::msgRead(int msgcount) { |
|||
_msgcount = msgcount; |
|||
if (msgcount == 0) { |
|||
gotoHomeScreen(); |
|||
} |
|||
} |
|||
|
|||
void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) { |
|||
_msgcount = msgcount; |
|||
|
|||
if (_display != NULL) { |
|||
if (!_display->isOn() && !hasConnection()) { |
|||
_display->turnOn(); |
|||
} |
|||
if (_display->isOn()) { |
|||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer
|
|||
_next_refresh = 100; // trigger refresh
|
|||
} |
|||
} |
|||
} |
|||
|
|||
void UITask::userLedHandler() { |
|||
#ifdef PIN_STATUS_LED |
|||
int cur_time = millis(); |
|||
if (cur_time > next_led_change) { |
|||
if (led_state == 0) { |
|||
led_state = 1; |
|||
if (_msgcount > 0) { |
|||
last_led_increment = LED_ON_MSG_MILLIS; |
|||
} else { |
|||
last_led_increment = LED_ON_MILLIS; |
|||
} |
|||
next_led_change = cur_time + last_led_increment; |
|||
} else { |
|||
led_state = 0; |
|||
next_led_change = cur_time + LED_CYCLE_MILLIS - last_led_increment; |
|||
} |
|||
digitalWrite(PIN_STATUS_LED, led_state == LED_STATE_ON); |
|||
} |
|||
#endif |
|||
} |
|||
|
|||
void UITask::setCurrScreen(UIScreen* c) { |
|||
curr = c; |
|||
_next_refresh = 100; |
|||
} |
|||
|
|||
/*
|
|||
hardware-agnostic pre-shutdown activity should be done here |
|||
*/ |
|||
void UITask::shutdown(bool restart){ |
|||
|
|||
#ifdef PIN_BUZZER |
|||
/* note: we have a choice here -
|
|||
we can do a blocking buzzer.loop() with non-deterministic consequences |
|||
or we can set a flag and delay the shutdown for a couple of seconds |
|||
while a non-blocking buzzer.loop() plays out in UITask::loop() |
|||
*/ |
|||
buzzer.shutdown(); |
|||
uint32_t buzzer_timer = millis(); // fail-safe shutdown
|
|||
while (buzzer.isPlaying() && (millis() - 2500) < buzzer_timer) |
|||
buzzer.loop(); |
|||
|
|||
#endif // PIN_BUZZER
|
|||
|
|||
if (restart) { |
|||
_board->reboot(); |
|||
} else { |
|||
_display->turnOff(); |
|||
radio_driver.powerOff(); |
|||
_board->powerOff(); |
|||
} |
|||
} |
|||
|
|||
bool UITask::isButtonPressed() const { |
|||
#ifdef PIN_USER_BTN |
|||
return user_btn.isPressed(); |
|||
#else |
|||
return false; |
|||
#endif |
|||
} |
|||
|
|||
void UITask::loop() { |
|||
char c = 0; |
|||
#if UI_HAS_JOYSTICK |
|||
int ev = user_btn.check(); |
|||
if (ev == BUTTON_EVENT_CLICK) { |
|||
c = checkDisplayOn(KEY_ENTER); |
|||
} else if (ev == BUTTON_EVENT_LONG_PRESS) { |
|||
c = handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code
|
|||
} |
|||
ev = joystick_left.check(); |
|||
if (ev == BUTTON_EVENT_CLICK) { |
|||
c = checkDisplayOn(KEY_LEFT); |
|||
} else if (ev == BUTTON_EVENT_LONG_PRESS) { |
|||
c = handleLongPress(KEY_LEFT); |
|||
} |
|||
ev = joystick_right.check(); |
|||
if (ev == BUTTON_EVENT_CLICK) { |
|||
c = checkDisplayOn(KEY_RIGHT); |
|||
} else if (ev == BUTTON_EVENT_LONG_PRESS) { |
|||
c = handleLongPress(KEY_RIGHT); |
|||
} |
|||
ev = back_btn.check(); |
|||
if (ev == BUTTON_EVENT_TRIPLE_CLICK) { |
|||
c = handleTripleClick(KEY_SELECT); |
|||
} |
|||
#elif defined(PIN_USER_BTN) |
|||
int ev = user_btn.check(); |
|||
if (ev == BUTTON_EVENT_CLICK) { |
|||
c = checkDisplayOn(KEY_NEXT); |
|||
} else if (ev == BUTTON_EVENT_LONG_PRESS) { |
|||
c = handleLongPress(KEY_ENTER); |
|||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { |
|||
c = handleDoubleClick(KEY_PREV); |
|||
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { |
|||
c = handleTripleClick(KEY_SELECT); |
|||
} |
|||
#endif |
|||
#if defined(PIN_USER_BTN_ANA) |
|||
if (abs(millis() - _analogue_pin_read_millis) > 10) { |
|||
int ev = analog_btn.check(); |
|||
if (ev == BUTTON_EVENT_CLICK) { |
|||
c = checkDisplayOn(KEY_NEXT); |
|||
} else if (ev == BUTTON_EVENT_LONG_PRESS) { |
|||
c = handleLongPress(KEY_ENTER); |
|||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { |
|||
c = handleDoubleClick(KEY_PREV); |
|||
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { |
|||
c = handleTripleClick(KEY_SELECT); |
|||
} |
|||
_analogue_pin_read_millis = millis(); |
|||
} |
|||
#endif |
|||
#if defined(BACKLIGHT_BTN) |
|||
if (millis() > next_backlight_btn_check) { |
|||
bool touch_state = digitalRead(PIN_BUTTON2); |
|||
#if defined(DISP_BACKLIGHT) |
|||
digitalWrite(DISP_BACKLIGHT, !touch_state); |
|||
#elif defined(EXP_PIN_BACKLIGHT) |
|||
expander.digitalWrite(EXP_PIN_BACKLIGHT, !touch_state); |
|||
#endif |
|||
next_backlight_btn_check = millis() + 300; |
|||
} |
|||
#endif |
|||
|
|||
if (c != 0 && curr) { |
|||
curr->handleInput(c); |
|||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
|
|||
_next_refresh = 100; // trigger refresh
|
|||
} |
|||
|
|||
userLedHandler(); |
|||
|
|||
#ifdef PIN_BUZZER |
|||
if (buzzer.isPlaying()) buzzer.loop(); |
|||
#endif |
|||
|
|||
if (curr) curr->poll(); |
|||
|
|||
if (_display != NULL && _display->isOn()) { |
|||
_statusBar.update(*_display, |
|||
_node_prefs->node_name, |
|||
_cached_batt_mv, |
|||
isBuzzerQuiet(), |
|||
getGPSState(), |
|||
isSerialEnabled()); |
|||
|
|||
bool status_dirty = _statusBar.needsRedraw(); |
|||
bool content_dirty = (millis() >= _next_refresh && curr); |
|||
|
|||
if (status_dirty || content_dirty) { |
|||
_display->startFrame(); |
|||
_statusBar.render(*_display); |
|||
|
|||
if (curr) { |
|||
int delay_millis = curr->render(*_display); |
|||
if (content_dirty) { |
|||
_next_refresh = millis() + delay_millis; |
|||
} |
|||
} |
|||
|
|||
if (millis() < _alert_expiry) { // render alert popup
|
|||
_display->setTextSize(1); |
|||
int y = _display->height() / 3; |
|||
int p = _display->height() / 32; |
|||
_display->setColor(DisplayDriver::DARK); |
|||
_display->fillRect(p, y, _display->width() - p*2, y); |
|||
_display->setColor(DisplayDriver::LIGHT); // draw box border
|
|||
_display->drawRect(p, y, _display->width() - p*2, y); |
|||
_display->drawTextCentered(_display->width() / 2, y + p*3, _alert); |
|||
_next_refresh = _alert_expiry; // will need refresh when alert is dismissed
|
|||
} |
|||
|
|||
_display->endFrame(); |
|||
} |
|||
#if AUTO_OFF_MILLIS > 0 |
|||
if (millis() > _auto_off) { |
|||
_display->turnOff(); |
|||
} |
|||
#endif |
|||
} |
|||
|
|||
#ifdef PIN_VIBRATION |
|||
vibration.loop(); |
|||
#endif |
|||
|
|||
#ifdef AUTO_SHUTDOWN_MILLIVOLTS |
|||
if (millis() > next_batt_chck) { |
|||
_cached_batt_mv = getBattMilliVolts(); |
|||
if (_cached_batt_mv > 0 && _cached_batt_mv < AUTO_SHUTDOWN_MILLIVOLTS) { |
|||
|
|||
shutdown(); |
|||
|
|||
} |
|||
next_batt_chck = millis() + 8000; |
|||
} |
|||
#else |
|||
if (_display != NULL && _display->isOn() && millis >= next_batt_chck) { |
|||
_cached_batt_mv = getBattMilliVolts(); |
|||
next_batt_chck = millis() + 8000; |
|||
} |
|||
#endif |
|||
} |
|||
|
|||
char UITask::checkDisplayOn(char c) { |
|||
if (_display != NULL) { |
|||
if (!_display->isOn()) { |
|||
_display->turnOn(); // turn display on and consume event
|
|||
c = 0; |
|||
} |
|||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
|
|||
_next_refresh = 0; // trigger refresh
|
|||
} |
|||
return c; |
|||
} |
|||
|
|||
char UITask::handleLongPress(char c) { |
|||
if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue
|
|||
the_mesh.enterCLIRescue(); |
|||
c = 0; // consume event
|
|||
} |
|||
return c; |
|||
} |
|||
|
|||
char UITask::handleDoubleClick(char c) { |
|||
MESH_DEBUG_PRINTLN("UITask: double click triggered"); |
|||
checkDisplayOn(c); |
|||
return c; |
|||
} |
|||
|
|||
char UITask::handleTripleClick(char c) { |
|||
MESH_DEBUG_PRINTLN("UITask: triple click triggered"); |
|||
checkDisplayOn(c); |
|||
toggleBuzzer(); |
|||
c = 0; |
|||
return c; |
|||
} |
|||
|
|||
bool UITask::getGPSState() { |
|||
if (_sensors != NULL) { |
|||
int num = _sensors->getNumSettings(); |
|||
for (int i = 0; i < num; i++) { |
|||
if (strcmp(_sensors->getSettingName(i), "gps") == 0) { |
|||
return !strcmp(_sensors->getSettingValue(i), "1"); |
|||
} |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
void UITask::toggleGPS() { |
|||
if (_sensors != NULL) { |
|||
// toggle GPS on/off
|
|||
int num = _sensors->getNumSettings(); |
|||
for (int i = 0; i < num; i++) { |
|||
if (strcmp(_sensors->getSettingName(i), "gps") == 0) { |
|||
if (strcmp(_sensors->getSettingValue(i), "1") == 0) { |
|||
_sensors->setSettingValue("gps", "0"); |
|||
_node_prefs->gps_enabled = 0; |
|||
notify(UIEventType::ack); |
|||
} else { |
|||
_sensors->setSettingValue("gps", "1"); |
|||
_node_prefs->gps_enabled = 1; |
|||
notify(UIEventType::ack); |
|||
} |
|||
the_mesh.savePrefs(); |
|||
showAlert(_node_prefs->gps_enabled ? "GPS: Enabled" : "GPS: Disabled", 800); |
|||
_next_refresh = 0; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
void UITask::toggleBuzzer() { |
|||
// Toggle buzzer quiet mode
|
|||
#ifdef PIN_BUZZER |
|||
if (buzzer.isQuiet()) { |
|||
buzzer.quiet(false); |
|||
notify(UIEventType::ack); |
|||
} else { |
|||
buzzer.quiet(true); |
|||
} |
|||
_node_prefs->buzzer_quiet = buzzer.isQuiet(); |
|||
the_mesh.savePrefs(); |
|||
showAlert(buzzer.isQuiet() ? "Buzzer: OFF" : "Buzzer: ON", 800); |
|||
_next_refresh = 0; // trigger refresh
|
|||
#endif |
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
#pragma once |
|||
|
|||
#include <MeshCore.h> |
|||
#include <helpers/ui/DisplayDriver.h> |
|||
#include <helpers/ui/UIScreen.h> |
|||
#include <helpers/SensorManager.h> |
|||
#include <helpers/BaseSerialInterface.h> |
|||
#include <Arduino.h> |
|||
#include <helpers/sensors/LPPDataHelpers.h> |
|||
|
|||
#include "ScrollingStatusBar.h" |
|||
|
|||
|
|||
#ifndef LED_STATE_ON |
|||
#define LED_STATE_ON 1 |
|||
#endif |
|||
|
|||
#ifdef PIN_BUZZER |
|||
#include <helpers/ui/buzzer.h> |
|||
#endif |
|||
#ifdef PIN_VIBRATION |
|||
#include <helpers/ui/GenericVibration.h> |
|||
#endif |
|||
|
|||
#include "../AbstractUITask.h" |
|||
#include "../NodePrefs.h" |
|||
|
|||
class UITask : public AbstractUITask { |
|||
DisplayDriver* _display; |
|||
SensorManager* _sensors; |
|||
ScrollingStatusBar _statusBar; |
|||
#ifdef PIN_BUZZER |
|||
genericBuzzer buzzer; |
|||
#endif |
|||
#ifdef PIN_VIBRATION |
|||
GenericVibration vibration; |
|||
#endif |
|||
unsigned long _next_refresh, _auto_off; |
|||
NodePrefs* _node_prefs; |
|||
char _alert[80]; |
|||
unsigned long _alert_expiry; |
|||
int _msgcount; |
|||
unsigned long ui_started_at, next_batt_chck; |
|||
int next_backlight_btn_check = 0; |
|||
uint16_t _cached_batt_mv; |
|||
#ifdef PIN_STATUS_LED |
|||
int led_state = 0; |
|||
int next_led_change = 0; |
|||
int last_led_increment = 0; |
|||
#endif |
|||
|
|||
#ifdef PIN_USER_BTN_ANA |
|||
unsigned long _analogue_pin_read_millis = millis(); |
|||
#endif |
|||
|
|||
UIScreen* splash; |
|||
UIScreen* home; |
|||
// UIScreen* msg_preview;
|
|||
UIScreen* curr; |
|||
|
|||
|
|||
void userLedHandler(); |
|||
|
|||
// Button action handlers
|
|||
char checkDisplayOn(char c); |
|||
char handleLongPress(char c); |
|||
char handleDoubleClick(char c); |
|||
char handleTripleClick(char c); |
|||
|
|||
void setCurrScreen(UIScreen* c); |
|||
|
|||
public: |
|||
|
|||
UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) { |
|||
next_batt_chck = _next_refresh = 0; |
|||
_cached_batt_mv = 0; |
|||
ui_started_at = 0; |
|||
curr = NULL; |
|||
} |
|||
void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs); |
|||
|
|||
void gotoHomeScreen() { setCurrScreen(home); } |
|||
void showAlert(const char* text, int duration_millis); |
|||
int getMsgCount() const { return _msgcount; } |
|||
uint16_t getCachedBattMV() const { return _cached_batt_mv; } |
|||
bool hasDisplay() const { return _display != NULL; } |
|||
bool isButtonPressed() const; |
|||
|
|||
bool isBuzzerQuiet() { |
|||
#ifdef PIN_BUZZER |
|||
return buzzer.isQuiet(); |
|||
#else |
|||
return true; |
|||
#endif |
|||
} |
|||
|
|||
void toggleBuzzer(); |
|||
bool getGPSState(); |
|||
void toggleGPS(); |
|||
|
|||
|
|||
// from AbstractUITask
|
|||
void msgRead(int msgcount) override; |
|||
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override; |
|||
void notify(UIEventType t = UIEventType::none) override; |
|||
void loop() override; |
|||
|
|||
void shutdown(bool restart = false); |
|||
}; |
|||
@ -0,0 +1,104 @@ |
|||
#pragma once |
|||
|
|||
#include <stdint.h> |
|||
// icons converted for use with U8g2 which needs a different format of xbm data.
|
|||
|
|||
// 'meshcore', 72x36px
|
|||
static const uint8_t meshcore_logo [] = { |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0xf0, 0x00, 0x3e, 0xfe, 0x3f, 0xfe, 0x3f, 0x1e, 0x78, |
|||
0xf8, 0x00, 0x1f, 0xff, 0x3f, 0xff, 0x3f, 0x1e, 0x78, 0xf8, 0x01, 0x1f, |
|||
0xff, 0x9f, 0xff, 0x1f, 0x0e, 0x78, 0xf8, 0x81, 0x1f, 0x0f, 0x80, 0x07, |
|||
0x00, 0x0f, 0x38, 0xf8, 0xc1, 0x1f, 0x0f, 0x80, 0x07, 0x00, 0x0f, 0x3c, |
|||
0xf8, 0xc3, 0x1f, 0xff, 0x87, 0xff, 0x07, 0xff, 0x3f, 0xf8, 0xe3, 0x1f, |
|||
0xff, 0x87, 0xff, 0x0f, 0xff, 0x3f, 0xfc, 0xf3, 0x8f, 0xff, 0x07, 0xff, |
|||
0x1f, 0xff, 0x3f, 0xfc, 0xf3, 0x8f, 0x07, 0x00, 0x00, 0x9e, 0x0f, 0x1e, |
|||
0xbc, 0x7f, 0x8f, 0x07, 0x00, 0x00, 0x9e, 0x07, 0x1e, 0x9c, 0x3f, 0x8f, |
|||
0x07, 0x00, 0x00, 0x9f, 0x07, 0x1e, 0x9c, 0x3f, 0x8f, 0xff, 0xcf, 0xff, |
|||
0x8f, 0x07, 0x1e, 0x1e, 0x1f, 0xc7, 0xff, 0xcf, 0xff, 0x87, 0x07, 0x1e, |
|||
0x1e, 0x0f, 0xc7, 0xff, 0xc7, 0xff, 0x83, 0x03, 0x0e, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0xc0, 0xff, 0xf0, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0x07, 0xf0, 0xff, 0xf8, |
|||
0xff, 0xf1, 0xff, 0xc3, 0xff, 0x07, 0xf0, 0xff, 0xfc, 0xff, 0xf1, 0xff, |
|||
0xc7, 0xff, 0x07, 0x78, 0x00, 0x3c, 0xe0, 0xf1, 0xc0, 0xe7, 0x01, 0x00, |
|||
0x78, 0x00, 0x1e, 0xe0, 0xf1, 0x80, 0xe7, 0x01, 0x00, 0x78, 0x00, 0x1e, |
|||
0xe0, 0xf1, 0xc0, 0xe3, 0x01, 0x00, 0x78, 0x00, 0x1e, 0xe0, 0x71, 0xc0, |
|||
0xe3, 0xff, 0x00, 0x3c, 0x00, 0x1e, 0xe0, 0xf9, 0xff, 0xe3, 0xff, 0x00, |
|||
0x3c, 0x00, 0x1e, 0xe0, 0xf8, 0xff, 0xf1, 0xff, 0x00, 0x3c, 0x00, 0x0e, |
|||
0xf0, 0xf8, 0xff, 0xf0, 0x00, 0x00, 0x3c, 0x00, 0x1f, 0xf0, 0x78, 0x7c, |
|||
0xf0, 0x00, 0x00, 0xfc, 0x3f, 0xff, 0xff, 0x38, 0xf8, 0xf0, 0xff, 0x01, |
|||
0xfc, 0x3f, 0xfe, 0x7f, 0x3c, 0xf0, 0xf0, 0xff, 0x01, 0xf8, 0x3f, 0xfe, |
|||
0x3f, 0x3c, 0xf0, 0xf1, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
|||
|
|||
}; |
|||
|
|||
|
|||
// bluetooth on icon, 32x32px, horizontal
|
|||
static const uint8_t bluetooth_on[] = { |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x0c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, |
|||
0x00, 0xfc, 0x01, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xdc, 0x07, 0x00, |
|||
0x0c, 0x1c, 0x1f, 0x00, 0x3c, 0x1c, 0x3e, 0x00, 0x7c, 0x1c, 0x3e, 0x00, |
|||
0xf8, 0x1d, 0x1f, 0x0e, 0xe0, 0x9f, 0x0f, 0x1e, 0xc0, 0xff, 0x03, 0x1e, |
|||
0x00, 0xff, 0x01, 0x3c, 0x00, 0xfe, 0xe0, 0x38, 0x00, 0x7e, 0xe0, 0x38, |
|||
0xc0, 0xff, 0x41, 0x38, 0xc0, 0xff, 0x03, 0x1e, 0xe0, 0xdf, 0x07, 0x1e, |
|||
0xf0, 0x1d, 0x1f, 0x0e, 0x7c, 0x1c, 0x3e, 0x00, 0x3c, 0x1c, 0x3e, 0x00, |
|||
0x1c, 0x1c, 0x1f, 0x00, 0x00, 0x9c, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, |
|||
0x00, 0xfc, 0x01, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, |
|||
0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; |
|||
|
|||
|
|||
// bluetooth off icon, 32x32px, horizontal
|
|||
static const uint8_t bluetooth_off[] = { |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, |
|||
0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x1c, 0xc0, 0x1f, 0x00, |
|||
0x3c, 0xc0, 0x3f, 0x00, 0x7c, 0xc0, 0xfd, 0x00, 0xf0, 0xc1, 0xf1, 0x01, |
|||
0xe0, 0xc3, 0xe1, 0x03, 0xc0, 0x0f, 0xc0, 0x03, 0x00, 0x1f, 0xf0, 0x01, |
|||
0x00, 0x3e, 0xf0, 0x00, 0x00, 0xf8, 0x70, 0x00, 0x00, 0xf0, 0x01, 0x00, |
|||
0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x0f, 0x00, 0x00, 0xf0, 0x1f, 0x00, |
|||
0x00, 0xfc, 0x7d, 0x00, 0x00, 0xfe, 0xf9, 0x00, 0x00, 0xdf, 0xf1, 0x03, |
|||
0xc0, 0xc7, 0xc1, 0x07, 0xc0, 0xc3, 0xe1, 0x0f, 0xc0, 0xc1, 0xf1, 0x3f, |
|||
0x00, 0xc0, 0xfd, 0x3c, 0x00, 0xc0, 0x3f, 0x38, 0x00, 0xc0, 0x1f, 0x00, |
|||
0x00, 0xc0, 0x07, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; |
|||
|
|||
|
|||
// power icon, 32x32px, horizontal
|
|||
static const uint8_t power_icon[] = { |
|||
0x00, 0x80, 0x01, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00, |
|||
0x00, 0xcc, 0x33, 0x00, 0x00, 0xcf, 0xf3, 0x00, 0x80, 0xcf, 0xf3, 0x01, |
|||
0xc0, 0xcf, 0xf3, 0x03, 0xe0, 0xcf, 0xf3, 0x07, 0xf0, 0xc7, 0xe3, 0x0f, |
|||
0xf8, 0xc3, 0xc3, 0x1f, 0xf8, 0xc1, 0x83, 0x1f, 0xfc, 0xc0, 0x03, 0x3f, |
|||
0x7c, 0xc0, 0x03, 0x3e, 0x7c, 0xc0, 0x03, 0x3e, 0x7e, 0x80, 0x01, 0x7e, |
|||
0x3e, 0x00, 0x00, 0x7c, 0x3e, 0x00, 0x00, 0x7c, 0x3e, 0x00, 0x00, 0x7c, |
|||
0x3e, 0x00, 0x00, 0x7c, 0x3e, 0x00, 0x00, 0x7c, 0x7c, 0x00, 0x00, 0x3e, |
|||
0x7c, 0x00, 0x00, 0x3e, 0xfc, 0x00, 0x00, 0x3f, 0xf8, 0x01, 0x80, 0x1f, |
|||
0xf8, 0x03, 0xc0, 0x1f, 0xf0, 0x07, 0xe0, 0x0f, 0xf0, 0x1f, 0xf8, 0x0f, |
|||
0xe0, 0xff, 0xff, 0x07, 0xc0, 0xff, 0xff, 0x03, 0x00, 0xff, 0xff, 0x00, |
|||
0x00, 0xfc, 0x3f, 0x00, 0x00, 0xf0, 0x0f, 0x00 }; |
|||
|
|||
|
|||
|
|||
|
|||
// 'advert', 32x32px, horizontal
|
|||
static const uint8_t advert_icon[] = { |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x0c, |
|||
0x38, 0x00, 0x00, 0x1c, 0x18, 0x00, 0x00, 0x18, 0x0c, 0x00, 0x00, 0x30, |
|||
0x0c, 0x06, 0x60, 0x30, 0x06, 0x07, 0xe0, 0x60, 0x86, 0x03, 0xc0, 0x61, |
|||
0x87, 0x81, 0x81, 0xe1, 0xc3, 0xe0, 0x07, 0xc3, 0xc3, 0xf0, 0x0f, 0xc3, |
|||
0xc3, 0xf0, 0x0f, 0xc3, 0xc3, 0xf0, 0x0f, 0xc3, 0xc3, 0xf0, 0x0f, 0xc3, |
|||
0xc3, 0xe0, 0x07, 0xc3, 0x83, 0xc1, 0x83, 0xc1, 0x86, 0x01, 0x80, 0x61, |
|||
0x06, 0x03, 0xc0, 0x60, 0x0e, 0x07, 0xe0, 0x70, 0x0c, 0x02, 0x40, 0x30, |
|||
0x1c, 0x00, 0x00, 0x38, 0x18, 0x00, 0x00, 0x18, 0x30, 0x00, 0x00, 0x0c, |
|||
0x20, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; |
|||
|
|||
|
|||
|
|||
// 'muted, 8x8px, horizontal
|
|||
static const uint8_t muted_icon[] = { |
|||
0x20, 0x6a, 0xea, 0xe4, 0xe4, 0xea, 0x6a, 0x20 }; |
|||
Loading…
Reference in new issue