mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-15 17:42:12 +00:00

The problem was we were pulsing the display power briefly down while reentering the ON state (because the ON states exit rule turned it off). Instead we now just turn off the screen on entry to DARK or LS states
726 lines
22 KiB
C++
726 lines
22 KiB
C++
/*
|
|
|
|
SSD1306 - Screen module
|
|
|
|
Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include <Wire.h>
|
|
#include "SSD1306Wire.h"
|
|
#include "OLEDDisplay.h"
|
|
#include "images.h"
|
|
#include "fonts.h"
|
|
#include "GPS.h"
|
|
#include "OLEDDisplayUi.h"
|
|
#include "screen.h"
|
|
#include "mesh-pb-constants.h"
|
|
#include "NodeDB.h"
|
|
#include "main.h"
|
|
|
|
#define FONT_HEIGHT 14 // actually 13 for "ariel 10" but want a little extra space
|
|
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
|
|
#define SCREEN_WIDTH 128
|
|
#define SCREEN_HEIGHT 64
|
|
|
|
#ifdef I2C_SDA
|
|
SSD1306Wire dispdev(SSD1306_ADDRESS, I2C_SDA, I2C_SCL);
|
|
#else
|
|
SSD1306Wire dispdev(SSD1306_ADDRESS, 0, 0); // fake values to keep build happy, we won't ever init
|
|
#endif
|
|
|
|
bool disp; // true if we are using display
|
|
bool screenOn; // true if the display is currently powered
|
|
|
|
OLEDDisplayUi ui(&dispdev);
|
|
|
|
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
|
|
// A text message frame + debug frame + all the node infos
|
|
FrameCallback nonBootFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
|
|
|
|
Screen screen;
|
|
static bool showingBluetooth;
|
|
|
|
/// If set to true (possibly from an ISR), we should turn on the screen the next time our idle loop runs.
|
|
static bool showingBootScreen = true; // start by showing the bootscreen
|
|
|
|
bool Screen::isOn() { return screenOn; }
|
|
|
|
void msOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
|
|
{
|
|
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
|
display->setFont(ArialMT_Plain_10);
|
|
display->drawString(128, 0, String(millis()));
|
|
}
|
|
|
|
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");
|
|
|
|
ui.disableIndicator();
|
|
}
|
|
|
|
static char btPIN[16] = "888888";
|
|
|
|
void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
{
|
|
// Demonstrates the 3 included default sizes. The fonts come from SSD1306Fonts.h file
|
|
// Besides the default fonts there will be a program to convert TrueType fonts into this format
|
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
|
display->setFont(ArialMT_Plain_16);
|
|
display->drawString(64 + x, 2 + y, "Bluetooth");
|
|
|
|
display->setFont(ArialMT_Plain_10);
|
|
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT + y, "Enter this code");
|
|
|
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
|
display->setFont(ArialMT_Plain_24);
|
|
display->drawString(64 + x, 22 + y, btPIN);
|
|
|
|
ui.disableIndicator();
|
|
}
|
|
|
|
void drawFrame2(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
{
|
|
// Demonstrates the 3 included default sizes. The fonts come from SSD1306Fonts.h file
|
|
// Besides the default fonts there will be a program to convert TrueType fonts into this format
|
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
display->setFont(ArialMT_Plain_10);
|
|
display->drawString(0 + x, 10 + y, "Arial 10");
|
|
|
|
display->setFont(ArialMT_Plain_16);
|
|
display->drawString(0 + x, 20 + y, "Arial 16");
|
|
|
|
display->setFont(ArialMT_Plain_24);
|
|
display->drawString(0 + x, 34 + y, "Arial 24");
|
|
}
|
|
|
|
void drawFrame3(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
{
|
|
// Text alignment demo
|
|
display->setFont(ArialMT_Plain_10);
|
|
|
|
// The coordinates define the left starting point of the text
|
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
display->drawString(0 + x, 11 + y, "Left aligned (0,10)");
|
|
|
|
// The coordinates define the center of the text
|
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
|
display->drawString(64 + x, 22 + y, "Center aligned (64,22)");
|
|
|
|
// The coordinates define the right end of the text
|
|
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
|
display->drawString(128 + x, 33 + y, "Right aligned (128,33)");
|
|
}
|
|
|
|
/// Draw the last text message we received
|
|
void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
{
|
|
MeshPacket &mp = devicestate.rx_text_message;
|
|
NodeInfo *node = nodeDB.getNode(mp.from);
|
|
// DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from, mp.payload.variant.data.payload.bytes);
|
|
|
|
// Demo for drawStringMaxWidth:
|
|
// 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);
|
|
String sender = (node && node->has_user) ? node->user.short_name : "???";
|
|
display->drawString(0 + x, 0 + y, sender);
|
|
display->setFont(ArialMT_Plain_10);
|
|
|
|
static char tempBuf[96];
|
|
snprintf(tempBuf, sizeof(tempBuf), " %s", mp.payload.variant.data.payload.bytes); // the max length of this buffer is much longer than we can possibly print
|
|
|
|
display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf);
|
|
|
|
// ui.disableIndicator();
|
|
}
|
|
|
|
/// Draw a series of fields in a column, wrapping to multiple colums if needed
|
|
void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
|
{
|
|
// The coordinates define the left starting point of the text
|
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
|
|
const char **f = fields;
|
|
int xo = x, yo = y;
|
|
while (*f)
|
|
{
|
|
display->drawString(xo, yo, *f);
|
|
yo += FONT_HEIGHT;
|
|
if (yo > SCREEN_HEIGHT - FONT_HEIGHT)
|
|
{
|
|
xo += SCREEN_WIDTH / 2;
|
|
yo = 0;
|
|
}
|
|
f++;
|
|
}
|
|
}
|
|
|
|
/// Draw a series of fields in a row, wrapping to multiple rows if needed
|
|
/// @return the max y we ended up printing to
|
|
uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
|
{
|
|
// The coordinates define the left starting point of the text
|
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
|
|
const char **f = fields;
|
|
int xo = x, yo = y;
|
|
while (*f)
|
|
{
|
|
display->drawString(xo, yo, *f);
|
|
xo += SCREEN_WIDTH / 2; // hardwired for two columns per row....
|
|
if (xo >= SCREEN_WIDTH)
|
|
{
|
|
yo += FONT_HEIGHT;
|
|
xo = 0;
|
|
}
|
|
f++;
|
|
}
|
|
|
|
yo += FONT_HEIGHT; // include the last line in our total
|
|
|
|
return yo;
|
|
}
|
|
|
|
/// Ported from my old java code, returns distance in meters along the globe surface (by magic?)
|
|
float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
|
|
{
|
|
double pk = (180 / 3.14169);
|
|
double a1 = lat_a / pk;
|
|
double a2 = lng_a / pk;
|
|
double b1 = lat_b / pk;
|
|
double b2 = lng_b / pk;
|
|
double cos_b1 = cos(b1);
|
|
double cos_a1 = cos(a1);
|
|
double t1 =
|
|
cos_a1 * cos(a2) * cos_b1 * cos(b2);
|
|
double t2 =
|
|
cos_a1 * sin(a2) * cos_b1 * sin(b2);
|
|
double t3 = sin(a1) * sin(b1);
|
|
double tt = acos(t1 + t2 + t3);
|
|
if (isnan(tt))
|
|
tt = 0.0; // Must have been the same point?
|
|
|
|
return (float)(6366000 * tt);
|
|
}
|
|
|
|
inline double toRadians(double deg)
|
|
{
|
|
return deg * PI / 180;
|
|
}
|
|
|
|
inline double toDegrees(double r)
|
|
{
|
|
return r * 180 / PI;
|
|
}
|
|
|
|
/**
|
|
* Computes the bearing in degrees between two points on Earth. Ported from my old Gaggle android app.
|
|
*
|
|
* @param lat1
|
|
* Latitude of the first point
|
|
* @param lon1
|
|
* Longitude of the first point
|
|
* @param lat2
|
|
* Latitude of the second point
|
|
* @param lon2
|
|
* Longitude of the second point
|
|
* @return Bearing between the two points in radians. A value of 0 means due
|
|
* north.
|
|
*/
|
|
float bearing(double lat1, double lon1, double lat2, double lon2)
|
|
{
|
|
double lat1Rad = toRadians(lat1);
|
|
double lat2Rad = toRadians(lat2);
|
|
double deltaLonRad = toRadians(lon2 - lon1);
|
|
double y = sin(deltaLonRad) * cos(lat2Rad);
|
|
double x = cos(lat1Rad) * sin(lat2Rad) - (sin(lat1Rad) * cos(lat2Rad) * cos(deltaLonRad));
|
|
return atan2(y, x);
|
|
}
|
|
|
|
/// A basic 2D point class for drawing
|
|
class Point
|
|
{
|
|
public:
|
|
float x, y;
|
|
|
|
Point(float _x, float _y) : x(_x), y(_y) {}
|
|
|
|
/// Apply a rotation around zero (standard rotation matrix math)
|
|
void rotate(float radian)
|
|
{
|
|
float cos = cosf(radian),
|
|
sin = sinf(radian);
|
|
float rx = x * cos - y * sin,
|
|
ry = x * sin + y * cos;
|
|
|
|
x = rx;
|
|
y = ry;
|
|
}
|
|
|
|
void translate(int16_t dx, int dy)
|
|
{
|
|
x += dx;
|
|
y += dy;
|
|
}
|
|
|
|
void scale(float f)
|
|
{
|
|
x *= f;
|
|
y *= f;
|
|
}
|
|
};
|
|
|
|
void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2)
|
|
{
|
|
d->drawLine(p1.x, p1.y, p2.x, p2.y);
|
|
}
|
|
|
|
/**
|
|
* Given a recent lat/lon return a guess of the heading the user is walking on.
|
|
*
|
|
* We keep a series of "after you've gone 10 meters, what is your heading since the last reference point?"
|
|
*/
|
|
float estimatedHeading(double lat, double lon)
|
|
{
|
|
static double oldLat, oldLon;
|
|
static float b;
|
|
|
|
if (oldLat == 0)
|
|
{
|
|
// just prepare for next time
|
|
oldLat = lat;
|
|
oldLon = lon;
|
|
|
|
return b;
|
|
}
|
|
|
|
float d = latLongToMeter(oldLat, oldLon, lat, lon);
|
|
if (d < 10) // haven't moved enough, just keep current bearing
|
|
return b;
|
|
|
|
b = bearing(oldLat, oldLon, lat, lon);
|
|
oldLat = lat;
|
|
oldLon = lon;
|
|
|
|
return b;
|
|
}
|
|
|
|
/// Sometimes we will have Position objects that only have a time, so check for valid lat/lon
|
|
bool hasPosition(NodeInfo *n)
|
|
{
|
|
return n->has_position && (n->position.latitude != 0 || n->position.longitude != 0);
|
|
}
|
|
#define COMPASS_DIAM 44
|
|
|
|
/// We will skip one node - the one for us, so we just blindly loop over all nodes
|
|
static size_t nodeIndex;
|
|
static int8_t prevFrame = -1;
|
|
|
|
void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
{
|
|
// We only advance our nodeIndex if the frame # has changed - because drawNodeInfo will be called repeatedly while the frame is shown
|
|
if (state->currentFrame != prevFrame)
|
|
{
|
|
prevFrame = state->currentFrame;
|
|
|
|
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
|
|
NodeInfo *n = nodeDB.getNodeByIndex(nodeIndex);
|
|
if (n->num == nodeDB.getNodeNum())
|
|
{
|
|
// Don't show our node, just skip to next
|
|
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
|
|
}
|
|
}
|
|
|
|
NodeInfo *node = nodeDB.getNodeByIndex(nodeIndex);
|
|
|
|
display->setFont(ArialMT_Plain_10);
|
|
|
|
// The coordinates define the left starting point of the text
|
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
|
|
const char *username = node->has_user ? node->user.long_name : "Unknown Name";
|
|
|
|
static char signalStr[20];
|
|
snprintf(signalStr, sizeof(signalStr), "Signal: %d", node->snr);
|
|
|
|
uint32_t agoSecs = sinceLastSeen(node);
|
|
static char lastStr[20];
|
|
if (agoSecs < 120) // last 2 mins?
|
|
snprintf(lastStr, sizeof(lastStr), "%d seconds ago", agoSecs);
|
|
else if (agoSecs < 120 * 60) // last 2 hrs
|
|
snprintf(lastStr, sizeof(lastStr), "%d minutes ago", agoSecs / 60);
|
|
else
|
|
snprintf(lastStr, sizeof(lastStr), "%d hours ago", agoSecs / 60 / 60);
|
|
|
|
static float simRadian;
|
|
simRadian += 0.1; // For testing, have the compass spin unless both locations are valid
|
|
|
|
static char distStr[20];
|
|
*distStr = 0; // might not have location data
|
|
float headingRadian = simRadian;
|
|
NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum());
|
|
if (ourNode && hasPosition(ourNode) && hasPosition(node))
|
|
{
|
|
Position &op = ourNode->position, &p = node->position;
|
|
float d = latLongToMeter(p.latitude, p.longitude, op.latitude, op.longitude);
|
|
if (d < 2000)
|
|
snprintf(distStr, sizeof(distStr), "%.0f m", d);
|
|
else
|
|
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
|
|
|
|
// FIXME, also keep the guess at the operators heading and add/substract it. currently we don't do this and instead draw north up only.
|
|
float bearingToOther = bearing(p.latitude, p.longitude, op.latitude, op.longitude);
|
|
float myHeading = estimatedHeading(p.latitude, p.longitude);
|
|
headingRadian = bearingToOther - myHeading;
|
|
}
|
|
|
|
const char *fields[] = {
|
|
username,
|
|
distStr,
|
|
signalStr,
|
|
lastStr,
|
|
NULL};
|
|
drawColumns(display, x, y, fields);
|
|
|
|
// coordinates for the center of the compass
|
|
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 1, compassY = y + SCREEN_HEIGHT / 2;
|
|
// display->drawXbm(compassX, compassY, compass_width, compass_height, (const uint8_t *)compass_bits);
|
|
|
|
Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially
|
|
float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
|
|
Point leftArrow(tip.x - arrowOffsetX, tip.y - arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y - arrowOffsetY);
|
|
|
|
Point *points[] = {&tip, &tail, &leftArrow, &rightArrow};
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
points[i]->rotate(headingRadian);
|
|
points[i]->scale(COMPASS_DIAM * 0.6);
|
|
points[i]->translate(compassX, compassY);
|
|
}
|
|
drawLine(display, tip, tail);
|
|
drawLine(display, leftArrow, tip);
|
|
drawLine(display, rightArrow, tip);
|
|
|
|
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
|
}
|
|
|
|
void drawDebugInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
{
|
|
display->setFont(ArialMT_Plain_10);
|
|
|
|
// The coordinates define the left starting point of the text
|
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
|
|
static char usersStr[20];
|
|
snprintf(usersStr, sizeof(usersStr), "Users %d/%d", nodeDB.getNumOnlineNodes(), nodeDB.getNumNodes());
|
|
|
|
static char channelStr[20];
|
|
snprintf(channelStr, sizeof(channelStr), "%s", channelSettings.name);
|
|
|
|
// We don't show battery levels yet - for now just lie and show debug info
|
|
static char batStr[20];
|
|
snprintf(batStr, sizeof(batStr), "Batt %x%%", (isCharging << 1) + isUSBPowered);
|
|
|
|
static char gpsStr[20];
|
|
if (myNodeInfo.has_gps)
|
|
snprintf(gpsStr, sizeof(gpsStr), "GPS %d%%", 75); // FIXME, use something based on hdop
|
|
else
|
|
gpsStr[0] = '\0'; // Just show emptystring
|
|
|
|
const char *fields[] = {
|
|
batStr,
|
|
gpsStr,
|
|
usersStr,
|
|
channelStr,
|
|
NULL};
|
|
uint32_t yo = drawRows(display, x, y, fields);
|
|
|
|
display->drawLogBuffer(x, yo);
|
|
}
|
|
|
|
// This array keeps function pointers to all frames
|
|
// frames are the single views that slide in
|
|
FrameCallback bootFrames[] = {drawBootScreen};
|
|
|
|
// Overlays are statically drawn on top of a frame eg. a clock
|
|
OverlayCallback overlays[] = {/* msOverlay */};
|
|
|
|
// how many frames are there?
|
|
const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
|
|
const int overlaysCount = sizeof(overlays) / sizeof(overlays[0]);
|
|
|
|
#if 0
|
|
void _screen_header()
|
|
{
|
|
if (!disp)
|
|
return;
|
|
|
|
|
|
// Message count
|
|
//snprintf(buffer, sizeof(buffer), "#%03d", ttn_get_count() % 1000);
|
|
//display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
//display->drawString(0, 2, buffer);
|
|
|
|
// Datetime
|
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
|
display->drawString(display->getWidth()/2, 2, gps.getTimeStr());
|
|
|
|
// Satellite count
|
|
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
|
char buffer[10];
|
|
display->drawString(display->getWidth() - SATELLITE_IMAGE_WIDTH - 4, 2, itoa(gps.satellites.value(), buffer, 10));
|
|
display->drawXbm(display->getWidth() - SATELLITE_IMAGE_WIDTH, 0, SATELLITE_IMAGE_WIDTH, SATELLITE_IMAGE_HEIGHT, SATELLITE_IMAGE);
|
|
}
|
|
#endif
|
|
|
|
void Screen::setOn(bool on)
|
|
{
|
|
if (!disp)
|
|
return;
|
|
|
|
if (on != screenOn)
|
|
{
|
|
if (on)
|
|
{
|
|
DEBUG_MSG("Turning on screen\n");
|
|
dispdev.displayOn();
|
|
setPeriod(1); // redraw ASAP
|
|
}
|
|
else {
|
|
DEBUG_MSG("Turning off screen\n");
|
|
dispdev.displayOff();
|
|
}
|
|
screenOn = on;
|
|
}
|
|
}
|
|
|
|
void screen_print(const char *text, uint8_t x, uint8_t y, uint8_t alignment)
|
|
{
|
|
DEBUG_MSG(text);
|
|
|
|
if (!disp)
|
|
return;
|
|
|
|
dispdev.setTextAlignment((OLEDDISPLAY_TEXT_ALIGNMENT)alignment);
|
|
dispdev.drawString(x, y, text);
|
|
}
|
|
|
|
void screen_print(const char *text)
|
|
{
|
|
DEBUG_MSG("Screen: %s", text);
|
|
if (!disp)
|
|
return;
|
|
|
|
dispdev.print(text);
|
|
// ui.update();
|
|
}
|
|
|
|
void Screen::setup()
|
|
{
|
|
#ifdef I2C_SDA
|
|
// Display instance
|
|
disp = true;
|
|
|
|
// The ESP is capable of rendering 60fps in 80Mhz mode
|
|
// but that won't give you much time for anything else
|
|
// run it in 160Mhz mode or just set it to 30 fps
|
|
// We do this now in loop()
|
|
// ui.setTargetFPS(30);
|
|
|
|
// Customize the active and inactive symbol
|
|
//ui.setActiveSymbol(activeSymbol);
|
|
//ui.setInactiveSymbol(inactiveSymbol);
|
|
|
|
ui.setTimePerTransition(300); // msecs
|
|
|
|
// You can change this to
|
|
// TOP, LEFT, BOTTOM, RIGHT
|
|
ui.setIndicatorPosition(BOTTOM);
|
|
|
|
// Defines where the first frame is located in the bar.
|
|
ui.setIndicatorDirection(LEFT_RIGHT);
|
|
|
|
// You can change the transition that is used
|
|
// SLIDE_LEFT, SLIDE_RIGHT, SLIDE_UP, SLIDE_DOWN
|
|
ui.setFrameAnimation(SLIDE_LEFT);
|
|
|
|
// Add frames - we subtract one from the framecount so there won't be a visual glitch when we take the boot screen out of the sequence.
|
|
ui.setFrames(bootFrames, bootFrameCount);
|
|
|
|
// Add overlays
|
|
ui.setOverlays(overlays, overlaysCount);
|
|
|
|
// Initialising the UI will init the display too.
|
|
ui.init();
|
|
|
|
// Scroll buffer
|
|
dispdev.setLogBuffer(3, 32);
|
|
|
|
setOn(true); // update our screenOn bool
|
|
|
|
#ifdef BICOLOR_DISPLAY
|
|
dispdev.flipScreenVertically(); // looks better without this on lora32
|
|
#endif
|
|
|
|
// dispdev.setFont(Custom_ArialMT_Plain_10);
|
|
|
|
ui.disableAutoTransition(); // we now require presses
|
|
#endif
|
|
}
|
|
|
|
#define TRANSITION_FRAMERATE 30 // fps
|
|
#define IDLE_FRAMERATE 10 // in fps
|
|
|
|
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
|
|
|
void Screen::doTask()
|
|
{
|
|
if (!disp)
|
|
{ // If we don't have a screen, don't ever spend any CPU for us
|
|
setPeriod(0);
|
|
return;
|
|
}
|
|
|
|
if (!screenOn)
|
|
{ // If we didn't just wake and the screen is still off, then stop updating until it is on again
|
|
setPeriod(0);
|
|
return;
|
|
}
|
|
|
|
// 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.
|
|
if (targetFramerate != IDLE_FRAMERATE && ui.getUiState()->frameState == FIXED)
|
|
{
|
|
// oldFrameState = ui.getUiState()->frameState;
|
|
DEBUG_MSG("Setting idle framerate\n");
|
|
targetFramerate = IDLE_FRAMERATE;
|
|
ui.setTargetFPS(targetFramerate);
|
|
}
|
|
|
|
// While showing the bluetooth pair screen all of our standard screen switching is stopped
|
|
if (!showingBluetooth)
|
|
{
|
|
// Once we finish showing the bootscreen, remove it from the loop
|
|
if (showingBootScreen)
|
|
{
|
|
if (millis() > 3 * 1000) // we show the boot screen for a few seconds only
|
|
{
|
|
showingBootScreen = false;
|
|
setFrames();
|
|
}
|
|
}
|
|
else // standard screen loop handling ehre
|
|
{
|
|
// If the # nodes changes, we need to regen our list of screens
|
|
if (nodeDB.updateGUI || nodeDB.updateTextMessage)
|
|
{
|
|
setFrames();
|
|
nodeDB.updateGUI = false;
|
|
nodeDB.updateTextMessage = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This must be after we possibly do screen_set_frames() to ensure we draw the new data
|
|
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 as fast as we really need so that any rounding errors still result
|
|
// with the correct framerate
|
|
setPeriod(1000 / targetFramerate);
|
|
}
|
|
|
|
#include "PowerFSM.h"
|
|
|
|
// Show the bluetooth PIN screen
|
|
void screen_start_bluetooth(uint32_t pin)
|
|
{
|
|
static FrameCallback btFrames[] = {drawFrameBluetooth};
|
|
|
|
snprintf(btPIN, sizeof(btPIN), "%06d", pin);
|
|
|
|
DEBUG_MSG("showing bluetooth screen\n");
|
|
showingBluetooth = true;
|
|
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
|
|
|
|
ui.setFrames(btFrames, 1); // Just show the bluetooth frame
|
|
// we rely on our main loop to show this screen (because we are invoked deep inside of bluetooth callbacks)
|
|
// ui.update(); // manually draw once, because I'm not sure if loop is getting called
|
|
}
|
|
|
|
// restore our regular frame list
|
|
void Screen::setFrames()
|
|
{
|
|
DEBUG_MSG("showing standard frames\n");
|
|
|
|
size_t numnodes = nodeDB.getNumNodes();
|
|
// We don't show the node info our our node (if we have it yet - we should)
|
|
if (numnodes > 0)
|
|
numnodes--;
|
|
|
|
size_t numframes = 0;
|
|
|
|
// If we have a text message - show it first
|
|
if (devicestate.has_rx_text_message)
|
|
nonBootFrames[numframes++] = drawTextMessageFrame;
|
|
|
|
// then all the nodes
|
|
for (size_t i = 0; i < numnodes; i++)
|
|
nonBootFrames[numframes++] = drawNodeInfo;
|
|
|
|
// then the debug info
|
|
nonBootFrames[numframes++] = drawDebugInfo;
|
|
|
|
ui.setFrames(nonBootFrames, numframes);
|
|
showingBluetooth = false;
|
|
|
|
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed)
|
|
}
|
|
|
|
/// handle press of the button
|
|
void Screen::onPress()
|
|
{
|
|
// 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)
|
|
{
|
|
setPeriod(1); // 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);
|
|
}
|
|
}
|