kopia lustrzana https://github.com/meshtastic/firmware
				
				
				
			
		
			
				
	
	
		
			643 wiersze
		
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
			
		
		
	
	
			643 wiersze
		
	
	
		
			19 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 <OLEDDisplay.h>
 | 
						|
#include <Wire.h>
 | 
						|
 | 
						|
#include "GPS.h"
 | 
						|
#include "NodeDB.h"
 | 
						|
#include "configuration.h"
 | 
						|
#include "fonts.h"
 | 
						|
#include "images.h"
 | 
						|
#include "main.h"
 | 
						|
#include "mesh-pb-constants.h"
 | 
						|
#include "screen.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
 | 
						|
#define TRANSITION_FRAMERATE 30 // fps
 | 
						|
#define IDLE_FRAMERATE 10       // in fps
 | 
						|
#define COMPASS_DIAM 44
 | 
						|
 | 
						|
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
 | 
						|
 | 
						|
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 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");
 | 
						|
}
 | 
						|
 | 
						|
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
 | 
						|
{
 | 
						|
    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);
 | 
						|
}
 | 
						|
 | 
						|
/// Draw the last text message we received
 | 
						|
static 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);
 | 
						|
 | 
						|
    // the max length of this buffer is much longer than we can possibly print
 | 
						|
    static char tempBuf[96];
 | 
						|
    snprintf(tempBuf, sizeof(tempBuf), "         %s", mp.payload.variant.data.payload.bytes);
 | 
						|
 | 
						|
    display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf);
 | 
						|
}
 | 
						|
 | 
						|
/// Draw a series of fields in a column, wrapping to multiple colums if needed
 | 
						|
static 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
 | 
						|
static 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;
 | 
						|
    const int COLUMNS = 2; // hardwired for two columns per row....
 | 
						|
    int col = 0;           // track which column we are on
 | 
						|
    while (*f) {
 | 
						|
        display->drawString(xo, yo, *f);
 | 
						|
        xo += SCREEN_WIDTH / COLUMNS;
 | 
						|
        // Wrap to next row, if needed.
 | 
						|
        if (++col > COLUMNS) {
 | 
						|
            xo = x;
 | 
						|
            yo += FONT_HEIGHT;
 | 
						|
            col = 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?)
 | 
						|
static 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);
 | 
						|
}
 | 
						|
 | 
						|
static inline double toRadians(double deg)
 | 
						|
{
 | 
						|
    return deg * PI / 180;
 | 
						|
}
 | 
						|
 | 
						|
static 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.
 | 
						|
 */
 | 
						|
static 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);
 | 
						|
}
 | 
						|
 | 
						|
namespace
 | 
						|
{
 | 
						|
 | 
						|
/// 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;
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
static 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?"
 | 
						|
 */
 | 
						|
static 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
 | 
						|
static bool hasPosition(NodeInfo *n)
 | 
						|
{
 | 
						|
    return n->has_position && (n->position.latitude != 0 || n->position.longitude != 0);
 | 
						|
}
 | 
						|
 | 
						|
/// 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;
 | 
						|
 | 
						|
static 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);
 | 
						|
}
 | 
						|
 | 
						|
static 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);
 | 
						|
}
 | 
						|
 | 
						|
#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
 | 
						|
 | 
						|
