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