2020-02-07 21:51:17 +00:00
/*
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"
2020-02-11 18:51:45 +00:00
# include "mesh-pb-constants.h"
# include "NodeDB.h"
2020-02-07 21:51:17 +00:00
2020-02-07 22:52:45 +00:00
# define FONT_HEIGHT 14 // actually 13 for "ariel 10" but want a little extra space
2020-02-12 19:52:53 +00:00
# define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
2020-02-07 23:37:25 +00:00
# define SCREEN_WIDTH 128
# define SCREEN_HEIGHT 64
2020-02-07 21:51:17 +00:00
# ifdef I2C_SDA
2020-02-07 23:37:25 +00:00
SSD1306Wire dispdev ( SSD1306_ADDRESS , I2C_SDA , I2C_SCL ) ;
2020-02-07 21:51:17 +00:00
# else
2020-02-07 23:37:25 +00:00
SSD1306Wire dispdev ( SSD1306_ADDRESS , 0 , 0 ) ; // fake values to keep build happy, we won't ever init
# endif
2020-02-07 21:51:17 +00:00
2020-02-12 19:52:53 +00:00
bool disp ; // true if we are using display
2020-02-12 17:58:46 +00:00
bool screenOn ; // true if the display is currently powered
2020-02-07 21:51:17 +00:00
OLEDDisplayUi ui ( & dispdev ) ;
2020-02-11 18:51:45 +00:00
# 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 ] ;
2020-02-21 18:51:36 +00:00
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 wakeScreen ;
static bool showingBootScreen = true ; // start by showing the bootscreen
uint32_t lastPressMs ;
2020-02-21 12:57:08 +00:00
bool is_screen_on ( ) { return screenOn ; }
2020-02-07 21:51:17 +00:00
void msOverlay ( OLEDDisplay * display , OLEDDisplayUiState * state )
{
display - > setTextAlignment ( TEXT_ALIGN_RIGHT ) ;
display - > setFont ( ArialMT_Plain_10 ) ;
display - > drawString ( 128 , 0 , String ( millis ( ) ) ) ;
}
2020-02-07 22:52:45 +00:00
void drawBootScreen ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y )
2020-02-07 21:51:17 +00:00
{
// draw an xbm image.
// Please note that everything that should be transitioned
// needs to be drawn relative to x and y
2020-02-07 23:37:25 +00:00
display - > drawXbm ( x + 32 , y , icon_width , icon_height , ( const uint8_t * ) icon_bits ) ;
2020-02-07 22:52:45 +00:00
2020-02-12 19:52:53 +00:00
display - > setFont ( ArialMT_Plain_16 ) ;
2020-02-07 22:52:45 +00:00
display - > setTextAlignment ( TEXT_ALIGN_CENTER ) ;
2020-02-12 19:52:53 +00:00
display - > drawString ( 64 + x , SCREEN_HEIGHT - FONT_HEIGHT_16 , " meshtastic.org " ) ;
2020-02-07 22:52:45 +00:00
ui . disableIndicator ( ) ;
2020-02-07 21:51:17 +00:00
}
2020-02-08 04:59:21 +00:00
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 ( ) ;
}
2020-02-07 21:51:17 +00:00
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) " ) ;
}
2020-02-07 22:52:45 +00:00
/// Draw the last text message we received
2020-02-07 23:37:25 +00:00
void drawTextMessageFrame ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y )
2020-02-07 21:51:17 +00:00
{
2020-02-15 00:25:11 +00:00
MeshPacket & mp = devicestate . rx_text_message ;
2020-02-13 03:58:44 +00:00
NodeInfo * node = nodeDB . getNode ( mp . from ) ;
2020-02-07 21:51:17 +00:00
// 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 ) ;
2020-02-07 23:37:25 +00:00
display - > setFont ( ArialMT_Plain_16 ) ;
2020-02-13 03:58:44 +00:00
String sender = ( node & & node - > has_user ) ? node - > user . short_name : " ??? " ;
2020-02-07 23:37:25 +00:00
display - > drawString ( 0 + x , 0 + y , sender ) ;
2020-02-07 21:51:17 +00:00
display - > setFont ( ArialMT_Plain_10 ) ;
2020-02-15 00:25:11 +00:00
2020-02-13 03:58:44 +00:00
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 ) ;
2020-02-07 23:37:25 +00:00
2020-02-08 01:26:42 +00:00
// ui.disableIndicator();
2020-02-07 21:51:17 +00:00
}
2020-02-08 01:26:42 +00:00
/// 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 )
2020-02-07 21:51:17 +00:00
{
2020-02-08 01:26:42 +00:00
// 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 ;
}
2020-02-15 00:25:11 +00:00
/// 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 ) ;
}
2020-02-15 03:11:31 +00:00
inline double toRadians ( double deg )
{
return deg * PI / 180 ;
}
2020-02-19 23:29:18 +00:00
inline double toDegrees ( double r )
{
return r * 180 / PI ;
}
2020-02-15 03:11:31 +00:00
/**
2020-02-15 16:19:55 +00:00
* Computes the bearing in degrees between two points on Earth . Ported from my old Gaggle android app .
2020-02-15 03:11:31 +00:00
*
* @ 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 ) ;
}
2020-02-11 17:39:47 +00:00
/// A basic 2D point class for drawing
class Point
{
public :
float x , y ;
2020-02-11 18:51:45 +00:00
Point ( float _x , float _y ) : x ( _x ) , y ( _y ) { }
2020-02-11 17:39:47 +00:00
/// 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 ,
2020-02-11 18:51:45 +00:00
ry = x * sin + y * cos ;
2020-02-11 17:39:47 +00:00
x = rx ;
y = ry ;
}
2020-02-11 18:51:45 +00:00
void translate ( int16_t dx , int dy )
{
2020-02-11 17:39:47 +00:00
x + = dx ;
y + = dy ;
}
2020-02-11 18:51:45 +00:00
void scale ( float f )
{
2020-02-11 17:39:47 +00:00
x * = f ;
y * = f ;
}
} ;
2020-02-11 18:51:45 +00:00
void drawLine ( OLEDDisplay * d , const Point & p1 , const Point & p2 )
{
2020-02-11 17:39:47 +00:00
d - > drawLine ( p1 . x , p1 . y , p2 . x , p2 . y ) ;
}
2020-02-19 23:29:18 +00:00
/**
* 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 ;
}
2020-02-20 02:51:17 +00:00
/// Sometimes we will have Position objects that only have a time, so check for valid lat/lon
2020-02-21 18:13:51 +00:00
bool hasPosition ( NodeInfo * n )
{
2020-02-20 02:51:17 +00:00
return n - > has_position & & ( n - > position . latitude ! = 0 | | n - > position . longitude ! = 0 ) ;
}
2020-02-11 17:39:47 +00:00
# define COMPASS_DIAM 44
2020-02-11 18:51:45 +00:00
/// We will skip one node - the one for us, so we just blindly loop over all nodes
static size_t nodeIndex ;
2020-02-19 23:29:18 +00:00
static int8_t prevFrame = - 1 ;
2020-02-11 18:51:45 +00:00
2020-02-08 01:26:42 +00:00
void drawNodeInfo ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y )
{
2020-02-11 18:51:45 +00:00
// 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 ) ;
2020-02-08 01:26:42 +00:00
display - > setFont ( ArialMT_Plain_10 ) ;
// The coordinates define the left starting point of the text
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
2020-02-11 18:51:45 +00:00
const char * username = node - > has_user ? node - > user . long_name : " Unknown Name " ;
2020-02-11 19:03:03 +00:00
static char signalStr [ 20 ] ;
snprintf ( signalStr , sizeof ( signalStr ) , " Signal: %d " , node - > snr ) ;
2020-02-12 19:52:53 +00:00
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 ) ;
2020-02-15 03:11:31 +00:00
static float simRadian ;
simRadian + = 0.1 ; // For testing, have the compass spin unless both locations are valid
2020-02-15 00:25:11 +00:00
static char distStr [ 20 ] ;
* distStr = 0 ; // might not have location data
2020-02-15 03:11:31 +00:00
float headingRadian = simRadian ;
2020-02-15 00:25:11 +00:00
NodeInfo * ourNode = nodeDB . getNode ( nodeDB . getNodeNum ( ) ) ;
2020-02-20 02:51:17 +00:00
if ( ourNode & & hasPosition ( ourNode ) & & hasPosition ( node ) )
2020-02-15 00:25:11 +00:00
{
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 ) ;
2020-02-15 03:11:31 +00:00
// 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.
2020-02-19 23:29:18 +00:00
float bearingToOther = bearing ( p . latitude , p . longitude , op . latitude , op . longitude ) ;
float myHeading = estimatedHeading ( p . latitude , p . longitude ) ;
headingRadian = bearingToOther - myHeading ;
2020-02-15 00:25:11 +00:00
}
2020-02-08 01:26:42 +00:00
const char * fields [ ] = {
2020-02-11 18:51:45 +00:00
username ,
2020-02-15 00:25:11 +00:00
distStr ,
2020-02-11 19:03:03 +00:00
signalStr ,
2020-02-12 19:52:53 +00:00
lastStr ,
2020-02-08 01:26:42 +00:00
NULL } ;
drawColumns ( display , x , y , fields ) ;
2020-02-11 17:39:47 +00:00
// 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);
2020-02-11 18:51:45 +00:00
Point tip ( 0.0f , 0.5f ) , tail ( 0.0f , - 0.5f ) ; // pointing up initially
2020-02-12 15:51:04 +00:00
float arrowOffsetX = 0.2f , arrowOffsetY = 0.2f ;
2020-02-11 17:39:47 +00:00
Point leftArrow ( tip . x - arrowOffsetX , tip . y - arrowOffsetY ) , rightArrow ( tip . x + arrowOffsetX , tip . y - arrowOffsetY ) ;
2020-02-11 18:51:45 +00:00
Point * points [ ] = { & tip , & tail , & leftArrow , & rightArrow } ;
2020-02-11 17:39:47 +00:00
2020-02-11 18:51:45 +00:00
for ( int i = 0 ; i < 4 ; i + + )
{
2020-02-11 17:39:47 +00:00
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 ) ;
2020-02-08 01:26:42 +00:00
}
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 ) ;
2020-02-11 19:56:48 +00:00
static char usersStr [ 20 ] ;
snprintf ( usersStr , sizeof ( usersStr ) , " Users %d/%d " , nodeDB . getNumOnlineNodes ( ) , nodeDB . getNumNodes ( ) ) ;
2020-02-12 03:06:12 +00:00
static char channelStr [ 20 ] ;
2020-02-12 15:51:04 +00:00
snprintf ( channelStr , sizeof ( channelStr ) , " %s " , channelSettings . name ) ;
2020-02-12 03:06:12 +00:00
2020-02-08 01:26:42 +00:00
const char * fields [ ] = {
" Batt 89% " ,
" GPS 75% " ,
2020-02-11 19:56:48 +00:00
usersStr ,
2020-02-12 03:06:12 +00:00
channelStr ,
2020-02-08 01:26:42 +00:00
NULL } ;
uint32_t yo = drawRows ( display , x , y , fields ) ;
display - > drawLogBuffer ( x , yo ) ;
2020-02-07 21:51:17 +00:00
}
// This array keeps function pointers to all frames
// frames are the single views that slide in
2020-02-12 17:58:46 +00:00
FrameCallback bootFrames [ ] = { drawBootScreen } ;
2020-02-07 21:51:17 +00:00
// Overlays are statically drawn on top of a frame eg. a clock
2020-02-08 01:26:42 +00:00
OverlayCallback overlays [ ] = { /* msOverlay */ } ;
2020-02-07 21:51:17 +00:00
2020-02-07 23:37:25 +00:00
// how many frames are there?
2020-02-11 18:51:45 +00:00
const int bootFrameCount = sizeof ( bootFrames ) / sizeof ( bootFrames [ 0 ] ) ;
2020-02-07 23:37:25 +00:00
const int overlaysCount = sizeof ( overlays ) / sizeof ( overlays [ 0 ] ) ;
2020-02-08 04:59:21 +00:00
#if 0
2020-02-07 21:51:17 +00:00
void _screen_header ( )
{
if ( ! disp )
return ;
2020-02-08 04:59:21 +00:00
2020-02-07 21:51:17 +00:00
// 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 ) ;
}
2020-02-08 04:59:21 +00:00
# endif
2020-02-07 21:51:17 +00:00
void screen_off ( )
{
if ( ! disp )
return ;
dispdev . displayOff ( ) ;
2020-02-12 17:58:46 +00:00
screenOn = false ;
2020-02-07 21:51:17 +00:00
}
void screen_on ( )
{
if ( ! disp )
return ;
dispdev . displayOn ( ) ;
2020-02-12 17:58:46 +00:00
screenOn = true ;
2020-02-07 21:51:17 +00:00
}
static 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 )
{
2020-02-08 18:13:04 +00:00
DEBUG_MSG ( " Screen: %s " , text ) ;
2020-02-07 21:51:17 +00:00
if ( ! disp )
return ;
dispdev . print ( text ) ;
2020-02-08 01:26:42 +00:00
// ui.update();
2020-02-07 21:51:17 +00:00
}
2020-02-21 18:51:36 +00:00
void Screen : : doWakeScreen ( ) {
wakeScreen = true ;
setPeriod ( 1 ) ; // wake asap
}
void Screen : : setup ( )
2020-02-07 21:51:17 +00:00
{
# 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
2020-02-21 18:13:51 +00:00
// We do this now in loop()
// ui.setTargetFPS(30);
2020-02-07 21:51:17 +00:00
// Customize the active and inactive symbol
//ui.setActiveSymbol(activeSymbol);
//ui.setInactiveSymbol(inactiveSymbol);
// 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 ) ;
2020-02-08 01:26:42 +00:00
// 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.
2020-02-11 18:51:45 +00:00
ui . setFrames ( bootFrames , bootFrameCount ) ;
2020-02-07 21:51:17 +00:00
// Add overlays
ui . setOverlays ( overlays , overlaysCount ) ;
// Initialising the UI will init the display too.
ui . init ( ) ;
// Scroll buffer
2020-02-09 03:45:37 +00:00
dispdev . setLogBuffer ( 3 , 32 ) ;
2020-02-07 21:51:17 +00:00
2020-02-12 17:58:46 +00:00
screen_on ( ) ; // update our screenOn bool
2020-02-09 02:49:15 +00:00
# ifdef BICOLOR_DISPLAY
dispdev . flipScreenVertically ( ) ; // looks better without this on lora32
# endif
2020-02-07 23:37:25 +00:00
// dispdev.setFont(Custom_ArialMT_Plain_10);
2020-02-12 17:58:46 +00:00
ui . disableAutoTransition ( ) ; // we now require presses
2020-02-07 21:51:17 +00:00
# endif
}
2020-02-12 17:58:46 +00:00
/// Turn off the screen this many ms after last press or wake
# define SCREEN_SLEEP_MS (60 * 1000)
2020-02-21 18:13:51 +00:00
# define TRANSITION_FRAMERATE 60 // fps
# define IDLE_FRAMERATE 10 // in fps
static uint32_t targetFramerate = IDLE_FRAMERATE ;
2020-02-21 18:51:36 +00:00
void Screen : : doTask ( )
2020-02-07 21:51:17 +00:00
{
2020-02-21 18:51:36 +00:00
if ( ! disp ) { // If we don't have a screen, don't ever spend any CPU for us
setPeriod ( 0 ) ;
return ;
}
2020-02-07 21:51:17 +00:00
2020-02-21 18:51:36 +00:00
if ( wakeScreen ) // If a new text message arrived, turn the screen on immedately
2020-02-07 21:51:17 +00:00
{
2020-02-18 00:32:51 +00:00
lastPressMs = millis ( ) ; // if we were told to wake the screen, reset the press timeout
screen_on ( ) ; // make sure the screen is not asleep
2020-02-08 15:55:12 +00:00
wakeScreen = false ;
2020-02-07 21:51:17 +00:00
}
2020-02-08 15:38:08 +00:00
2020-02-21 18:51:36 +00:00
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 ;
}
2020-02-12 17:58:46 +00:00
2020-02-21 18:13:51 +00:00
// 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 ) ;
}
2020-02-08 01:26:42 +00:00
ui . update ( ) ;
2020-02-07 23:37:25 +00:00
2020-02-12 17:58:46 +00:00
// While showing the bluetooth pair screen all of our standard screen switching is stopped
if ( ! showingBluetooth )
2020-02-07 23:37:25 +00:00
{
2020-02-12 17:58:46 +00:00
// Once we finish showing the bootscreen, remove it from the loop
if ( showingBootScreen )
2020-02-11 18:51:45 +00:00
{
2020-02-12 17:58:46 +00:00
if ( millis ( ) > 5 * 1000 ) // we show the boot screen for a few seconds only
{
showingBootScreen = false ;
screen_set_frames ( ) ;
}
}
else // standard screen loop handling ehre
{
// If the # nodes changes, we need to regen our list of screens
2020-02-18 00:27:29 +00:00
if ( nodeDB . updateGUI | | nodeDB . updateTextMessage )
2020-02-12 17:58:46 +00:00
{
screen_set_frames ( ) ;
2020-02-18 00:27:29 +00:00
nodeDB . updateGUI = false ;
nodeDB . updateTextMessage = false ;
2020-02-12 17:58:46 +00:00
}
2020-02-12 19:52:53 +00:00
if ( millis ( ) - lastPressMs > SCREEN_SLEEP_MS )
{
2020-02-12 17:58:46 +00:00
DEBUG_MSG ( " screen timeout, turn it off for now... \n " ) ;
screen_off ( ) ;
}
2020-02-11 18:51:45 +00:00
}
2020-02-07 23:37:25 +00:00
}
2020-02-08 00:12:55 +00:00
2020-02-21 18:13:51 +00:00
// 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
2020-02-21 18:51:36 +00:00
setPeriod ( 1000 / targetFramerate / 2 ) ;
2020-02-07 21:51:17 +00:00
}
2020-02-08 01:48:12 +00:00
2020-02-08 04:59:21 +00:00
// Show the bluetooth PIN screen
2020-02-08 15:55:12 +00:00
void screen_start_bluetooth ( uint32_t pin )
{
static FrameCallback btFrames [ ] = { drawFrameBluetooth } ;
2020-02-08 04:59:21 +00:00
snprintf ( btPIN , sizeof ( btPIN ) , " %06d " , pin ) ;
DEBUG_MSG ( " showing bluetooth screen \n " ) ;
showingBluetooth = true ;
2020-02-21 18:51:36 +00:00
screen . doWakeScreen ( ) ;
2020-02-08 04:59:21 +00:00
2020-02-12 17:58:46 +00:00
ui . setFrames ( btFrames , 1 ) ; // Just show the bluetooth frame
2020-02-08 04:59:21 +00:00
// 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
2020-02-08 15:55:12 +00:00
void screen_set_frames ( )
{
2020-02-08 04:59:21 +00:00
DEBUG_MSG ( " showing standard frames \n " ) ;
2020-02-11 18:51:45 +00:00
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 - - ;
2020-02-13 03:58:44 +00:00
size_t numframes = 0 ;
// If we have a text message - show it first
2020-02-15 00:25:11 +00:00
if ( devicestate . has_rx_text_message )
2020-02-13 03:58:44 +00:00
nonBootFrames [ numframes + + ] = drawTextMessageFrame ;
// then all the nodes
2020-02-11 18:51:45 +00:00
for ( size_t i = 0 ; i < numnodes ; i + + )
2020-02-13 03:58:44 +00:00
nonBootFrames [ numframes + + ] = drawNodeInfo ;
// then the debug info
nonBootFrames [ numframes + + ] = drawDebugInfo ;
2020-02-11 18:51:45 +00:00
2020-02-13 03:58:44 +00:00
ui . setFrames ( nonBootFrames , numframes ) ;
2020-02-08 04:59:21 +00:00
showingBluetooth = false ;
2020-02-19 23:29:18 +00:00
prevFrame = - 1 ; // Force drawNodeInfo to pick a new node (because our list just changed)
2020-02-08 04:59:21 +00:00
}
2020-02-08 01:48:12 +00:00
/// handle press of the button
2020-02-08 15:55:12 +00:00
void screen_press ( )
{
2020-02-08 15:38:08 +00:00
// screen_start_bluetooth(123456);
2020-02-08 04:59:21 +00:00
2020-02-12 17:58:46 +00:00
lastPressMs = millis ( ) ;
2020-02-21 18:51:36 +00:00
screen . doWakeScreen ( ) ;
2020-02-12 17:58:46 +00:00
// If screen was off, just wake it, otherwise advance to next frame
2020-02-21 18:13:51 +00:00
// If we are in a transition, the press must have bounced, drop it.
if ( screenOn & & ui . getUiState ( ) - > frameState = = FIXED )
{
2020-02-12 17:58:46 +00:00
ui . nextFrame ( ) ;
2020-02-21 18:13:51 +00:00
DEBUG_MSG ( " Setting fast framerate \n " ) ;
// We are about to start a transition so speed up fps
targetFramerate = TRANSITION_FRAMERATE ;
ui . setTargetFPS ( targetFramerate ) ;
}
2020-02-08 01:48:12 +00:00
}