diff --git a/.vscode/settings.json b/.vscode/settings.json index aebb3d4a..3aca0452 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -59,6 +59,7 @@ "cfsr", "descs", "ocrypto", - "protobufs" + "protobufs", + "wifi" ] } \ No newline at end of file diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 47943553..1e42fa30 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -2,12 +2,12 @@ You probably don't care about this section - skip to the next one. -- bluetooth toggle enable stress test, we are not properly restarting our connect -- make new android release -- check in our modified arduino binaries -- post bug on esp32-arduino -- implement first cut of router mode: preferentially handle flooding, and change sleep and GPS behaviors +- test BLE software update again +- @feh123 Sony Xperia Z1 C6903 running Android 5.1.1 +- first message sent is still doubled for some people +- Android frontend should refetch the android messages from backend service on Resume - let users set arbitrary params in android +- implement first cut of router mode: preferentially handle flooding, and change sleep and GPS behaviors - NRF52 BLE support # Medium priority @@ -58,6 +58,13 @@ Items after the first final candidate release. - report esp32 device code bugs back to the mothership via android - change BLE bonding to something more secure. see comment by pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND) +Changes related to wifi support on ESP32: + +- iram space: https://esp32.com/viewtopic.php?t=8460 +- set https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/external-ram.html spi ram bss +- figure out if iram or bluetooth classic caused ble problems +- post bug on esp32-arduino with BLE bug findings + # Spinoff project ideas - an open source version of https://www.burnair.ch/skynet/ diff --git a/docs/software/esp32-arduino-build-notes.md b/docs/software/esp32-arduino-build-notes.md index 45fb7a54..b9fd6850 100644 --- a/docs/software/esp32-arduino-build-notes.md +++ b/docs/software/esp32-arduino-build-notes.md @@ -2,7 +2,7 @@ We build our own custom version of esp32-arduino, in order to get some fixes we've made but haven't yet been merged in master. -These are a set of currently unformatted notes on how to build and install them. Most developers should not care about this, because +These are a set of currently unformatted notes on how to build and install them. Most developers should not care about this, because you'll automatically get our fixed libraries. ``` @@ -12,5 +12,5 @@ you'll automatically get our fixed libraries. https://docs.espressif.com/projects/esp-idf/en/release-v3.3/get-started/linux-setup.html kevinh@kevin-server:~/development/meshtastic/esp32-arduino-lib-builder\$ python /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/esp-idf/components/esptool*py/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dout --flash_freq 40m --flash_size detect 0x1000 /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/build/bootloader/bootloader.bin cp -a out/tools/sdk/* components/arduino/tools/sdk - cp -ar components/arduino/ ~/.platformio/packages/framework-arduinoespressif32@src-fba9d33740f719f712e9f8b07da6ea13/ + cp -ar components/arduino/* ~/.platformio/packages/framework-arduinoespressif32@src-fba9d33740f719f712e9f8b07da6ea13/ ``` diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index ecad282a..2a2e4dd2 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -19,17 +19,18 @@ reliable messaging tasks (stage one for DSR): - DONE once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender - DONE after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) - DONE test one hop ack/nak with the python framework -- Do stress test with acks +- DONE Do stress test with acks dsr tasks -- oops I might have broken message reception +- DONE oops I might have broken message reception - DONE Don't use broadcasts for the network pings (close open github issue) - DONE add ignoreSenders to radioconfig to allow testing different mesh topologies by refusing to see certain senders -- test multihop delivery with the python framework +- DONE test multihop delivery with the python framework optimizations / low priority: +- read this [this](http://pages.cs.wisc.edu/~suman/pubs/nadv-mobihoc05.pdf) paper and others and make our naive flood routing less naive - read @cyclomies long email with good ideas on optimizations and reply - Remove NodeNum assignment algorithm (now that we use 4 byte node nums) - make android app warn if firmware is too old or too new to talk to diff --git a/linker/esp32.extram.bss.ld b/linker/esp32.extram.bss.ld new file mode 100644 index 00000000..582f6eb6 --- /dev/null +++ b/linker/esp32.extram.bss.ld @@ -0,0 +1,18 @@ +/* This section is only included if CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY + is set, to link some sections to BSS in PSRAM */ + +SECTIONS +{ + /* external memory bss, from any global variable with EXT_RAM_ATTR attribute*/ + .ext_ram.bss (NOLOAD) : + { + _ext_ram_bss_start = ABSOLUTE(.); + *(.ext_ram.bss*) + *libnet80211.a:(.dynsbss .sbss .sbss.* .gnu.linkonce.sb.* .scommon .sbss2.* .gnu.linkonce.sb2.* .dynbss .bss .bss.* .share.mem .gnu.linkonce.b.* COMMON) + *libpp.a:(.dynsbss .sbss .sbss.* .gnu.linkonce.sb.* .scommon .sbss2.* .gnu.linkonce.sb2.* .dynbss .bss .bss.* .share.mem .gnu.linkonce.b.* COMMON) + *liblwip.a:(.dynsbss .sbss .sbss.* .gnu.linkonce.sb.* .scommon .sbss2.* .gnu.linkonce.sb2.* .dynbss .bss .bss.* .share.mem .gnu.linkonce.b.* COMMON) + *libbt.a:(EXCLUDE_FILE (libbtdm_app.a) .dynsbss .sbss .sbss.* .gnu.linkonce.sb.* .scommon .sbss2.* .gnu.linkonce.sb2.* .dynbss .bss .bss.* .share.mem .gnu.linkonce.b.* COMMON) + . = ALIGN(4); + _ext_ram_bss_end = ABSOLUTE(.); + } > extern_ram_seg +} diff --git a/platformio.ini b/platformio.ini index 298032ce..ab23d99d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -84,10 +84,12 @@ src_filter = upload_speed = 921600 debug_init_break = tbreak setup build_flags = - ${env.build_flags} -Wall -Wextra -Isrc/esp32 + ${env.build_flags} -Wall -Wextra -Isrc/esp32 -mfix-esp32-psram-cache-issue +# Hmm - this doesn't work yet +# board_build.ldscript = linker/esp32.extram.bss.ld lib_ignore = segger_rtt platform_packages = - framework-arduinoespressif32 @ https://github.com/meshtastic/arduino-esp32.git + framework-arduinoespressif32 @ https://github.com/meshtastic/arduino-esp32.git#f26c4f96fefd13ed0ed042e27954f8aba6328f6b ; The 1.0 release of the TBEAM board [env:tbeam] diff --git a/proto b/proto index 72cbde93..fc79db45 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 72cbde93ffbc2ee917f9d7328558475e02a91cba +Subproject commit fc79db45944959ee3246f6f808db18e4debab72b diff --git a/src/esp32/BluetoothUtil.cpp b/src/esp32/BluetoothUtil.cpp index 8d6b5d7d..224b8c0a 100644 --- a/src/esp32/BluetoothUtil.cpp +++ b/src/esp32/BluetoothUtil.cpp @@ -122,7 +122,7 @@ BLEService *createBatteryService(BLEServer *server) addWithDesc(pBattery, batteryLevelC, "Percentage 0 - 100"); batteryLevelC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification - // I don't think we need to advertise this + // I don't think we need to advertise this? and some phones only see the first thing advertised anyways... // server->getAdvertising()->addServiceUUID(pBattery->getUUID()); pBattery->start(); @@ -135,8 +135,8 @@ BLEService *createBatteryService(BLEServer *server) */ void updateBatteryLevel(uint8_t level) { - // Pretend to update battery levels - fixme do elsewhere if (batteryLevelC) { + DEBUG_MSG("set BLE battery level %u\n", level); batteryLevelC->setValue(&level, 1); batteryLevelC->notify(); } @@ -215,7 +215,7 @@ class MySecurity : public BLESecurityCallbacks BLEServer *pServer; -BLEService *pDevInfo, *pUpdate; +BLEService *pDevInfo, *pUpdate, *pBattery; void deinitBLE() { @@ -230,6 +230,9 @@ void deinitBLE() pUpdate->executeDelete(); } + pBattery->stop(); + pBattery->executeDelete(); + pDevInfo->stop(); pDevInfo->executeDelete(); @@ -242,6 +245,7 @@ void deinitBLE() if (pUpdate != NULL) delete pUpdate; delete pDevInfo; + delete pBattery; delete pServer; batteryLevelC = NULL; // Don't let anyone generate bogus notifies @@ -279,10 +283,9 @@ BLEServer *initBLE(StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoo pDevInfo = createDeviceInfomationService(pServer, hwVendor, swVersion, hwVersion); - // We now let users create the battery service only if they really want (not all devices have a battery) - // BLEService *pBattery = createBatteryService(pServer); + pBattery = createBatteryService(pServer); -// #define BLE_SOFTWARE_UPDATE +#define BLE_SOFTWARE_UPDATE #ifdef BLE_SOFTWARE_UPDATE pUpdate = createUpdateService(pServer, hwVendor, swVersion, hwVersion); // We need to advertise this so our android ble scan operation can see it diff --git a/src/esp32/BluetoothUtil.h b/src/esp32/BluetoothUtil.h index b1aa77db..67797a0c 100644 --- a/src/esp32/BluetoothUtil.h +++ b/src/esp32/BluetoothUtil.h @@ -35,5 +35,8 @@ BLECharacteristic *addBLECharacteristic(BLECharacteristic *c); /// Add a characteristic that we will delete when we restart BLEDescriptor *addBLEDescriptor(BLEDescriptor *c); +/// Given a level between 0-100, update the BLE attribute +void updateBatteryLevel(uint8_t level); + /// Any bluetooth objects you allocate _must_ come from this pool if you want to be able to call deinitBLE() extern SimpleAllocator btPool; diff --git a/src/gps/UBloxGPS.cpp b/src/gps/UBloxGPS.cpp index 8427e68b..01221129 100644 --- a/src/gps/UBloxGPS.cpp +++ b/src/gps/UBloxGPS.cpp @@ -99,12 +99,12 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). */ struct tm t; - t.tm_sec = ublox.getSecond(); - t.tm_min = ublox.getMinute(); - t.tm_hour = ublox.getHour(); - t.tm_mday = ublox.getDay(); - t.tm_mon = ublox.getMonth() - 1; - t.tm_year = ublox.getYear() - 1900; + t.tm_sec = ublox.getSecond(0); + t.tm_min = ublox.getMinute(0); + t.tm_hour = ublox.getHour(0); + t.tm_mday = ublox.getDay(0); + t.tm_mon = ublox.getMonth(0) - 1; + t.tm_year = ublox.getYear(0) - 1900; t.tm_isdst = false; perhapsSetRTC(t); } @@ -112,10 +112,10 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s if ((fixtype >= 3 && fixtype <= 4) && ublox.getP(0)) // rd fixes only { // we only notify if position has changed - latitude = ublox.getLatitude(); - longitude = ublox.getLongitude(); - altitude = ublox.getAltitude() / 1000; // in mm convert to meters - dop = ublox.getPDOP(); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it + latitude = ublox.getLatitude(0); + longitude = ublox.getLongitude(0); + altitude = ublox.getAltitude(0) / 1000; // in mm convert to meters + dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d, pdop=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2); // bogus lat lon is reported as 0 or 0 (can be bogus just for one) diff --git a/src/main.cpp b/src/main.cpp index 78a16245..5a501a46 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,6 +42,7 @@ #ifndef NO_ESP32 #include "BluetoothUtil.h" +#include "WiFi.h" #endif #include "RF95Interface.h" @@ -124,7 +125,28 @@ static uint32_t ledBlinker() Periodic ledPeriodic(ledBlinker); +#ifndef NO_ESP32 +void initWifi() +{ + strcpy(radioConfig.preferences.wifi_ssid, "geeksville"); + strcpy(radioConfig.preferences.wifi_password, "xxx"); + if (radioConfig.has_preferences) { + const char *wifiName = radioConfig.preferences.wifi_ssid; + if (*wifiName) { + const char *wifiPsw = radioConfig.preferences.wifi_password; + if (radioConfig.preferences.wifi_ap_mode) { + // DEBUG_MSG("STARTING WIFI AP: ssid=%s, ok=%d\n", wifiName, WiFi.softAP(wifiName, wifiPsw)); + } else { + // WiFi.mode(WIFI_MODE_STA); + DEBUG_MSG("JOINING WIFI: ssid=%s\n", wifiName); + // WiFi.begin(wifiName, wifiPsw); + } + } + } else + DEBUG_MSG("Not using WIFI\n"); +} +#endif void setup() { @@ -213,6 +235,11 @@ void setup() service.init(); +#ifndef NO_ESP32 + // Must be after we init the service, because the wifi settings are loaded by NodeDB (oops) + initWifi(); +#endif + #ifdef SX1262_ANT_SW // make analog PA vs not PA switch on SX1262 eval board work properly pinMode(SX1262_ANT_SW, OUTPUT); diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index eebfb227..70e20f80 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -11,6 +11,8 @@ #include "PowerFSM.h" #include "main.h" #include "mesh-pb-constants.h" +#include "power.h" +#include "BluetoothUtil.h" // needed for updateBatteryLevel, FIXME, eventually when we pull mesh out into a lib we shouldn't be whacking bluetooth from here /* receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone. @@ -280,6 +282,8 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) sendToMesh(p); } + + int MeshService::onGPSChanged(void *unused) { // DEBUG_MSG("got gps notify\n"); @@ -298,6 +302,10 @@ int MeshService::onGPSChanged(void *unused) pos.time = getValidTime(); } + // Include our current battery voltage in our position announcement + pos.battery_level = powerStatus.batteryChargePercent; + updateBatteryLevel(pos.battery_level); + // We limit our GPS broadcasts to a max rate static uint32_t lastGpsSend; uint32_t now = millis(); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 9bebda11..2a202f23 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -19,7 +19,7 @@ NodeDB nodeDB; // we have plenty of ram so statically alloc this tempbuf (for now) -DeviceState devicestate; +EXT_RAM_ATTR DeviceState devicestate; MyNodeInfo &myNodeInfo = devicestate.my_node; RadioConfig &radioConfig = devicestate.radio; ChannelSettings &channelSettings = radioConfig.channel_settings; diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index 2651d6cb..808ffab8 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -101,8 +101,9 @@ typedef struct _RadioConfig_UserPreferences { uint32_t sds_secs; uint32_t ls_secs; uint32_t min_wake_secs; - bool keep_all_packets; - bool promiscuous_mode; + char wifi_ssid[33]; + char wifi_password[64]; + bool wifi_ap_mode; pb_size_t ignore_incoming_count; uint32_t ignore_incoming[3]; } RadioConfig_UserPreferences; @@ -241,7 +242,7 @@ typedef struct _ToRadio { #define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0} #define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} -#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}} +#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, 0, {0, 0, 0}} #define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0} #define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0} #define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default}, false, MeshPacket_init_default, 0} @@ -257,7 +258,7 @@ typedef struct _ToRadio { #define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0} #define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} -#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}} +#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, 0, {0, 0, 0}} #define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0} #define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0} #define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero}, false, MeshPacket_init_zero, 0} @@ -308,8 +309,9 @@ typedef struct _ToRadio { #define RadioConfig_UserPreferences_sds_secs_tag 9 #define RadioConfig_UserPreferences_ls_secs_tag 10 #define RadioConfig_UserPreferences_min_wake_secs_tag 11 -#define RadioConfig_UserPreferences_keep_all_packets_tag 100 -#define RadioConfig_UserPreferences_promiscuous_mode_tag 101 +#define RadioConfig_UserPreferences_wifi_ssid_tag 12 +#define RadioConfig_UserPreferences_wifi_password_tag 13 +#define RadioConfig_UserPreferences_wifi_ap_mode_tag 14 #define RadioConfig_UserPreferences_ignore_incoming_tag 102 #define RouteDiscovery_route_tag 2 #define User_id_tag 1 @@ -456,8 +458,9 @@ X(a, STATIC, SINGULAR, UINT32, mesh_sds_timeout_secs, 8) \ X(a, STATIC, SINGULAR, UINT32, sds_secs, 9) \ X(a, STATIC, SINGULAR, UINT32, ls_secs, 10) \ X(a, STATIC, SINGULAR, UINT32, min_wake_secs, 11) \ -X(a, STATIC, SINGULAR, BOOL, keep_all_packets, 100) \ -X(a, STATIC, SINGULAR, BOOL, promiscuous_mode, 101) \ +X(a, STATIC, SINGULAR, STRING, wifi_ssid, 12) \ +X(a, STATIC, SINGULAR, STRING, wifi_password, 13) \ +X(a, STATIC, SINGULAR, BOOL, wifi_ap_mode, 14) \ X(a, STATIC, REPEATED, UINT32, ignore_incoming, 102) #define RadioConfig_UserPreferences_CALLBACK NULL #define RadioConfig_UserPreferences_DEFAULT NULL @@ -592,11 +595,11 @@ extern const pb_msgdesc_t ManufacturingData_msg; #define SubPacket_size 274 #define MeshPacket_size 313 #define ChannelSettings_size 60 -#define RadioConfig_size 157 -#define RadioConfig_UserPreferences_size 93 +#define RadioConfig_size 253 +#define RadioConfig_UserPreferences_size 188 #define NodeInfo_size 132 #define MyNodeInfo_size 110 -#define DeviceState_size 5305 +#define DeviceState_size 5401 #define DebugString_size 258 #define FromRadio_size 322 #define ToRadio_size 316 diff --git a/src/screen.cpp b/src/screen.cpp index 9085dc0e..9d43eda8 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -52,7 +52,7 @@ namespace meshtastic // A text message frame + debug frame + all the node infos static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES]; static uint32_t targetFramerate = IDLE_FRAMERATE; -static char btPIN[16] = "888888"; +static char btPIN[16] = "888888"; uint8_t imgBattery[16] = { 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C }; @@ -66,6 +66,14 @@ static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int1 display->setFont(ArialMT_Plain_16); display->setTextAlignment(TEXT_ALIGN_CENTER); display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org"); + display->setFont(ArialMT_Plain_10); + const char *region = xstr(HW_VERSION); + if(*region && region[3] == '-') // Skip past 1.0- in the 1.0-EU865 string + region += 4; + char buf[16]; + snprintf(buf, sizeof(buf), "%s", xstr(APP_VERSION)); // Note: we don't bother printing region or now, it makes the string too long + display->drawString(SCREEN_WIDTH - 20, 0, buf); + } static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)