mirror of https://github.com/lumapu/ahoy.git
32 changed files with 1577 additions and 973 deletions
@ -0,0 +1,285 @@ |
|||
#ifndef __MONOCHROME_DISPLAY__ |
|||
#define __MONOCHROME_DISPLAY__ |
|||
|
|||
#if defined(ENA_NOKIA) || defined(ENA_SSD1306) |
|||
#ifdef ENA_NOKIA |
|||
#include <U8g2lib.h> |
|||
#define DISP_PROGMEM U8X8_PROGMEM |
|||
#else // ENA_SSD1306
|
|||
/* esp8266 : SCL = 5, SDA = 4 */ |
|||
/* ewsp32 : SCL = 22, SDA = 21 */ |
|||
#include <Wire.h> |
|||
#include <SSD1306Wire.h> |
|||
#define DISP_PROGMEM PROGMEM |
|||
#endif |
|||
|
|||
#include <Timezone.h> |
|||
|
|||
#include "../../utils/helper.h" |
|||
#include "../../hm/hmSystem.h" |
|||
|
|||
static uint8_t bmp_arrow[] DISP_PROGMEM = { |
|||
B00000000, B00011100, B00011100, B00001110, B00001110, B11111110, B01111111, |
|||
B01110000, B01110000, B00110000, B00111000, B00011000, B01111111, B00111111, |
|||
B00011110, B00001110, B00000110, B00000000, B00000000, B00000000, B00000000} ; |
|||
|
|||
template<class HMSYSTEM> |
|||
class MonochromeDisplay { |
|||
public: |
|||
#if defined(ENA_NOKIA) |
|||
MonochromeDisplay() : mDisplay(U8G2_R0,5,4,16) { |
|||
mNewPayload = false; |
|||
mExtra = 0; |
|||
} |
|||
#else // ENA_SSD1306
|
|||
MonochromeDisplay() : mDisplay(0x3c, SDA, SCL) { |
|||
mNewPayload = false; |
|||
mExtra = 0; |
|||
mRx = 0; |
|||
mUp = 1; |
|||
} |
|||
#endif |
|||
|
|||
void setup(HMSYSTEM *sys, uint32_t *utcTs) { |
|||
mSys = sys; |
|||
mUtcTs = utcTs; |
|||
#if defined(ENA_NOKIA) |
|||
mDisplay.begin(); |
|||
ShowInfoText("booting..."); |
|||
#else |
|||
mDisplay.init(); |
|||
mDisplay.flipScreenVertically(); |
|||
mDisplay.setContrast(63); |
|||
mDisplay.setBrightness(63); |
|||
|
|||
mDisplay.clear(); |
|||
mDisplay.setFont(ArialMT_Plain_24); |
|||
mDisplay.setTextAlignment(TEXT_ALIGN_CENTER_BOTH); |
|||
|
|||
mDisplay.drawString(64,22,"Starting..."); |
|||
mDisplay.display(); |
|||
mDisplay.setTextAlignment(TEXT_ALIGN_LEFT); |
|||
#endif |
|||
} |
|||
|
|||
void loop(void) { |
|||
|
|||
} |
|||
|
|||
void payloadEventListener(uint8_t cmd) { |
|||
mNewPayload = true; |
|||
} |
|||
|
|||
void tickerSecond() { |
|||
if(mNewPayload) { |
|||
mNewPayload = false; |
|||
DataScreen(); |
|||
} |
|||
} |
|||
|
|||
private: |
|||
#if defined(ENA_NOKIA) |
|||
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; |
|||
} |
|||
mDisplay.sendBuffer(); |
|||
} while( mDisplay.nextPage() ); |
|||
} |
|||
#endif |
|||
|
|||
void DataScreen(void) { |
|||
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; // Central European Summer Time
|
|||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; // Central European Standard Time
|
|||
Timezone CE(CEST, CET); |
|||
String timeStr = ah::getDateTimeStr(CE.toLocal(*mUtcTs)).substring(2, 22); |
|||
IPAddress ip = WiFi.localIP(); |
|||
float totalYield = 0.000, totalYieldToday = 0.000, 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 ); |
|||
|
|||
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}; |
|||
|
|||
num_inv++; |
|||
|
|||
if ( !iv->isProducing(*mUtcTs,rec) ) |
|||
continue; |
|||
|
|||
for (uint8_t fld = 0; fld < 3; fld++) { |
|||
pos = iv->getPosByChFld(CH0, list[fld],rec); |
|||
|
|||
if(fld == 1) |
|||
totalYield += iv->getValue(pos,rec); |
|||
if(fld == 2) |
|||
totalYieldToday += iv->getValue(pos,rec); |
|||
if(fld == 0) |
|||
{ |
|||
pow_i[num_inv-1] = iv->getValue(pos,rec); |
|||
totalActual += iv->getValue(pos,rec); |
|||
} |
|||
} |
|||
ucnt++; |
|||
} |
|||
} |
|||
|
|||
/* u8g2_font_open_iconic_embedded_2x_t 'D' + 'G' + 'J' */ |
|||
mDisplay.clear(); |
|||
#if defined(ENA_NOKIA) |
|||
mDisplay.firstPage(); |
|||
do { |
|||
if(ucnt) { |
|||
mDisplay.drawXBMP(10,0,8,17,bmp_arrow); |
|||
mDisplay.setFont(u8g2_font_logisoso16_tr); |
|||
mDisplay.setCursor(25,16); |
|||
sprintf(fmtText,"%3.0f",totalActual); |
|||
mDisplay.print(String(fmtText)+F(" W")); |
|||
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 |
|||
{ |
|||
if( pow_i[0] ) |
|||
mDisplay.print(F("#1 ")+String(pow_i[0])+F(" W")); |
|||
else |
|||
mDisplay.print(F("#1 -----")); |
|||
mDisplay.setCursor(5,37); |
|||
if( pow_i[1] ) |
|||
mDisplay.print(F("#2 ")+String(pow_i[1])+F(" W")); |
|||
else |
|||
mDisplay.print(F("#2 -----")); |
|||
} |
|||
} |
|||
else { |
|||
mDisplay.setFont(u8g2_font_logisoso16_tr); |
|||
mDisplay.setCursor(30,30); |
|||
mDisplay.print(F("off")); |
|||
mDisplay.setFont(u8g2_font_5x8_tr); |
|||
} |
|||
if ( !(mExtra%10) && ip ) { |
|||
mDisplay.setCursor(5,47); |
|||
mDisplay.print(ip.toString()); |
|||
} |
|||
else { |
|||
mDisplay.setCursor(0,47); |
|||
mDisplay.print(timeStr); |
|||
} |
|||
|
|||
mDisplay.sendBuffer(); |
|||
} while( mDisplay.nextPage() ); |
|||
mExtra++; |
|||
#else // ENA_SSD1306
|
|||
if(mUp) { |
|||
mRx += 2; |
|||
if(mRx >= 20) |
|||
mUp = 0; |
|||
} else { |
|||
mRx -= 2; |
|||
if(mRx <= 0) |
|||
mUp = 1; |
|||
} |
|||
int ex = 2*( mExtra % 5 ); |
|||
|
|||
if(ucnt) { |
|||
mDisplay.setBrightness(63); |
|||
mDisplay.drawXbm(10+ex,5,8,17,bmp_arrow); |
|||
mDisplay.setFont(ArialMT_Plain_24); |
|||
sprintf(fmtText,"%3.0f",totalActual); |
|||
mDisplay.drawString(25+ex,0,String(fmtText)+F(" W")); |
|||
mDisplay.setFont(ArialMT_Plain_16); |
|||
|
|||
if (( num_inv != 2 ) || !(mExtra%2)) |
|||
{ |
|||
sprintf(fmtText,"%4.0f",totalYieldToday); |
|||
mDisplay.drawString(5,22,F("today ")+String(fmtText)+F(" Wh")); |
|||
sprintf(fmtText,"%.1f",totalYield); |
|||
mDisplay.drawString(5,35,F("total ")+String(fmtText)+F(" kWh")); |
|||
} |
|||
else |
|||
{ |
|||
if( pow_i[0] ) |
|||
mDisplay.drawString(15,22,F("#1 ")+String(pow_i[0])+F(" W")); |
|||
else |
|||
mDisplay.drawString(15,22,F("#1 -----")); |
|||
if( pow_i[1] ) |
|||
mDisplay.drawString(15,35,F("#2 ")+String(pow_i[1])+F(" W")); |
|||
else |
|||
mDisplay.drawString(15,35,F("#2 -----")); |
|||
} |
|||
mDisplay.drawLine(2,23,123,23); |
|||
} |
|||
else { |
|||
mDisplay.setBrightness(1); |
|||
mDisplay.setFont(ArialMT_Plain_24); |
|||
mDisplay.drawString(mRx+50, 10, F("off")); |
|||
mDisplay.setFont(ArialMT_Plain_16); |
|||
} |
|||
if ( (!(mExtra%10) && ip )|| (timeStr.length()<16)) |
|||
{ |
|||
mDisplay.drawString(5,49,ip.toString()); |
|||
} |
|||
else |
|||
{ |
|||
int w=mDisplay.getStringWidth(timeStr.c_str(),timeStr.length(),0); |
|||
if ( w>127 ) |
|||
{ |
|||
String tt=timeStr.substring(9,17); |
|||
w=mDisplay.getStringWidth(tt.c_str(),tt.length(),0); |
|||
mDisplay.drawString(127-w-mRx,49,tt); |
|||
} |
|||
else |
|||
mDisplay.drawString(0,49,timeStr); |
|||
} |
|||
|
|||
mDisplay.display(); |
|||
mExtra++; |
|||
#endif |
|||
} |
|||
|
|||
// private member variables
|
|||
#if defined(ENA_NOKIA) |
|||
U8G2_PCD8544_84X48_1_4W_HW_SPI mDisplay; |
|||
#else // ENA_SSD1306
|
|||
SSD1306Wire mDisplay; |
|||
int mRx; |
|||
char mUp; |
|||
#endif |
|||
int mExtra; |
|||
bool mNewPayload; |
|||
uint32_t *mUtcTs; |
|||
HMSYSTEM *mSys; |
|||
}; |
|||
#endif |
|||
|
|||
#endif /*__MONOCHROME_DISPLAY__*/ |
@ -0,0 +1,42 @@ |
|||
//-----------------------------------------------------------------------------
|
|||
// 2022 Ahoy, https://github.com/lumpapu/ahoy
|
|||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
|||
//-----------------------------------------------------------------------------
|
|||
|
|||
#include "helper.h" |
|||
|
|||
namespace ah { |
|||
void ip2Arr(uint8_t ip[], const char *ipStr) { |
|||
memset(ip, 0, 4); |
|||
char *tmp = new char[strlen(ipStr)+1]; |
|||
strncpy(tmp, ipStr, strlen(ipStr)+1); |
|||
char *p = strtok(tmp, "."); |
|||
uint8_t i = 0; |
|||
while(NULL != p) { |
|||
ip[i++] = atoi(p); |
|||
p = strtok(NULL, "."); |
|||
} |
|||
delete[] tmp; |
|||
} |
|||
|
|||
// note: char *str needs to be at least 16 bytes long
|
|||
void ip2Char(uint8_t ip[], char *str) { |
|||
if(0 == ip[0]) |
|||
str[0] = '\0'; |
|||
else |
|||
snprintf(str, 16, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); |
|||
} |
|||
|
|||
double round3(double value) { |
|||
return (int)(value * 1000 + 0.5) / 1000.0; |
|||
} |
|||
|
|||
String getDateTimeStr(time_t t) { |
|||
char str[20]; |
|||
if(0 == t) |
|||
sprintf(str, "n/a"); |
|||
else |
|||
sprintf(str, "%04d-%02d-%02d %02d:%02d:%02d", year(t), month(t), day(t), hour(t), minute(t), second(t)); |
|||
return String(str); |
|||
} |
|||
} |
@ -0,0 +1,23 @@ |
|||
//-----------------------------------------------------------------------------
|
|||
// 2022 Ahoy, https://ahoydtu.de
|
|||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
|||
//-----------------------------------------------------------------------------
|
|||
|
|||
#ifndef __HELPER_H__ |
|||
#define __HELPER_H__ |
|||
|
|||
#include <Arduino.h> |
|||
#include <cstdint> |
|||
#include <cstring> |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <TimeLib.h> |
|||
|
|||
namespace ah { |
|||
void ip2Arr(uint8_t ip[], const char *ipStr); |
|||
void ip2Char(uint8_t ip[], char *str); |
|||
double round3(double value); |
|||
String getDateTimeStr(time_t t); |
|||
} |
|||
|
|||
#endif /*__HELPER_H__*/ |
@ -0,0 +1,108 @@ |
|||
//-----------------------------------------------------------------------------
|
|||
// 2022 Ahoy, https://ahoydtu.de
|
|||
// Lukas Pusch, lukas@lpusch.de
|
|||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
|||
//-----------------------------------------------------------------------------
|
|||
#ifndef __LIST_H__ |
|||
#define __LIST_H__ |
|||
|
|||
template<class T, class... Args> |
|||
struct node_s { |
|||
typedef T dT; |
|||
node_s *pre; |
|||
node_s *nxt; |
|||
uint32_t id; |
|||
dT d; |
|||
node_s() : pre(NULL), nxt(NULL), d() {} |
|||
node_s(Args... args) : id(0), pre(NULL), nxt(NULL), d(args...) {} |
|||
}; |
|||
|
|||
template<int MAX_NUM, class T, class... Args> |
|||
class llist { |
|||
typedef node_s<T, Args...> elmType; |
|||
typedef T dataType; |
|||
public: |
|||
llist() : root(mPool) { |
|||
root = NULL; |
|||
elmType *p = mPool; |
|||
for(uint32_t i = 0; i < MAX_NUM; i++) { |
|||
p->id = i; |
|||
p++; |
|||
} |
|||
mFill = mMax = 0; |
|||
} |
|||
|
|||
elmType *add(Args... args) { |
|||
elmType *p = root, *t; |
|||
if(NULL == (t = getFreeNode())) |
|||
return NULL; |
|||
if(++mFill > mMax) |
|||
mMax = mFill; |
|||
|
|||
if(NULL == root) { |
|||
p = root = t; |
|||
p->pre = p; |
|||
p->nxt = p; |
|||
} |
|||
else { |
|||
p = root->pre; |
|||
t->pre = p; |
|||
p->nxt->pre = t; |
|||
t->nxt = p->nxt; |
|||
p->nxt = t; |
|||
} |
|||
t->d = dataType(args...); |
|||
return p; |
|||
} |
|||
|
|||
elmType *getFront() { |
|||
return root; |
|||
} |
|||
|
|||
elmType *get(elmType *p) { |
|||
p = p->nxt; |
|||
return (p == root) ? NULL : p; |
|||
} |
|||
|
|||
elmType *rem(elmType *p) { |
|||
if(NULL == p) |
|||
return NULL; |
|||
elmType *t = p->nxt; |
|||
p->nxt->pre = p->pre; |
|||
p->pre->nxt = p->nxt; |
|||
if(root == p) |
|||
root = NULL; |
|||
p->nxt = NULL; |
|||
p->pre = NULL; |
|||
p = NULL; |
|||
mFill--; |
|||
return (NULL == root) ? NULL : ((t == root) ? NULL : t); |
|||
} |
|||
|
|||
uint16_t getFill(void) { |
|||
return mFill; |
|||
} |
|||
|
|||
uint16_t getMaxFill(void) { |
|||
return mMax; |
|||
} |
|||
|
|||
protected: |
|||
elmType *root; |
|||
|
|||
private: |
|||
elmType *getFreeNode(void) { |
|||
elmType *n = mPool; |
|||
for(uint32_t i = 0; i < MAX_NUM; i++) { |
|||
if(NULL == n->nxt) |
|||
return n; |
|||
n++; |
|||
} |
|||
return NULL; |
|||
} |
|||
|
|||
elmType mPool[MAX_NUM]; |
|||
uint16_t mFill, mMax; |
|||
}; |
|||
|
|||
#endif /*__LIST_H__*/ |
Loading…
Reference in new issue