@ -1,46 +1,41 @@
# ifndef __MONOCHROME_DISPLAY__
# define __MONOCHROME_DISPLAY__
/* esp8266 : SCL = 5, SDA = 4 */
/* ewsp32 : SCL = 22, SDA = 21 */
# if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106)
# include <U8g2lib.h>
# ifdef ENA_NOKIA
# define DISP_PROGMEM U8X8_PROGMEM
# else // ENA_SSD1306 || ENA_SH1106
# define DISP_PROGMEM PROGMEM
# endif
# include <Timezone.h>
# include "../../utils/helper.h"
# include "../../hm/hmSystem.h"
# define DISP_DEFAULT_TIMEOUT 60 // in seconds
static uint8_t bmp_logo [ ] PROGMEM = {
B00000000 , B00000000 , // ................
B11101100 , B00110111 , // ..##.######.##..
B11101100 , B00110111 , // ..##.######.##..
B11100000 , B00000111 , // .....######.....
B11010000 , B00001011 , // ....#.####.#....
B10011000 , B00011001 , // ...##..##..##...
B10000000 , B00000001 , // .......##.......
B00000000 , B00000000 , // ................
B01111000 , B00011110 , // ...####..####...
B11111100 , B00111111 , // ..############..
B01111100 , B00111110 , // ..#####..#####..
B00000000 , B00000000 , // ................
B11111100 , B00111111 , // ..############..
B11111110 , B01111111 , // .##############.
B01111110 , B01111110 , // .######..######.
B00000000 , B00000000 // ................
B00000000 , B00000000 , // ................
B11101100 , B00110111 , // ..##.######.##..
B11101100 , B00110111 , // ..##.######.##..
B11100000 , B00000111 , // .....######.....
B11010000 , B00001011 , // ....#.####.#....
B10011000 , B00011001 , // ...##..##..##...
B10000000 , B00000001 , // .......##.......
B00000000 , B00000000 , // ................
B01111000 , B00011110 , // ...####..####...
B11111100 , B00111111 , // ..############..
B01111100 , B00111110 , // ..#####..#####..
B00000000 , B00000000 , // ................
B11111100 , B00111111 , // ..############..
B11111110 , B01111111 , // .##############.
B01111110 , B01111110 , // .######..######.
B00000000 , B00000000 // ................
} ;
static uint8_t bmp_arrow [ ] DISP_ PROGMEM = {
static uint8_t bmp_arrow [ ] PROGMEM = {
B00000000 , B00011100 , B00011100 , B00001110 , B00001110 , B11111110 , B01111111 ,
B01110000 , B01110000 , B00110000 , B00111000 , B00011000 , B01111111 , B00111111 ,
B00011110 , B00001110 , B00000110 , B00000000 , B00000000 , B00000000 , B00000000 } ;
B00011110 , B00001110 , B00000110 , B00000000 , B00000000 , B00000000 , B00000000
} ;
static TimeChangeRule CEST = { " CEST " , Last , Sun , Mar , 2 , 120 } ; // Central European Summer Time
static TimeChangeRule CET = { " CET " , Last , Sun , Oct , 3 , 60 } ; // Central European Standard Tim
@ -48,31 +43,41 @@ static TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; // Central Eu
template < class HMSYSTEM >
class MonochromeDisplay {
public :
# ifdef ENA_NOKIA
MonochromeDisplay ( ) : mDisplay ( U8G2_R0 , 5 , 4 , 16 ) , mCE ( CEST , CET ) {
mNewPayload = false ;
mExtra = 0 ;
}
# else // ENA_SSD1306 || ENA_SH1106
MonochromeDisplay ( ) : mDisplay ( U8G2_R0 , SCL , SDA , U8X8_PIN_NONE ) , mCE ( CEST , CET ) {
mNewPayload = false ;
mExtra = 0 ;
}
# endif
uint8_t dispContrast = 60 ;
void setup ( display_t * cfg , HMSYSTEM * sys , uint32_t * utcTs ) {
MonochromeDisplay ( ) : mCE ( CEST , CET ) { }
void setup ( display_t * cfg , HMSYSTEM * sys , uint32_t * utcTs , uint8_t disp_reset , const char * version ) {
mCfg = cfg ;
mSys = sys ;
mUtcTs = utcTs ;
memset ( mToday , 0 , sizeof ( float ) * MAX_NUM_INVERTERS ) ;
memset ( mTotal , 0 , sizeof ( float ) * MAX_NUM_INVERTERS ) ;
mLastHour = 25 ;
mDisplay . begin ( ) ;
ShowInfoText ( " booting... " ) ;
}
mNewPayload = false ;
mLoopCnt = 0 ;
mTimeout = DISP_DEFAULT_TIMEOUT ; // power off timeout (after inverters go offline)
if ( mCfg - > type ) {
switch ( mCfg - > type ) {
case 1 :
mDisplay = new U8G2_PCD8544_84X48_F_4W_HW_SPI ( U8G2_R0 , mCfg - > pin0 , mCfg - > pin1 , disp_reset ) ;
break ;
case 2 :
mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C ( U8G2_R0 , disp_reset , mCfg - > pin0 , mCfg - > pin1 ) ;
break ;
case 3 :
mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C ( U8G2_R0 , disp_reset , mCfg - > pin0 , mCfg - > pin1 ) ;
break ;
}
mDisplay - > begin ( ) ;
void loop ( void ) {
mIsLarge = ( ( mDisplay - > getWidth ( ) > 120 ) & & ( mDisplay - > getHeight ( ) > 60 ) ) ;
calcLineHeights ( ) ;
mDisplay - > clearBuffer ( ) ;
mDisplay - > setContrast ( mCfg - > contrast ) ;
printText ( " Ahoy! " , 0 , 35 ) ;
printText ( version , 3 , 46 ) ;
mDisplay - > sendBuffer ( ) ;
}
}
void payloadEventListener ( uint8_t cmd ) {
@ -80,244 +85,136 @@ class MonochromeDisplay {
}
void tickerSecond ( ) {
static int cnt = 1 ;
if ( mNewPayload | | ! ( cnt % 10 ) ) {
cnt = 1 ;
if ( mCfg - > pwrSaveAtIvOffline ) {
if ( mTimeout ! = 0 )
mTimeout - - ;
}
if ( mNewPayload | | ( ( + + mLoopCnt % 10 ) = = 0 ) ) {
mNewPayload = false ;
mLoopCnt = 0 ;
DataScreen ( ) ;
}
else
cnt + + ;
}
private :
void ShowInfoText ( const char * txt ) {
/* u8g2_font_open_iconic_embedded_2x_t 'D' + 'G' + 'J' */
mDisplay . clear ( ) ;
mDisplay . firstPage ( ) ;
do {
const char * e ;
const char * p = txt ;
int y = 10 ;
mDisplay . setFont ( u8g2_font_5x8_tr ) ;
while ( 1 ) {
for ( e = p + 1 ; ( * e & & ( * e ! = ' \n ' ) ) ; e + + ) ;
size_t len = e - p ;
mDisplay . setCursor ( 2 , y ) ;
String res = ( ( String ) p ) . substring ( 0 , len ) ;
mDisplay . print ( res ) ;
if ( ! * e )
break ;
p = e + 1 ;
y + = 12 ;
void DataScreen ( ) {
if ( mCfg - > type = = 0 )
return ;
if ( * mUtcTs = = 0 )
return ;
float totalPower = 0 ;
float totalYieldDay = 0 ;
float totalYieldTotal = 0 ;
bool isprod = false ;
Inverter < > * iv ;
record_t < > * rec ;
for ( uint8_t i = 0 ; i < mSys - > getNumInverters ( ) ; i + + ) {
iv = mSys - > getInverterByPos ( i ) ;
rec = iv - > getRecordStruct ( RealTimeRunData_Debug ) ;
if ( iv = = NULL )
continue ;
if ( iv - > isProducing ( * mUtcTs ) )
isprod = true ;
totalPower + = iv - > getChannelFieldValue ( CH0 , FLD_PAC , rec ) ;
totalYieldDay + = iv - > getChannelFieldValue ( CH0 , FLD_YD , rec ) ;
totalYieldTotal + = iv - > getChannelFieldValue ( CH0 , FLD_YT , rec ) ;
}
mDisplay - > clearBuffer ( ) ;
// Logos
// pxMovement +x (0 - 6 px)
uint8_t ex = ( _mExtra % 7 ) ;
if ( isprod ) {
mDisplay - > drawXBMP ( 5 + ex , 1 , 8 , 17 , bmp_arrow ) ;
if ( mCfg - > logoEn )
mDisplay - > drawXBMP ( mDisplay - > getWidth ( ) - 24 + ex , 2 , 16 , 16 , bmp_logo ) ;
}
if ( ( totalPower > 0 ) & & isprod ) {
mTimeout = DISP_DEFAULT_TIMEOUT ;
mDisplay - > setPowerSave ( false ) ;
mDisplay - > setContrast ( mCfg - > contrast ) ;
if ( totalPower > 999 )
snprintf ( _fmtText , sizeof ( _fmtText ) , " %2.1f kW " , ( totalPower / 1000 ) ) ;
else
snprintf ( _fmtText , sizeof ( _fmtText ) , " %3.0f W " , totalPower ) ;
printText ( _fmtText , 0 , 20 ) ;
} else {
printText ( " offline " , 0 , 25 ) ;
if ( mCfg - > pwrSaveAtIvOffline ) {
if ( mTimeout = = 0 )
mDisplay - > setPowerSave ( true ) ;
}
mDisplay . sendBuffer ( ) ;
} while ( mDisplay . nextPage ( ) ) ;
}
}
snprintf ( _fmtText , sizeof ( _fmtText ) , " today: %4.0f Wh " , totalYieldDay ) ;
printText ( _fmtText , 1 ) ;
snprintf ( _fmtText , sizeof ( _fmtText ) , " total: %.1f kWh " , totalYieldTotal ) ;
printText ( _fmtText , 2 ) ;
void DataScreen ( void ) {
String timeStr = ah : : getDateTimeStr ( mCE . toLocal ( * mUtcTs ) ) . substring ( 2 , 16 ) ;
int hr = timeStr . substring ( 9 , 2 ) . toInt ( ) ;
IPAddress ip = WiFi . localIP ( ) ;
float totalYield = 0.0 , totalYieldToday = 0.0 , totalActual = 0.0 ;
char fmtText [ 32 ] ;
int ucnt = 0 , num_inv = 0 ;
unsigned int pow_i [ MAX_NUM_INVERTERS ] ;
memset ( pow_i , 0 , sizeof ( unsigned int ) * MAX_NUM_INVERTERS ) ;
if ( hr < mLastHour ) // next day ? reset today-values
memset ( mToday , 0 , sizeof ( float ) * MAX_NUM_INVERTERS ) ;
mLastHour = hr ;
for ( uint8_t id = 0 ; id < mSys - > getNumInverters ( ) ; id + + ) {
Inverter < > * iv = mSys - > getInverterByPos ( id ) ;
if ( NULL ! = iv ) {
record_t < > * rec = iv - > getRecordStruct ( RealTimeRunData_Debug ) ;
uint8_t pos ;
uint8_t list [ ] = { FLD_PAC , FLD_YT , FLD_YD } ;
for ( uint8_t fld = 0 ; fld < 3 ; fld + + ) {
pos = iv - > getPosByChFld ( CH0 , list [ fld ] , rec ) ;
int isprod = iv - > isProducing ( * mUtcTs ) ;
if ( fld = = 1 )
{
if ( isprod )
mTotal [ num_inv ] = iv - > getValue ( pos , rec ) ;
totalYield + = mTotal [ num_inv ] ;
}
if ( fld = = 2 )
{
if ( isprod )
mToday [ num_inv ] = iv - > getValue ( pos , rec ) ;
totalYieldToday + = mToday [ num_inv ] ;
}
if ( ( fld = = 0 ) & & isprod )
{
pow_i [ num_inv ] = iv - > getValue ( pos , rec ) ;
totalActual + = iv - > getValue ( pos , rec ) ;
ucnt + + ;
}
}
num_inv + + ;
}
if ( ! ( _mExtra % 10 ) & & ( ip ) ) {
printText ( ip . toString ( ) . c_str ( ) , 3 ) ;
} else {
// Get current time
if ( mIsLarge )
printText ( ah : : getDateTimeStr ( mCE . toLocal ( * mUtcTs ) ) . c_str ( ) , 3 ) ;
else
printText ( ah : : getTimeStr ( mCE . toLocal ( * mUtcTs ) ) . c_str ( ) , 3 ) ;
}
mDisplay - > sendBuffer ( ) ;
_mExtra + + ;
}
void calcLineHeights ( ) {
uint8_t yOff = 0 ;
for ( uint8_t i = 0 ; i < 4 ; i + + ) {
setFont ( i ) ;
yOff + = ( mDisplay - > getMaxCharHeight ( ) + 1 ) ;
mLineOffsets [ i ] = yOff ;
}
}
inline void setFont ( uint8_t line ) {
switch ( line ) {
case 0 : mDisplay - > setFont ( ( mIsLarge ) ? u8g2_font_ncenB14_tr : u8g2_font_lubBI14_tr ) ; break ;
case 3 : mDisplay - > setFont ( u8g2_font_5x8_tr ) ; break ;
default : mDisplay - > setFont ( ( mIsLarge ) ? u8g2_font_ncenB10_tr : u8g2_font_5x8_tr ) ; break ;
}
/* u8g2_font_open_iconic_embedded_2x_t 'D' + 'G' + 'J' */
mDisplay . clear ( ) ;
mDisplay . firstPage ( ) ;
do {
# ifdef ENA_NOKIA
if ( ucnt ) {
//=====> Actual Production
mDisplay . drawXBMP ( 10 , 1 , 8 , 17 , bmp_arrow ) ;
mDisplay . setFont ( u8g2_font_logisoso16_tr ) ;
mDisplay . setCursor ( 25 , 17 ) ;
if ( totalActual > 999 ) {
sprintf ( fmtText , " %2.1f " , ( totalActual / 1000 ) ) ;
mDisplay . print ( String ( fmtText ) + F ( " kW " ) ) ;
} else {
sprintf ( fmtText , " %3.0f " , totalActual ) ;
mDisplay . print ( String ( fmtText ) + F ( " W " ) ) ;
}
//<=======================
}
else
{
//=====> Offline
mDisplay . setFont ( u8g2_font_logisoso16_tr ) ;
mDisplay . setCursor ( 10 , 17 ) ;
mDisplay . print ( String ( F ( " offline " ) ) ) ;
//<=======================
}
mDisplay . drawHLine ( 2 , 20 , 78 ) ;
mDisplay . setFont ( u8g2_font_5x8_tr ) ;
mDisplay . setCursor ( 5 , 29 ) ;
if ( ( num_inv < 2 ) | | ! ( mExtra % 2 ) )
{
sprintf ( fmtText , " %4.0f " , totalYieldToday ) ;
mDisplay . print ( F ( " today " ) + String ( fmtText ) + F ( " Wh " ) ) ;
mDisplay . setCursor ( 5 , 37 ) ;
sprintf ( fmtText , " %.1f " , totalYield ) ;
mDisplay . print ( F ( " total " ) + String ( fmtText ) + F ( " kWh " ) ) ;
}
else
{
int id1 = ( mExtra / 2 ) % ( num_inv - 1 ) ;
if ( pow_i [ id1 ] )
mDisplay . print ( F ( " # " ) + String ( id1 + 1 ) + F ( " " ) + String ( pow_i [ id1 ] ) + F ( " W " ) ) ;
else
mDisplay . print ( F ( " # " ) + String ( id1 + 1 ) + F ( " ----- " ) ) ;
mDisplay . setCursor ( 5 , 37 ) ;
if ( pow_i [ id1 + 1 ] )
mDisplay . print ( F ( " # " ) + String ( id1 + 2 ) + F ( " " ) + String ( pow_i [ id1 + 1 ] ) + F ( " W " ) ) ;
else
mDisplay . print ( F ( " # " ) + String ( id1 + 2 ) + F ( " ----- " ) ) ;
}
if ( ! ( mExtra % 10 ) & & ip ) {
mDisplay . setCursor ( 5 , 47 ) ;
mDisplay . print ( ip . toString ( ) ) ;
}
else {
mDisplay . setCursor ( 5 , 47 ) ;
mDisplay . print ( timeStr ) ;
}
# else // ENA_SSD1306
mDisplay . setContrast ( mCfg - > contrast ) ;
// pxZittern in +x (0 - 8 px)
int ex = 2 * ( mExtra % 5 ) ;
mDisplay . drawXBM ( 100 + ex , 2 , 16 , 16 , bmp_logo ) ;
mDisplay . setFont ( u8g2_font_ncenB08_tr ) ;
if ( ucnt ) {
//=====> Actual Production
mDisplay . setPowerSave ( false ) ;
displaySleep = false ;
mDisplay . setFont ( u8g2_font_logisoso18_tr ) ;
mDisplay . drawXBM ( 10 + ex , 2 , 8 , 17 , bmp_arrow ) ;
mDisplay . setCursor ( 25 + ex , 20 ) ;
if ( totalActual > 999 ) {
sprintf ( fmtText , " %2.1f " , ( totalActual / 1000 ) ) ;
mDisplay . print ( String ( fmtText ) + F ( " kW " ) ) ;
} else {
sprintf ( fmtText , " %3.0f " , totalActual ) ;
mDisplay . print ( String ( fmtText ) + F ( " W " ) ) ;
}
//<=======================
}
else
{
//=====> Offline
if ( ! displaySleep ) {
displaySleepTimer = millis ( ) ;
displaySleep = true ;
}
mDisplay . setFont ( u8g2_font_logisoso18_tr ) ;
mDisplay . setCursor ( 10 + ex , 20 ) ;
mDisplay . print ( String ( F ( " offline " ) ) ) ;
if ( mCfg - > pwrSaveAtIvOffline ) {
if ( ( millis ( ) - displaySleepTimer ) > displaySleepDelay )
mDisplay . setPowerSave ( true ) ;
}
//<=======================
}
mDisplay . drawLine ( 2 + ex , 23 , 123 , 23 ) ;
mDisplay . setFont ( u8g2_font_ncenB10_tr ) ;
mDisplay . setCursor ( 2 + ex , 36 ) ;
if ( ( num_inv < 2 ) | | ! ( mExtra % 2 ) )
{
//=====> Today & Total Production
sprintf ( fmtText , " %5.0f " , totalYieldToday ) ;
mDisplay . print ( F ( " today: " ) + String ( fmtText ) + F ( " Wh " ) ) ;
mDisplay . setCursor ( 2 + ex , 50 ) ;
sprintf ( fmtText , " %.1f " , totalYield ) ;
mDisplay . print ( F ( " total: " ) + String ( fmtText ) + F ( " kWh " ) ) ;
//<=======================
} else {
int id1 = ( mExtra / 2 ) % ( num_inv - 1 ) ;
if ( pow_i [ id1 ] )
mDisplay . print ( F ( " # " ) + String ( id1 + 1 ) + F ( " " ) + String ( pow_i [ id1 ] ) + F ( " W " ) ) ;
else
mDisplay . print ( F ( " # " ) + String ( id1 + 1 ) + F ( " ----- " ) ) ;
mDisplay . setCursor ( 5 + ex , 50 ) ;
if ( pow_i [ id1 + 1 ] )
mDisplay . print ( F ( " # " ) + String ( id1 + 2 ) + F ( " " ) + String ( pow_i [ id1 + 1 ] ) + F ( " W " ) ) ;
else
mDisplay . print ( F ( " # " ) + String ( id1 + 2 ) + F ( " ----- " ) ) ;
}
mDisplay . setFont ( u8g2_font_5x8_tr ) ;
mDisplay . setCursor ( 5 + ex , 63 ) ;
if ( ! ( mExtra % 10 ) & & ip )
mDisplay . print ( ip . toString ( ) ) ;
else
mDisplay . print ( timeStr ) ;
# endif
mDisplay . sendBuffer ( ) ;
} while ( mDisplay . nextPage ( ) ) ;
delay ( 200 ) ;
mExtra + + ;
}
void printText ( const char * text , uint8_t line , uint8_t dispX = 5 ) {
if ( ! mIsLarge )
dispX = 5 ;
setFont ( line ) ;
if ( mCfg - > pxShift )
dispX + = ( _mExtra % 7 ) ; // add pixel movement
mDisplay - > drawStr ( dispX , mLineOffsets [ line ] , text ) ;
}
// private member variables
# ifdef ENA_NOKIA
U8G2_PCD8544_84X48_1_4W_HW_SPI mDisplay ;
# elif defined(ENA_SSD1306)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C mDisplay ;
# elif defined(ENA_SH1106)
U8G2_SH1106_128X64_NONAME_F_HW_I2C mDisplay ;
# endif
int mExtra ;
U8G2 * mDisplay ;
uint8_t _mExtra ;
uint16_t mTimeout ; // interval at which to power save (milliseconds)
char _fmtText [ 32 ] ;
bool mNewPayload ;
float mTotal [ MAX_NUM_INVERTERS ] ;
float mToday [ MAX_NUM_INVERTERS ] ;
bool mIsLarge ;
uint8_t mLoopCnt ;
uint32_t * mUtcTs ;
int mLastHour ;
uint8_t mLineOffsets [ 5 ] ;
display_t * mCfg ;
HMSYSTEM * mSys ;
Timezone mCE ;
bool displaySleep ;
uint32_t displaySleepTimer ;
const uint32_t displaySleepDelay = 60000 ;
} ;
# endif
# endif /*__MONOCHROME_DISPLAY__*/