diff --git a/bin/install-eink.sh b/bin/install-eink.sh new file mode 100755 index 000000000..5bfb6f1a7 --- /dev/null +++ b/bin/install-eink.sh @@ -0,0 +1,22 @@ +# You probably don't want to use this script, it programs a custom bootloader build onto a nrf52 board + +set -e + +BOOTDIR=/home/kevinh/development/meshtastic/Adafruit_nRF52_Bootloader + +nrfjprog --eraseall -f nrf52 + +# this generates an intel hex file that can be programmed into a NRF52 to tell the adafruit bootloader that the current app image is valid +# Bootloader settings are at BOOTLOADER_SETTINGS (rw) : ORIGIN = 0xFF000, LENGTH = 0x1000 +# first 4 bytes should be 0x01 to indicate valid app image +# second 4 bytes should be 0x00 to indicate no CRC required for image +echo "01 00 00 00 00 00 00 00" | xxd -r -p - >/tmp/bootconf.bin +srec_cat /tmp/bootconf.bin -binary -offset 0xff000 -output /tmp/bootconf.hex -intel + +echo Generating merged hex file +mergehex -m $BOOTDIR/_build/build-ttgo_eink/ttgo_eink_bootloader-0.3.2-125-gf38f8f4-dirty_s140_6.1.1.hex .pio/build/eink/firmware.hex /tmp/bootconf.hex -o ttgo_eink_full.hex + +echo Telling bootloader app region is valid and telling CPU to run +nrfjprog --program ttgo_eink_full.hex -f nrf52 --reset + +# nrfjprog --readuicr /tmp/uicr.hex; objdump -s /tmp/uicr.hex | less diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 46d320346..257862c3b 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -7,6 +7,10 @@ TODO: +- enter SDS state at correct time (to protect battery or loss of phone contact) +- show screen on eink when we enter SDS state (with app info and say sleeping) +- require button press to pair + - shrink soft device RAM usage - get nrf52832 working again (currently OOM) - i2c gps comms not quite right diff --git a/platformio.ini b/platformio.ini index 3b7c6abb7..507f1a693 100644 --- a/platformio.ini +++ b/platformio.ini @@ -120,7 +120,7 @@ board_build.partitions = partition-table.csv extends = esp32_base board = ttgo-t-beam lib_deps = - ${arduino_base.lib_deps} + ${esp32_base.lib_deps} build_flags = ${esp32_base.build_flags} -D TBEAM_V10 diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 6295b9dd4..ccc2f54ac 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -245,10 +245,16 @@ void PowerFSM_setup() powerFSM.add_timed_transition(&stateON, &stateDARK, getPref_screen_on_secs() * 1000, NULL, "Screen-on timeout"); - powerFSM.add_timed_transition(&stateDARK, &stateNB, getPref_phone_timeout_secs() * 1000, NULL, "Phone timeout"); + // On most boards we use light-sleep to be our main state, but on NRF52 we just stay in DARK + State *lowPowerState = &stateLS; #ifndef NRF52_SERIES - // We never enter light-sleep state on NRF52 (because the CPU uses so little power normally) + // We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally) + + lowPowerState = &stateDARK; + + powerFSM.add_timed_transition(&stateDARK, &stateNB, getPref_phone_timeout_secs() * 1000, NULL, "Phone timeout"); + powerFSM.add_timed_transition(&stateNB, &stateLS, getPref_min_wake_secs() * 1000, NULL, "Min wake timeout"); powerFSM.add_timed_transition(&stateDARK, &stateLS, getPref_wait_bluetooth_secs() * 1000, NULL, "Bluetooth timeout"); @@ -256,9 +262,9 @@ void PowerFSM_setup() auto meshSds = getPref_mesh_sds_timeout_secs(); if (meshSds != UINT32_MAX) - powerFSM.add_timed_transition(&stateLS, &stateSDS, meshSds * 1000, NULL, "mesh timeout"); + powerFSM.add_timed_transition(lowPowerState, &stateSDS, meshSds * 1000, NULL, "mesh timeout"); // removing for now, because some users don't even have phones - // powerFSM.add_timed_transition(&stateLS, &stateSDS, getPref_phone_sds_timeout_sec() * 1000, NULL, "phone + // powerFSM.add_timed_transition(lowPowerState, &stateSDS, getPref_phone_sds_timeout_sec() * 1000, NULL, "phone // timeout"); powerFSM.run_machine(); // run one interation of the state machine, so we run our on enter tasks for the initial DARK state diff --git a/src/gps/NMEAGPS.cpp b/src/gps/NMEAGPS.cpp index d4d9639df..8db155a32 100644 --- a/src/gps/NMEAGPS.cpp +++ b/src/gps/NMEAGPS.cpp @@ -1,6 +1,6 @@ #include "NMEAGPS.h" -#include "configuration.h" #include "RTC.h" +#include "configuration.h" static int32_t toDegInt(RawDegrees d) { @@ -32,7 +32,7 @@ bool NMEAGPS::lookForTime() { auto ti = reader.time; auto d = reader.date; - if (ti.isUpdated() && ti.isValid() && d.isValid()) { + if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed /* Convert to unix time The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). @@ -65,27 +65,27 @@ bool NMEAGPS::lookForLocation() // uint8_t fixtype = reader.fixQuality(); // hasValidLocation = ((fixtype >= 1) && (fixtype <= 5)); + if (reader.satellites.isUpdated()) { + setNumSatellites(reader.satellites.value()); + } + + // Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it + if (reader.hdop.isUpdated()) { + dop = reader.hdop.value(); + } + if (reader.course.isUpdated()) { + heading = reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5 + } + + if (reader.altitude.isUpdated()) + altitude = reader.altitude.meters(); + if (reader.location.isUpdated()) { - if (reader.altitude.isValid()) - altitude = reader.altitude.meters(); - if (reader.location.isValid()) { - auto loc = reader.location.value(); - latitude = toDegInt(loc.lat); - longitude = toDegInt(loc.lng); - foundLocation = true; - } - - // Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it - if (reader.hdop.isValid()) { - dop = reader.hdop.value(); - } - if (reader.course.isValid()) { - heading = reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5 - } - if (reader.satellites.isValid()) { - setNumSatellites(reader.satellites.value()); - } + auto loc = reader.location.value(); + latitude = toDegInt(loc.lat); + longitude = toDegInt(loc.lng); + foundLocation = true; // expect gps pos lat=37.520825, lon=-122.309162, alt=158 DEBUG_MSG("new NMEA GPS pos lat=%f, lon=%f, alt=%d, hdop=%g, heading=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, @@ -102,7 +102,7 @@ bool NMEAGPS::whileIdle() // First consume any chars that have piled up at the receiver while (_serial_gps->available() > 0) { int c = _serial_gps->read(); - // DEBUG_MSG("%c", c); + //DEBUG_MSG("%c", c); isValid |= reader.encode(c); } diff --git a/src/graphics/EInkDisplay.cpp b/src/graphics/EInkDisplay.cpp index 7a6e02df0..0d5e8307f 100644 --- a/src/graphics/EInkDisplay.cpp +++ b/src/graphics/EInkDisplay.cpp @@ -46,15 +46,18 @@ void updateDisplay(uint8_t *blackFrame = framePtr) EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl) { - setGeometry(GEOMETRY_128_64); // FIXME - currently we lie and claim 128x64 because I'm not yet sure other resolutions will - // work ie GEOMETRY_RAWMODE + setGeometry(GEOMETRY_RAWMODE, EPD_WIDTH, EPD_HEIGHT); + // setGeometry(GEOMETRY_RAWMODE, 128, 64); // old resolution + // setGeometry(GEOMETRY_128_64); // We originally used this because I wasn't sure if rawmode worked - it does } // FIXME quick hack to limit drawing to a very slow rate uint32_t lastDrawMsec; -// Write the buffer to the display memory -void EInkDisplay::display(void) +/** + * Force a display update if we haven't drawn within the specified msecLimit + */ +bool EInkDisplay::forceDisplay(uint32_t msecLimit) { // No need to grab this lock because we are on our own SPI bus // concurrency::LockGuard g(spiLock); @@ -62,16 +65,16 @@ void EInkDisplay::display(void) uint32_t now = millis(); uint32_t sinceLast = now - lastDrawMsec; - if (framePtr && (sinceLast > 60 * 1000 || lastDrawMsec == 0)) { + if (framePtr && (sinceLast > msecLimit || lastDrawMsec == 0)) { lastDrawMsec = now; // FIXME - only draw bits have changed (use backbuf similar to the other displays) // tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK); - for (uint8_t y = 0; y < SCREEN_HEIGHT; y++) { - for (uint8_t x = 0; x < SCREEN_WIDTH; x++) { + for (uint8_t y = 0; y < displayHeight; y++) { + for (uint8_t x = 0; x < displayWidth; x++) { // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent - auto b = buffer[x + (y / 8) * SCREEN_WIDTH]; + auto b = buffer[x + (y / 8) * displayWidth]; auto isset = b & (1 << (y & 7)); frame.drawPixel(x, y, isset ? INK : PAPER); } @@ -83,11 +86,25 @@ void EInkDisplay::display(void) updateDisplay(); // Send image to display and refresh DEBUG_MSG("done\n"); - // Put screen to sleep to save power + // Put screen to sleep to save power ePaper.Sleep(); + return true; + } else { + // DEBUG_MSG("Skipping eink display\n"); + return false; } } +// Write the buffer to the display memory +void EInkDisplay::display(void) +{ + // We don't allow regular 'dumb' display() calls to draw on eink until we've shown + // at least one forceDisplay() keyframe. This prevents flashing when we should the critical + // bootscreen (that we want to look nice) + if (lastDrawMsec) + forceDisplay(slowUpdateMsec); // Show the first screen a few seconds after boot, then slower +} + // Send a command to the display (low level function) void EInkDisplay::sendCommand(uint8_t com) { diff --git a/src/graphics/EInkDisplay.h b/src/graphics/EInkDisplay.h index f12ad6882..18b900305 100644 --- a/src/graphics/EInkDisplay.h +++ b/src/graphics/EInkDisplay.h @@ -14,15 +14,26 @@ */ class EInkDisplay : public OLEDDisplay { + /// How often should we update the display + /// thereafter we do once per 5 minutes + uint32_t slowUpdateMsec = 5 * 60 * 1000; + public: /* constructor FIXME - the parameters are not used, just a temporary hack to keep working like the old displays */ EInkDisplay(uint8_t address, int sda, int scl); - // Write the buffer to the display memory + // Write the buffer to the display memory (for eink we only do this occasionally) virtual void display(void); + /** + * Force a display update if we haven't drawn within the specified msecLimit + * + * @return true if we did draw the screen + */ + bool forceDisplay(uint32_t msecLimit = 1000); + protected: // the header size of the buffer used, e.g. for the SPI command header virtual int getBufferOffset(void) { return 0; } @@ -33,3 +44,5 @@ class EInkDisplay : public OLEDDisplay // Connect to the display virtual bool connect(); }; + + diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index b7dc55313..a97e04b9e 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -58,17 +58,48 @@ static char ourId[5]; static bool heartbeat = false; #endif +// We used to use constants for this - now we pull from the device at startup +//#define SCREEN_WIDTH 128 +//#define SCREEN_HEIGHT 64 + +static uint16_t displayWidth, displayHeight; + +#define SCREEN_WIDTH displayWidth +#define SCREEN_HEIGHT displayHeight + +#ifdef HAS_EINK +// The screen is bigger so use bigger fonts +#define FONT_SMALL ArialMT_Plain_16 +#define FONT_MEDIUM ArialMT_Plain_24 +#define FONT_LARGE ArialMT_Plain_24 +#else +#define FONT_SMALL ArialMT_Plain_10 +#define FONT_MEDIUM ArialMT_Plain_16 +#define FONT_LARGE ArialMT_Plain_24 +#endif + +#define fontHeight(font) ((font)[1] + 1) // height is position 1 + +#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) +#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) + +#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2) + static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { // draw an xbm image. // Please note that everything that should be transitioned // needs to be drawn relative to x and y - display->drawXbm(x + 32, y, icon_width, icon_height, (const uint8_t *)icon_bits); - 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); + // draw centered left to right and centered above the one line of app text + display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2, icon_width, + icon_height, (const uint8_t *)icon_bits); + + display->setFont(FONT_MEDIUM); + display->setTextAlignment(TEXT_ALIGN_LEFT); + const char *title = "meshtastic.org"; + display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); + display->setFont(FONT_SMALL); const char *region = xstr(HW_VERSION); if (*region && region[3] == '-') // Skip past 1.0- in the 1.0-EU865 string region += 4; @@ -76,21 +107,24 @@ static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int1 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); + screen->forceDisplay(); } + + static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(ArialMT_Plain_16); + display->setFont(FONT_MEDIUM); display->drawString(64 + x, y, "Bluetooth"); - display->setFont(ArialMT_Plain_10); - display->drawString(64 + x, FONT_HEIGHT + y + 2, "Enter this code"); + display->setFont(FONT_SMALL); + display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Enter this code"); - display->setFont(ArialMT_Plain_24); + display->setFont(FONT_LARGE); display->drawString(64 + x, 26 + y, btPIN); - display->setFont(ArialMT_Plain_10); + display->setFont(FONT_SMALL); char buf[30]; const char *name = "Name: "; strcpy(buf, name); @@ -112,10 +146,10 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state // with the third parameter you can define the width after which words will // be wrapped. Currently only spaces and "-" are allowed for wrapping display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(ArialMT_Plain_16); + display->setFont(FONT_MEDIUM); String sender = (node && node->has_user) ? node->user.short_name : "???"; display->drawString(0 + x, 0 + y, sender); - display->setFont(ArialMT_Plain_10); + display->setFont(FONT_SMALL); // the max length of this buffer is much longer than we can possibly print static char tempBuf[96]; @@ -135,8 +169,8 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char * int xo = x, yo = y; while (*f) { display->drawString(xo, yo, *f); - yo += FONT_HEIGHT; - if (yo > SCREEN_HEIGHT - FONT_HEIGHT) { + yo += FONT_HEIGHT_SMALL; + if (yo > SCREEN_HEIGHT - FONT_HEIGHT_SMALL) { xo += SCREEN_WIDTH / 2; yo = 0; } @@ -162,14 +196,14 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char * // Wrap to next row, if needed. if (++col >= COLUMNS) { xo = x; - yo += FONT_HEIGHT; + yo += FONT_HEIGHT_SMALL; col = 0; } f++; } if (col != 0) { // Include last incomplete line in our total. - yo += FONT_HEIGHT; + yo += FONT_HEIGHT_SMALL; } return yo; @@ -236,7 +270,7 @@ static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus display->drawFastImage(x + 24, y, 8, 8, imgSatellite); // Draw the number of satellites - sprintf(satsString, "%d", gps->getNumSatellites()); + sprintf(satsString, "%lu", gps->getNumSatellites()); display->drawString(x + 34, y - 2, satsString); } } @@ -476,7 +510,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ NodeInfo *node = nodeDB.getNodeByIndex(nodeIndex); - display->setFont(ArialMT_Plain_10); + display->setFont(FONT_SMALL); // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -489,11 +523,11 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ uint32_t agoSecs = sinceLastSeen(node); static char lastStr[20]; if (agoSecs < 120) // last 2 mins? - snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs); + snprintf(lastStr, sizeof(lastStr), "%lu seconds ago", agoSecs); else if (agoSecs < 120 * 60) // last 2 hrs - snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60); + snprintf(lastStr, sizeof(lastStr), "%lu minutes ago", agoSecs / 60); else - snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60); + snprintf(lastStr, sizeof(lastStr), "%lu hours ago", agoSecs / 60 / 60); static char distStr[20]; strcpy(distStr, "? km"); // might not have location data @@ -531,7 +565,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ // direction to node is unknown so display question mark // Debug info for gps lock errors // DEBUG_MSG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasPosition(ourNode), hasPosition(node)); - display->drawString(compassX - FONT_HEIGHT / 4, compassY - FONT_HEIGHT / 2, "?"); + display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); display->drawCircle(compassX, compassY, COMPASS_DIAM / 2); // Must be after distStr is populated @@ -561,7 +595,8 @@ void _screen_header() } #endif -Screen::Screen(uint8_t address, int sda, int scl) : OSThread("Screen"), cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev) { +Screen::Screen(uint8_t address, int sda, int scl) : OSThread("Screen"), cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev) +{ cmdQueue.setReader(this); } @@ -596,6 +631,10 @@ void Screen::setup() // Initialising the UI will init the display too. ui.init(); + + displayWidth = dispdev.width(); + displayHeight = dispdev.height(); + ui.setTimePerTransition(300); // msecs ui.setIndicatorPosition(BOTTOM); // Defines where the first frame is located in the bar. @@ -645,6 +684,14 @@ void Screen::setup() nodeStatusObserver.observe(&nodeStatus->onNewStatus); } +void Screen::forceDisplay() +{ + // Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup. +#ifdef HAS_EINK + dispdev.forceDisplay(); +#endif +} + int32_t Screen::runOnce() { // If we don't have a screen, don't ever spend any CPU for us. @@ -655,7 +702,8 @@ int32_t Screen::runOnce() // Show boot screen for first 3 seconds, then switch to normal operation. static bool showingBootScreen = true; - if (showingBootScreen && (millis() > 3000)) { + if (showingBootScreen && (millis() > 5000)) { + DEBUG_MSG("Done with boot screen...\n"); stopBootScreen(); showingBootScreen = false; } @@ -701,6 +749,10 @@ int32_t Screen::runOnce() return 0; } + // this must be before the frameState == FIXED check, because we always + // want to draw at least one FIXED frame before doing forceDisplay + ui.update(); + // Switch to a low framerate (to save CPU) when we are not in transition // but we should only call setTargetFPS when framestate changes, because // otherwise that breaks animations. @@ -709,6 +761,7 @@ int32_t Screen::runOnce() DEBUG_MSG("Setting idle framerate\n"); targetFramerate = IDLE_FRAMERATE; ui.setTargetFPS(targetFramerate); + forceDisplay(); } // While showing the bootscreen or Bluetooth pair screen all of our @@ -717,8 +770,6 @@ int32_t Screen::runOnce() // standard screen loop handling here } - ui.update(); - // DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate, // ui.getUiState()->frameState); If we are scrolling we need to be called // soon, otherwise just 1 fps (to save CPU) We also ask to be called twice @@ -785,6 +836,8 @@ void Screen::setFrames() prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list // just changed) + + setFastFramerate(); // Draw ASAP } void Screen::handleStartBluetoothPinScreen(uint32_t pin) @@ -794,16 +847,17 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin) static FrameCallback btFrames[] = {drawFrameBluetooth}; - snprintf(btPIN, sizeof(btPIN), "%06u", pin); + snprintf(btPIN, sizeof(btPIN), "%06lu", pin); ui.disableAllIndicators(); ui.setFrames(btFrames, 1); + setFastFramerate(); } void Screen::handlePrint(const char *text) { DEBUG_MSG("Screen: %s", text); - if (!useDisplay) + if (!useDisplay || !showingNormalScreen) return; dispdev.print(text); @@ -814,22 +868,27 @@ void Screen::handleOnPress() // If screen was off, just wake it, otherwise advance to next frame // If we are in a transition, the press must have bounced, drop it. if (ui.getUiState()->frameState == FIXED) { - setInterval(0); // redraw ASAP ui.nextFrame(); - DEBUG_MSG("Setting fast framerate\n"); - - // We are about to start a transition so speed up fps - targetFramerate = TRANSITION_FRAMERATE; - ui.setTargetFPS(targetFramerate); + setFastFramerate(); } } +void Screen::setFastFramerate() +{ + DEBUG_MSG("Setting fast framerate\n"); + + // We are about to start a transition so speed up fps + targetFramerate = TRANSITION_FRAMERATE; + ui.setTargetFPS(targetFramerate); + setInterval(0); // redraw ASAP +} + void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { displayedNodeNum = 0; // Not currently showing a node pane - display->setFont(ArialMT_Plain_10); + display->setFont(FONT_SMALL); // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -851,13 +910,13 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus); // Draw the channel name - display->drawString(x, y + FONT_HEIGHT, channelStr); + display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr); // Draw our hardware ID to assist with bluetooth pairing - display->drawFastImage(x + SCREEN_WIDTH - (10) - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT, 8, 8, imgInfo); - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT, ourId); + display->drawFastImage(x + SCREEN_WIDTH - (10) - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgInfo); + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT_SMALL, ourId); // Draw any log messages - display->drawLogBuffer(x, y + (FONT_HEIGHT * 2)); + display->drawLogBuffer(x, y + (FONT_HEIGHT_SMALL * 2)); /* Display a heartbeat pixel that blinks every time the frame is redrawn */ #ifdef SHOW_REDRAWS @@ -876,7 +935,7 @@ void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, i displayedNodeNum = 0; // Not currently showing a node pane - display->setFont(ArialMT_Plain_10); + display->setFont(FONT_SMALL); // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -907,86 +966,86 @@ void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, i if (WiFi.status() == WL_CONNECTED) { if (radioConfig.preferences.wifi_ap_mode) { - display->drawString(x, y + FONT_HEIGHT * 1, "IP: " + String(WiFi.softAPIP().toString().c_str())); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.softAPIP().toString().c_str())); } else { - display->drawString(x, y + FONT_HEIGHT * 1, "IP: " + String(WiFi.localIP().toString().c_str())); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.localIP().toString().c_str())); } } else if (WiFi.status() == WL_NO_SSID_AVAIL) { - display->drawString(x, y + FONT_HEIGHT * 1, "SSID Not Found"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "SSID Not Found"); } else if (WiFi.status() == WL_CONNECTION_LOST) { - display->drawString(x, y + FONT_HEIGHT * 1, "Connection Lost"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Lost"); } else if (WiFi.status() == WL_CONNECT_FAILED) { - display->drawString(x, y + FONT_HEIGHT * 1, "Connection Failed"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed"); //} else if (WiFi.status() == WL_DISCONNECTED) { - // display->drawString(x, y + FONT_HEIGHT * 1, "Disconnected"); + // display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Disconnected"); } else if (WiFi.status() == WL_IDLE_STATUS) { - display->drawString(x, y + FONT_HEIGHT * 1, "Idle ... Reconnecting"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Idle ... Reconnecting"); } else { // Codes: // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code if (getWifiDisconnectReason() == 2) { - display->drawString(x, y + FONT_HEIGHT * 1, "Authentication Invalid"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Authentication Invalid"); } else if (getWifiDisconnectReason() == 3) { - display->drawString(x, y + FONT_HEIGHT * 1, "De-authenticated"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "De-authenticated"); } else if (getWifiDisconnectReason() == 4) { - display->drawString(x, y + FONT_HEIGHT * 1, "Disassociated Expired"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Disassociated Expired"); } else if (getWifiDisconnectReason() == 5) { - display->drawString(x, y + FONT_HEIGHT * 1, "AP - Too Many Clients"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AP - Too Many Clients"); } else if (getWifiDisconnectReason() == 6) { - display->drawString(x, y + FONT_HEIGHT * 1, "NOT_AUTHED"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "NOT_AUTHED"); } else if (getWifiDisconnectReason() == 7) { - display->drawString(x, y + FONT_HEIGHT * 1, "NOT_ASSOCED"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "NOT_ASSOCED"); } else if (getWifiDisconnectReason() == 8) { - display->drawString(x, y + FONT_HEIGHT * 1, "Disassociated"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Disassociated"); } else if (getWifiDisconnectReason() == 9) { - display->drawString(x, y + FONT_HEIGHT * 1, "ASSOC_NOT_AUTHED"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "ASSOC_NOT_AUTHED"); } else if (getWifiDisconnectReason() == 10) { - display->drawString(x, y + FONT_HEIGHT * 1, "DISASSOC_PWRCAP_BAD"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "DISASSOC_PWRCAP_BAD"); } else if (getWifiDisconnectReason() == 11) { - display->drawString(x, y + FONT_HEIGHT * 1, "DISASSOC_SUPCHAN_BAD"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "DISASSOC_SUPCHAN_BAD"); } else if (getWifiDisconnectReason() == 13) { - display->drawString(x, y + FONT_HEIGHT * 1, "IE_INVALID"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IE_INVALID"); } else if (getWifiDisconnectReason() == 14) { - display->drawString(x, y + FONT_HEIGHT * 1, "MIC_FAILURE"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "MIC_FAILURE"); } else if (getWifiDisconnectReason() == 15) { - display->drawString(x, y + FONT_HEIGHT * 1, "AP Handshake Timeout"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AP Handshake Timeout"); } else if (getWifiDisconnectReason() == 16) { - display->drawString(x, y + FONT_HEIGHT * 1, "GROUP_KEY_UPDATE_TIMEOUT"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "GROUP_KEY_UPDATE_TIMEOUT"); } else if (getWifiDisconnectReason() == 17) { - display->drawString(x, y + FONT_HEIGHT * 1, "IE_IN_4WAY_DIFFERS"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IE_IN_4WAY_DIFFERS"); } else if (getWifiDisconnectReason() == 18) { - display->drawString(x, y + FONT_HEIGHT * 1, "Invalid Group Cipher"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Invalid Group Cipher"); } else if (getWifiDisconnectReason() == 19) { - display->drawString(x, y + FONT_HEIGHT * 1, "Invalid Pairwise Cipher"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Invalid Pairwise Cipher"); } else if (getWifiDisconnectReason() == 20) { - display->drawString(x, y + FONT_HEIGHT * 1, "AKMP_INVALID"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AKMP_INVALID"); } else if (getWifiDisconnectReason() == 21) { - display->drawString(x, y + FONT_HEIGHT * 1, "UNSUPP_RSN_IE_VERSION"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "UNSUPP_RSN_IE_VERSION"); } else if (getWifiDisconnectReason() == 22) { - display->drawString(x, y + FONT_HEIGHT * 1, "INVALID_RSN_IE_CAP"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "INVALID_RSN_IE_CAP"); } else if (getWifiDisconnectReason() == 23) { - display->drawString(x, y + FONT_HEIGHT * 1, "802_1X_AUTH_FAILED"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "802_1X_AUTH_FAILED"); } else if (getWifiDisconnectReason() == 24) { - display->drawString(x, y + FONT_HEIGHT * 1, "CIPHER_SUITE_REJECTED"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "CIPHER_SUITE_REJECTED"); } else if (getWifiDisconnectReason() == 200) { - display->drawString(x, y + FONT_HEIGHT * 1, "BEACON_TIMEOUT"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "BEACON_TIMEOUT"); } else if (getWifiDisconnectReason() == 201) { - display->drawString(x, y + FONT_HEIGHT * 1, "AP Not Found"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AP Not Found"); } else if (getWifiDisconnectReason() == 202) { - display->drawString(x, y + FONT_HEIGHT * 1, "AUTH_FAIL"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AUTH_FAIL"); } else if (getWifiDisconnectReason() == 203) { - display->drawString(x, y + FONT_HEIGHT * 1, "ASSOC_FAIL"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "ASSOC_FAIL"); } else if (getWifiDisconnectReason() == 204) { - display->drawString(x, y + FONT_HEIGHT * 1, "HANDSHAKE_TIMEOUT"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "HANDSHAKE_TIMEOUT"); } else if (getWifiDisconnectReason() == 205) { - display->drawString(x, y + FONT_HEIGHT * 1, "Connection Failed"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed"); } else { - display->drawString(x, y + FONT_HEIGHT * 1, "Unknown Status"); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Unknown Status"); } } - display->drawString(x, y + FONT_HEIGHT * 2, "SSID: " + String(wifiName)); - display->drawString(x, y + FONT_HEIGHT * 3, "PWD: " + String(wifiPsw)); + display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "SSID: " + String(wifiName)); + display->drawString(x, y + FONT_HEIGHT_SMALL * 3, "PWD: " + String(wifiPsw)); /* Display a heartbeat pixel that blinks every time the frame is redrawn */ #ifdef SHOW_REDRAWS @@ -1001,7 +1060,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat { displayedNodeNum = 0; // Not currently showing a node pane - display->setFont(ArialMT_Plain_10); + display->setFont(FONT_SMALL); // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -1035,21 +1094,22 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat minutes %= 60; hours %= 24; - display->drawString(x, y + FONT_HEIGHT * 1, + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, String(days) + "d " + (hours < 10 ? "0" : "") + String(hours) + ":" + (minutes < 10 ? "0" : "") + String(minutes) + ":" + (seconds < 10 ? "0" : "") + String(seconds)); #ifndef NO_ESP32 // Show CPU Frequency. - display->drawString(x + SCREEN_WIDTH - display->getStringWidth("CPU " + String(getCpuFrequencyMhz()) + "MHz"), y + FONT_HEIGHT * 1, - "CPU " + String(getCpuFrequencyMhz()) + "MHz"); + display->drawString(x + SCREEN_WIDTH - display->getStringWidth("CPU " + String(getCpuFrequencyMhz()) + "MHz"), + y + FONT_HEIGHT_SMALL * 1, + "CPU " + String(getCpuFrequencyMhz()) + "MHz"); #endif // Line 3 - drawGPSAltitude(display, x, y + FONT_HEIGHT * 2, gpsStatus); + drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus); // Line 4 - drawGPScoordinates(display, x, y + FONT_HEIGHT * 3, gpsStatus); + drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus); /* Display a heartbeat pixel that blinks every time the frame is redrawn */ #ifdef SHOW_REDRAWS @@ -1079,9 +1139,7 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg) switch (arg->getStatusType()) { case STATUS_TYPE_NODE: if (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) { - setFrames(); // Regen the list of screens - prevFrame = -1; // Force a GUI update - setInterval(0); // Update the screen right away + setFrames(); // Regen the list of screens } nodeDB.updateGUI = false; nodeDB.updateTextMessage = false; diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index e4007aeac..b2f90e840 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -180,6 +180,9 @@ class Screen : public concurrency::OSThread int handleStatusUpdate(const meshtastic::Status *arg); + /// Used to force (super slow) eink displays to draw critical frames + void forceDisplay(); + protected: /// Updates the UI. // @@ -216,6 +219,9 @@ class Screen : public concurrency::OSThread /// Rebuilds our list of frames (screens) to default ones. void setFrames(); + /// Try to start drawing ASAP + void setFastFramerate(); + /// Called when debug screen is to be drawn, calls through to debugInfo.drawFrame. static void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index b9484b9d6..f12582914 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -11,8 +11,7 @@ static TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup. TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl) { - setGeometry(GEOMETRY_128_64); // FIXME - currently we lie and claim 128x64 because I'm not yet sure other resolutions will - // work ie GEOMETRY_RAWMODE + setGeometry(GEOMETRY_RAWMODE, 160, 80); } // Write the buffer to the display memory @@ -22,11 +21,11 @@ void TFTDisplay::display(void) // FIXME - only draw bits have changed (use backbuf similar to the other displays) // tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK); - for (uint8_t y = 0; y < SCREEN_HEIGHT; y++) { - for (uint8_t x = 0; x < SCREEN_WIDTH; x++) { + for (uint8_t y = 0; y < displayHeight; y++) { + for (uint8_t x = 0; x < displayWidth; x++) { // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent - auto b = buffer[x + (y / 8) * SCREEN_WIDTH]; + auto b = buffer[x + (y / 8) * displayWidth]; auto isset = b & (1 << (y & 7)); tft.drawPixel(x, y, isset ? TFT_WHITE : TFT_BLACK); } diff --git a/src/graphics/configs.h b/src/graphics/configs.h index c7698f898..65128294c 100644 --- a/src/graphics/configs.h +++ b/src/graphics/configs.h @@ -2,11 +2,7 @@ #include "fonts.h" -#define FONT_HEIGHT 14 // actually 13 for "Arial 10" but want a little extra space -#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1) // This means the *visible* area (sh1106 can address 132, but shows 128 for example) -#define SCREEN_WIDTH 128 -#define SCREEN_HEIGHT 64 #define TRANSITION_FRAMERATE 30 // fps #define IDLE_FRAMERATE 1 // in fps #define COMPASS_DIAM 44 diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 613665dc8..cf47cfd15 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -147,6 +147,7 @@ bool NodeDB::resetRadioConfig() radioConfig.preferences.wait_bluetooth_secs = 30; radioConfig.preferences.position_broadcast_secs = 6 * 60; radioConfig.preferences.ls_secs = 60; + radioConfig.preferences.region = RegionCode_TW; } return didFactoryReset; diff --git a/variants/eink/variant.h b/variants/eink/variant.h index 1dd2e9dc2..2dc5ccc1c 100644 --- a/variants/eink/variant.h +++ b/variants/eink/variant.h @@ -239,7 +239,7 @@ External serial flash WP25R1635FZUIL0 #define PIN_SPI_SCK (0 + 19) // To debug via the segger JLINK console rather than the CDC-ACM serial device -#define USE_SEGGER +// #define USE_SEGGER #ifdef __cplusplus }