Screen::Screen(uint8_t address, uint8_t sda, uint8_t scl)
 | 
						|
    : cmdQueue(32), useDisplay(sda || scl), dispdev(address, sda, scl), ui(&dispdev)
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
void Screen::handleSetOn(bool on)
 | 
						|
{
 | 
						|
    if (!useDisplay)
 | 
						|
        return;
 | 
						|
 | 
						|
    if (on != screenOn) {
 | 
						|
        if (on) {
 | 
						|
            DEBUG_MSG("Turning on screen\n");
 | 
						|
            dispdev.displayOn();
 | 
						|
        } else {
 | 
						|
            DEBUG_MSG("Turning off screen\n");
 | 
						|
            dispdev.displayOff();
 | 
						|
        }
 | 
						|
        screenOn = on;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void Screen::setup()
 | 
						|
{
 | 
						|
    if (!useDisplay)
 | 
						|
        return;
 | 
						|
 | 
						|
    dispdev.resetOrientation();
 | 
						|
 | 
						|
    // Initialising the UI will init the display too.
 | 
						|
    ui.init();
 | 
						|
    ui.setTimePerTransition(300); // msecs
 | 
						|
    ui.setIndicatorPosition(BOTTOM);
 | 
						|
    // Defines where the first frame is located in the bar.
 | 
						|
    ui.setIndicatorDirection(LEFT_RIGHT);
 | 
						|
    ui.setFrameAnimation(SLIDE_LEFT);
 | 
						|
    // Don't show the page swipe dots while in boot screen.
 | 
						|
    ui.disableAllIndicators();
 | 
						|
 | 
						|
    // Add frames.
 | 
						|
    static FrameCallback bootFrames[] = {drawBootScreen};
 | 
						|
    static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
 | 
						|
    ui.setFrames(bootFrames, bootFrameCount);
 | 
						|
    // No overlays.
 | 
						|
    ui.setOverlays(nullptr, 0);
 | 
						|
 | 
						|
    // Require presses to switch between frames.
 | 
						|
    ui.disableAutoTransition();
 | 
						|
 | 
						|
    // Set up a log buffer with 3 lines, 32 chars each.
 | 
						|
    dispdev.setLogBuffer(3, 32);
 | 
						|
 | 
						|
#ifdef FLIP_SCREEN_VERTICALLY
 | 
						|
    dispdev.flipScreenVertically();
 | 
						|
#endif
 | 
						|
 | 
						|
    // Turn on the display.
 | 
						|
    handleSetOn(true);
 | 
						|
 | 
						|
    // On some ssd1306 clones, the first draw command is discarded, so draw it
 | 
						|
    // twice initially.
 | 
						|
    ui.update();
 | 
						|
    ui.update();
 | 
						|
}
 | 
						|
 | 
						|
void Screen::doTask()
 | 
						|
{
 | 
						|
    // If we don't have a screen, don't ever spend any CPU for us.
 | 
						|
    if (!useDisplay) {
 | 
						|
        setPeriod(0);
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Process incoming commands.
 | 
						|
    for (;;) {
 | 
						|
        CmdItem cmd;
 | 
						|
        if (!cmdQueue.dequeue(&cmd, 0)) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        switch (cmd.cmd) {
 | 
						|
        case Cmd::SET_ON:
 | 
						|
            handleSetOn(true);
 | 
						|
            break;
 | 
						|
        case Cmd::SET_OFF:
 | 
						|
            handleSetOn(false);
 | 
						|
            break;
 | 
						|
        case Cmd::ON_PRESS:
 | 
						|
            handleOnPress();
 | 
						|
            break;
 | 
						|
        case Cmd::START_BLUETOOTH_PIN_SCREEN:
 | 
						|
            handleStartBluetoothPinScreen(cmd.bluetooth_pin);
 | 
						|
            break;
 | 
						|
        case Cmd::STOP_BLUETOOTH_PIN_SCREEN:
 | 
						|
        case Cmd::STOP_BOOT_SCREEN:
 | 
						|
            setFrames();
 | 
						|
            break;
 | 
						|
        case Cmd::PRINT:
 | 
						|
            handlePrint(cmd.print_text);
 | 
						|
            free(cmd.print_text);
 | 
						|
            break;
 | 
						|
        default:
 | 
						|
            DEBUG_MSG("BUG: invalid cmd");
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    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 bootscreen or Bluetooth pair screen all of our
 | 
						|
    // standard screen switching is stopped.
 | 
						|
    if (showingNormalScreen) {
 | 
						|
        // TODO(girts): decouple nodeDB from screen.
 | 
						|
        // 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;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    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);
 | 
						|
}
 | 
						|
 | 
						|
// restore our regular frame list
 | 
						|
void Screen::setFrames()
 | 
						|
{
 | 
						|
    DEBUG_MSG("showing standard frames\n");
 | 
						|
    showingNormalScreen = true;
 | 
						|
 | 
						|
    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)
 | 
						|
        normalFrames[numframes++] = drawTextMessageFrame;
 | 
						|
 | 
						|
    // then all the nodes
 | 
						|
    for (size_t i = 0; i < numnodes; i++)
 | 
						|
        normalFrames[numframes++] = drawNodeInfo;
 | 
						|
 | 
						|
    // then the debug info
 | 
						|
    normalFrames[numframes++] = drawDebugInfo;
 | 
						|
 | 
						|
    ui.setFrames(normalFrames, numframes);
 | 
						|
    ui.enableAllIndicators();
 | 
						|
 | 
						|
    prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
 | 
						|
                    // just changed)
 | 
						|
}
 | 
						|
 | 
						|
void Screen::handleStartBluetoothPinScreen(uint32_t pin)
 | 
						|
{
 | 
						|
    DEBUG_MSG("showing bluetooth screen\n");
 | 
						|
    showingNormalScreen = false;
 | 
						|
 | 
						|
    static FrameCallback btFrames[] = {drawFrameBluetooth};
 | 
						|
 | 
						|
    snprintf(btPIN, sizeof(btPIN), "%06d", pin);
 | 
						|
 | 
						|
    ui.disableAllIndicators();
 | 
						|
    ui.setFrames(btFrames, 1);
 | 
						|
}
 | 
						|
 | 
						|
void Screen::handlePrint(const char *text)
 | 
						|
{
 | 
						|
    DEBUG_MSG("Screen: %s", text);
 | 
						|
    if (!useDisplay)
 | 
						|
        return;
 | 
						|
 | 
						|
    dispdev.print(text);
 | 
						|
}
 | 
						|
 | 
						|
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) {
 | 
						|
        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);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
} // namespace meshtastic
 |