Created
November 18, 2019 19:26
-
-
Save bobmcwhirter/236dd816a743180b13a1e6cd2365e4c9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Project mw | |
* Description: | |
* Author: | |
* Date: | |
*/ | |
#include "application.h" | |
#include <GxEPD.h> | |
#include <GxGDEW0583T7/GxGDEW0583T7.h> // 5.83" b/w | |
#include <GxIO/GxIO_SPI/GxIO_SPI.h> | |
#include <GxIO/GxIO.h> | |
//#include "fonts/Roboto-Bold8pt7b.h" | |
//#include "fonts/Roboto-Black12pt7b.h" | |
//#include "fonts/Roboto-Black24pt7b.h" | |
//#include "fonts/Roboto-Black48pt7b.h" | |
//#define FONT_S &Roboto_Bold8pt7b | |
//#define FONT_M &Roboto_Black12pt7b | |
//#define FONT_L &Roboto_Black24pt7b | |
//#define FONT_XL &Roboto_Black48pt7b | |
#include "fonts/Courier8pt7b.h" | |
#include "fonts/Courier-Bold12pt7b.h" | |
#include "fonts/Courier-Bold24pt7b.h" | |
#include "fonts/Courier-Bold48pt7b.h" | |
#include "fonts/FFF_Tusj36pt7b.h" | |
#define FONT_S &Courier8pt7b | |
#define FONT_M &Courier_Bold12pt7b | |
#define FONT_L &Courier_Bold24pt7b | |
#define FONT_XL &Courier_Bold48pt7b | |
#define FONT_SPLASH &FFF_Tusj36pt7b | |
#include "icons/cloud-sun.h" | |
#include "icons/cloud.h" | |
#include "icons/fog.h" | |
#include "icons/rain.h" | |
#include "icons/snow.h" | |
#include "icons/sun.h" | |
#include "icons/wind.h" | |
GxIO_Class io(SPI, A3, D9, A1); // arbitrary selection of 17, 16 | |
//GxEPD_Class display(io, A1, A0); // arbitrary selection of (16), 4 | |
GxEPD_Class display(io, A1, D7); // arbitrary selection of (16), 4 | |
struct forecast { | |
char day[5] = "-"; | |
char icon[20]; | |
char high[4]; | |
char low[4]; | |
char precip[4]; | |
}; | |
struct Dashboard { | |
char temperature[20] = "0.0"; | |
char temperature_trend[10] = "stable"; | |
char temperature_min[20] = "0.0"; | |
char temperature_max[20] = "0.0"; | |
char humidity[10] = "0"; | |
char pressure[10] = "0"; | |
char rain_hour[10] = "0.0"; | |
char rain_day[10] = "0.0"; | |
char wind_speed[5] = "0"; | |
char wind_gust[5] = "0"; | |
char wind_max[5] = "0"; | |
char wind_direction[5] = ""; | |
char wind_angle[5] = "0"; | |
forecast forecast0; | |
forecast forecast1; | |
forecast forecast2; | |
forecast forecast3; | |
forecast forecast4; | |
char words[400] = ""; | |
int changed = 0; | |
time_t lastUpdateReceived = 0; | |
}; | |
Dashboard current = {}; | |
//SYSTEM_THREAD(ENABLED); | |
// setup() runs once, when the device is first turned on. | |
void setup() { | |
Serial.begin(115200); | |
delay(3000); | |
display.init(115200); // enable diagnostic output on Serial | |
Serial.printf( "%s %s %s %s\n", current.temperature, current.rain_day, current.wind_speed, current.wind_direction ); | |
Particle.subscribe("mrweathery.", handleMessage, MY_DEVICES); | |
} | |
int first = 1; | |
// loop() runs over and over again, as quickly as it can execute. | |
void loop() { | |
//delay(5000); | |
if ( first ) { | |
display.init(); | |
showWhiteout(); | |
//delay(2000); | |
showSplash(); | |
first = 0; | |
} | |
delay(3000); | |
showDashboard(); | |
delay(4000); | |
} | |
void showWhiteout() { | |
display.fillScreen(GxEPD_WHITE); | |
display.update(); | |
} | |
void showSplash() { | |
display.fillScreen(GxEPD_WHITE); | |
display.setTextColor(GxEPD_BLACK); | |
display.setFont(FONT_SPLASH); | |
display.setCursor( 300 - (widthOf( "Mr. Weathery", FONT_SPLASH)/2), 150); | |
display.print("mr. weathery"); | |
display.setFont(FONT_M); | |
display.setCursor( 300 - (widthOf( "starting up.", FONT_M)/2), 250); | |
display.print("starting up."); | |
display.drawBitmap(IconWind, 170, 300, 60, 60, GxEPD_BLACK, GxEPD::bm_invert); | |
display.drawBitmap(IconSun, 270, 300, 60, 60, GxEPD_BLACK, GxEPD::bm_invert); | |
display.drawBitmap(IconSnow, 370, 300, 60, 60, GxEPD_BLACK, GxEPD::bm_invert); | |
display.update(); | |
} | |
void showDashboard() { | |
Serial.printlnf( "start change flag now: %d", current.changed ); | |
if ( ! current.changed ) { | |
Serial.println( "no change"); | |
return; | |
} else { | |
Serial.println( "has change"); | |
if ( ( Time.now() - current.lastUpdateReceived ) < 10 ) { | |
Serial.println( "has change but delaying"); | |
return; | |
} | |
} | |
display.fillScreen(GxEPD_WHITE); | |
display.setTextColor(GxEPD_BLACK); | |
int startY = 100; | |
showTemperatureHumidity(&display, 150, startY); | |
showWind( &display, 450, startY ); | |
showForecastBar( &display, 50, startY+130); | |
showWords( &display, 300, startY+270); | |
showRain( &display, 300, startY+280); | |
display.update(); | |
Serial.printlnf( "end change flag now: %d", current.changed ); | |
current.changed = 0; | |
} | |
#define ICON_OFFSET 1 | |
void showForecast(GxEPD_Class *display, forecast *f, int x, int y) { | |
if ( strcmp(f->icon, "clear" ) == 0 ) { | |
display->drawBitmap(IconSun, x+20, y+ICON_OFFSET, 60, 60, GxEPD_BLACK, GxEPD::bm_invert); | |
} else if ( strcmp(f->icon, "rain" ) == 0 ) { | |
display->drawBitmap(IconRain, x+20, y+ICON_OFFSET, 60, 60, GxEPD_BLACK, GxEPD::bm_invert); | |
} else if ( strcmp(f->icon, "snow" ) == 0 ) { | |
display->drawBitmap(IconSnow, x+20, y+ICON_OFFSET, 60, 60, GxEPD_BLACK, GxEPD::bm_invert); | |
} else if ( strcmp(f->icon, "wind" ) == 0 ) { | |
display->drawBitmap(IconWind, x+20, y+ICON_OFFSET, 60, 60, GxEPD_BLACK, GxEPD::bm_invert); | |
} else if ( strcmp(f->icon, "fog" ) == 0 ) { | |
display->drawBitmap(IconFog, x+20, y+ICON_OFFSET, 60, 60, GxEPD_BLACK, GxEPD::bm_invert); | |
} else if ( strcmp(f->icon, "cloudy" ) == 0 ) { | |
display->drawBitmap(IconCloud, x+20, y+ICON_OFFSET, 60, 60, GxEPD_BLACK, GxEPD::bm_invert); | |
} else if ( strcmp(f->icon, "partly-cloudy" ) == 0 ) { | |
display->drawBitmap(IconCloudSun, x+20, y+ICON_OFFSET, 60, 60, GxEPD_BLACK, GxEPD::bm_invert); | |
} else { | |
Serial.printlnf( "******** unhandled %s", f->icon); | |
} | |
display->setCursor(x+50-( widthOf(f->day, FONT_M)/2), y); | |
display->setFont( FONT_M ); | |
display->print( f->day ); | |
char str[20]; | |
sprintf( str, "%s-%s", f->low, f->high); | |
display->setCursor(x+50-( widthOf(str, FONT_M )/2), y + 72); | |
display->setFont( FONT_M ); | |
display->print( str ); | |
sprintf( str, "%s%%", f->precip); | |
display->setCursor(x+50-( widthOf(f->precip, FONT_M)/2), y + 98); | |
display->setFont( FONT_M ); | |
display->print(str); | |
} | |
void showForecastBar(GxEPD_Class *display, int x, int y) { | |
showForecast( display, &(current.forecast0), x, y ); | |
showForecast( display, &(current.forecast1), x + 100, y ); | |
showForecast( display, &(current.forecast2), x + 200, y ); | |
showForecast( display, &(current.forecast3), x + 300, y ); | |
showForecast( display, &(current.forecast4), x + 400, y ); | |
} | |
void showWords(GxEPD_Class *display, int x, int y) { | |
//display->setCursor(x-( widthOf(current.words, FONT_M)/2), y); | |
//display->setFont( FONT_M ); | |
//display->print(current.words); | |
int fastWidth = widthOf(current.words, FONT_M); | |
if ( fastWidth < 600 ) { | |
display->setCursor( 300 - (fastWidth/2), y + 20); | |
display->print( current.words ); | |
return; | |
} | |
String allWords = String(current.words); | |
String word; | |
int curX = 20; | |
int curY = y; | |
int cur = 0; | |
int spaceLoc = 0; | |
while( ( spaceLoc = allWords.indexOf( ' ', cur) ) >= 0 ) { | |
word = allWords.substring(cur, spaceLoc); | |
cur = spaceLoc + 1; | |
int width = widthOf(word.c_str(), FONT_M); | |
Serial.printlnf( "word %s %d at %d", word.c_str(), width, curX); | |
if ( curX + width > 580 ) { | |
curX = 20; | |
curY = curY + 20; | |
} | |
display->setCursor(curX, curY); | |
display->print(word.c_str()); | |
curX = curX + width + 15; | |
} | |
display->setCursor(curX, curY); | |
display->print(allWords.substring(cur).c_str()); | |
} | |
void showRain(GxEPD_Class *display, int x, int y) { | |
char str[20]; | |
sprintf(str, "%s\"", current.rain_day); | |
display->setCursor(x - ( widthOf( str, FONT_L ) / 2 ), y+50); | |
display->setFont(FONT_L); | |
display->print(str); | |
} | |
void showTemperatureHumidity(GxEPD_Class *display, int x, int y) { | |
display->setFont(FONT_XL); | |
char str[40]; | |
sprintf(str, "%s", current.temperature); | |
display->setCursor(x - ( widthOf( str, FONT_XL ) / 2 ), y); | |
display->print(str); | |
drawTemperatureTrend(display, x + ( widthOf( str, FONT_XL ) / 2 ) + 20, y ); | |
display->setFont(FONT_M); | |
sprintf(str, "%s - %s", current.temperature_min, current.temperature_max); | |
display->setCursor(x - ( widthOf( str, FONT_M ) / 2 ), y + 35 ); | |
display->print(str); | |
display->setFont(FONT_M); | |
sprintf(str, "%s%%/%s\"Hg", current.humidity, current.pressure); | |
display->setCursor(x - ( widthOf( str, FONT_M ) / 2 ), y + 70); | |
display->print(str); | |
} | |
void drawTemperatureTrend(GxEPD_Class *display, int x, int y) { | |
if ( strcmp( current.temperature_trend, "up" ) == 0 ) { | |
showUpTrend(display, x, y - 32 ); | |
} else if ( strcmp( current.temperature_trend, "down" ) == 0 ) { | |
showDownTrend(display, x, y - 30 ); | |
} else { | |
showStableTrend(display, x, y - 28); | |
} | |
} | |
void showUpTrend(GxEPD_Class *display, int x, int y) { | |
display->fillTriangle( x, y, x+40, y, x+20, y-20, GxEPD_BLACK); | |
} | |
void showDownTrend(GxEPD_Class *display, int x, int y) { | |
display->fillTriangle( x, y, x+40, y, x+20, y+20, GxEPD_BLACK); | |
display->fillTriangle( x, y, x+40, y, x+20, y+20, GxEPD_BLACK); | |
} | |
void showStableTrend(GxEPD_Class *display, int x, int y) { | |
display->drawLine( x, y, x+40, y, GxEPD_BLACK); | |
display->drawLine( x, y+1, x+40, y+1, GxEPD_BLACK); | |
display->drawLine( x, y-1, x+40, y-1, GxEPD_BLACK); | |
} | |
#define COMPASS_SIZE 80 | |
void showWind(GxEPD_Class *display, int x, int y) { | |
Serial.printf("wind angle str [%s]\n", current.wind_angle); | |
int angle = atoi( current.wind_angle ); | |
display->fillCircle(x, y, COMPASS_SIZE, GxEPD_BLACK ); | |
display->fillCircle(x, y, COMPASS_SIZE - 5, GxEPD_WHITE ); | |
Serial.printf( "wind angle: %d\n", angle ); | |
int windSpeed = atoi( current.wind_speed ); | |
int gustSpeed = atoi( current.wind_gust ); | |
if ( windSpeed > 0 || gustSpeed >= 2 ) { | |
drawWindDireciontIndicator( display, x, y, COMPASS_SIZE - 2, angle, GxEPD_BLACK ); | |
} | |
int textOffsetY = y - 10; | |
char str[30]; | |
sprintf( str, "%s-%s", current.wind_speed, current.wind_gust); | |
display->setCursor( x - (widthOf( str, FONT_L) / 2), textOffsetY ); | |
display->setFont(FONT_L); | |
display->print( str ); | |
sprintf( str, "max: %s", current.wind_max); | |
display->setCursor( x - ( widthOf( str, FONT_M)/2), textOffsetY + 32 ); | |
display->setFont(FONT_M); | |
display->print(str); | |
display->setCursor( x - ( widthOf( "mph", FONT_M)/2), textOffsetY + 64 ); | |
display->setFont(FONT_M); | |
display->print("mph"); | |
} | |
int widthOf(const char *str, const GFXfont *font) { | |
int w = 0; | |
int i = 0; | |
while ( str[i] != 0 ) { | |
int c = str[i] - ' '; | |
if ( str[i + 1] != 0 ) { | |
w += font->glyph[c].xAdvance; | |
} else { | |
w += font->glyph[c].width; | |
} | |
++i; | |
} | |
return w; | |
} | |
void handleMessage(const char *event, const char *data) { | |
Serial.printf("handle message %s = %s\n", event, data); | |
if ( strcmp( event, "mrweathery.temperature" ) == 0 ) { | |
setData( current.temperature, data ); | |
} else if ( strcmp( event, "mrweathery.temperature.trend" ) == 0 ) { | |
setData( current.temperature_trend, data ); | |
} else if ( strcmp( event, "mrweathery.temperature.max" ) == 0 ) { | |
setData( current.temperature_max, data ); | |
} else if ( strcmp( event, "mrweathery.temperature.min" ) == 0 ) { | |
setData( current.temperature_min, data ); | |
} else if ( strcmp( event, "mrweathery.humidity" ) == 0 ) { | |
setData( current.humidity, data ); | |
} else if ( strcmp( event, "mrweathery.pressure" ) == 0 ) { | |
setData( current.pressure, data ); | |
} else if ( strcmp( event, "mrweathery.wind.strength" ) == 0 ) { | |
setData( current.wind_speed, data ); | |
} else if ( strcmp( event, "mrweathery.wind.strength.max" ) == 0 ) { | |
setData( current.wind_max, data ); | |
} else if ( strcmp( event, "mrweathery.wind.angle" ) == 0 ) { | |
setData( current.wind_angle, data ); | |
} else if ( strcmp( event, "mrweathery.gust.strength" ) == 0 ) { | |
setData( current.wind_gust, data ); | |
} else if ( strcmp( event, "mrweathery.rain.hour") == 0 ) { | |
setData( current.rain_hour, data ); | |
} else if ( strcmp( event, "mrweathery.rain.day" ) == 0 ) { | |
setData( current.rain_day, data ); | |
} else if ( strcmp( event, "mrweathery.words" ) == 0 ) { | |
setData( current.words, data ); | |
} else { | |
String eventStr = String(event); | |
if ( eventStr.startsWith("mrweathery.forecast")) { | |
handleForecast(eventStr, String(data)); | |
} | |
} | |
} | |
void processForecast(forecast *f, String data) { | |
//strcpy(f->day, data.substring(0,3).c_str()); | |
setData(f->day, String( data.substring(0,3).toLowerCase() + "." ).c_str()); | |
//String daystr = String( data.substring(0,3).c_str() ); | |
//daystr.concat("."); | |
//daystr.toLowerCase(); | |
//char daystr[5]; | |
//strcpy(daystr, data.substring(0,3).c_str()); | |
//daystr[3] = '.'; | |
//setData( f->day, daystr); | |
int colon1 = data.indexOf(':'); | |
int colon2 = data.indexOf(':', colon1+1); | |
//strcpy(f->icon, data.substring(colon1+1, colon2).c_str()); | |
setData(f->icon, data.substring(colon1+1, colon2).c_str()); | |
colon1 = colon2; | |
colon2 = data.indexOf(':', colon1+1); | |
setData(f->high, data.substring(colon1+1, colon2).c_str()); | |
colon1 = colon2; | |
colon2 = data.indexOf(':', colon1+1); | |
setData(f->low, data.substring(colon1+1, colon2).c_str()); | |
colon1 = colon2; | |
setData(f->precip, data.substring(colon1+1).c_str()); | |
Serial.printlnf( "forecast result: %s // %s // %s // %s // %s", f->day, f->icon, f->high, f->low, f->precip); | |
} | |
void handleForecast(String event, String data) { | |
Serial.printlnf("%s == %s", event.c_str(), data.c_str()); | |
if ( event.endsWith(".0") ) { | |
processForecast( &(current.forecast0), data ); | |
} else if ( event.endsWith( ".1" ) ) { | |
processForecast( &(current.forecast1), data ); | |
} else if ( event.endsWith( ".2" ) ) { | |
processForecast( &(current.forecast2), data ); | |
} else if ( event.endsWith( ".3" ) ) { | |
processForecast( &(current.forecast3), data ); | |
} else if ( event.endsWith( ".4" ) ) { | |
processForecast( &(current.forecast4), data ); | |
} | |
} | |
void setData(char *dest, const char *data) { | |
if ( strcmp( dest, data ) != 0 ) { | |
strcpy(dest, data ); | |
Serial.printf( "marking as changed due to %s\n", data); | |
current.changed = 1; | |
current.lastUpdateReceived = Time.now(); | |
} | |
} | |
void drawWindDireciontIndicator(GxEPD_Class *display, int cx, int cy, int r, int angle, int color) { | |
double leftRadians = ( ( angle - 90) - 12 ) * 3.1415 / 180; | |
double rightRadians = ( ( angle - 90 ) + 12 ) * 3.1415 / 180; | |
double centerRadians = ( ( angle - 90 ) ) * 3.1415 / 180; | |
int leftX = cx + ((r+10) * cos(leftRadians)); | |
int leftY = cy + ((r+10) * sin(leftRadians)); | |
int rightX = cx + ((r+10) * cos(rightRadians)); | |
int rightY = cy + ((r+10) * sin(rightRadians)); | |
int centerX = cx + ((r-20) * cos(centerRadians)); | |
int centerY = cy + ((r-20) * sin(centerRadians)); | |
display->fillTriangle( leftX, leftY, rightX, rightY, centerX, centerY, color); | |
leftRadians = ( ( angle - 90) - 7 ) * 3.1415 / 180; | |
rightRadians = ( ( angle - 90 ) + 7 ) * 3.1415 / 180; | |
leftX = cx + ((r+4) * cos(leftRadians)); | |
leftY = cy + ((r+4) * sin(leftRadians)); | |
rightX = cx + ((r+4) * cos(rightRadians)); | |
rightY = cy + ((r+4) * sin(rightRadians)); | |
centerX = cx + ((r-12) * cos(centerRadians)); | |
centerY = cy + ((r-12) * sin(centerRadians)); | |
display->fillTriangle( leftX, leftY, rightX, rightY, centerX, centerY, GxEPD_WHITE); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment