Refactoring: created Display class.

This commit is contained in:
Dirk Jahnke 2019-01-29 15:25:27 +01:00
parent 52bfedb2aa
commit 37e8f1bfca
3 changed files with 274 additions and 230 deletions

198
src/Display.cpp Normal file
View File

@ -0,0 +1,198 @@
#include "Display.h"
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include "MD_RobotEyes.h"
#define DISPLAY_CLK_PIN D5
#define DISPLAY_DATA_PIN D7
#define DISPLAY_CS_PIN D6
#define VERTICAL_BAR_STARTS_TOP false
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DISPLAY_DEVICES 4
static MD_Parola P = MD_Parola(HARDWARE_TYPE, DISPLAY_DATA_PIN, DISPLAY_CLK_PIN, DISPLAY_CS_PIN, MAX_DISPLAY_DEVICES);
static MD_RobotEyes E;
static MD_MAX72XX *graphicDisplay = NULL;
void Display::begin() {
int charCode;
#if VERTICAL_BAR_STARTS_TOP
static uint8_t verticalBarFont[] = {
1, 0x00, /* blank */
1, 0x01, /* 1 dot */
1, 0x03, /* 2 dots */
1, 0x07,
1, 0x0f,
1, 0x1f,
1, 0x3f,
1, 0x7f,
1, 0xff, /* vertical bar completely set */
}; // columns from right to left, each byte is a single column
#else
static uint8_t verticalBarFont[] = {
1, 0x00, /* blank */
1, 0x80, /* 1 dot */
1, 0xc0, /* 2 dots */
1, 0xe0,
1, 0xf0,
1, 0xf8,
1, 0xfc,
1, 0xfe,
1, 0xff, /* vertical bar completely set */
}; // columns from right to left, each byte is a single column
#endif
static uint8_t newZero[] = {0x05, 0x3e, 0x41, 0x41, 0x41, 0x3e, 0x00};
P.begin();
// P.setZoneEffect(0, true, PA_FLIP_LR);
graphicDisplay = P.getGraphicObject();
E.begin(graphicDisplay);
P.setIntensity(1);
for (charCode=1; charCode<=9; ++charCode) {
P.addChar(charCode, verticalBarFont+2*(charCode-1));
}
// replace the 0 characters, we do not like the "slash"
P.addChar('0', newZero);
char intro[] = {':', '-', ')', ' ', 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00};
P.print(intro);
}
typedef struct
{
char name[7];
MD_RobotEyes::emotion_t e;
uint16_t timePause; // in milliseconds
} sampleItem_t;
static const sampleItem_t eSeq[] =
{
{ "Nutral", MD_RobotEyes::E_NEUTRAL, 1000 },
{ "Blink" , MD_RobotEyes::E_BLINK, 1000 },
{ "Wink" , MD_RobotEyes::E_WINK, 1000 },
{ "Left" , MD_RobotEyes::E_LOOK_L, 1000 },
{ "Right" , MD_RobotEyes::E_LOOK_R, 1000 },
{ "Up" , MD_RobotEyes::E_LOOK_U, 1000 },
{ "Down" , MD_RobotEyes::E_LOOK_D, 1000 },
{ "Angry" , MD_RobotEyes::E_ANGRY, 1000 },
{ "Sad" , MD_RobotEyes::E_SAD, 1000 },
{ "Evil" , MD_RobotEyes::E_EVIL, 1000 },
{ "Evil2" , MD_RobotEyes::E_EVIL2, 1000 },
{ "Squint", MD_RobotEyes::E_SQUINT, 1000 },
{ "Dead" , MD_RobotEyes::E_DEAD, 1000 },
{ "ScanV" , MD_RobotEyes::E_SCAN_UD, 1000 },
{ "ScanH" , MD_RobotEyes::E_SCAN_LR, 1000 },
};
#define DISPLAY_ANIM_NAME false
void Display::loopEyeAnimation() {
// show startup animation
boolean animationFinished = false;
static uint32_t timeStartDelay;
static uint8_t index = ARRAY_SIZE(eSeq);
static enum { S_IDLE, S_TEXT, S_ANIM, S_PAUSE } state = S_IDLE;
animationFinished = E.runAnimation();
switch (state)
{
case S_IDLE:
index++;
if (index >= ARRAY_SIZE(eSeq))
index = 0;
P.displayClear();
#if DISPLAY_ANIM_NAME
E.setText(eSeq[index].name);
#endif
state = S_TEXT;
break;
case S_TEXT: // wait for the text to finish
if (animationFinished) // text animation is finished
{
E.setAnimation(eSeq[index].e, true);
state = S_ANIM;
}
break;
case S_ANIM: // checking animation is completed
if (animationFinished) // animation is finished
{
timeStartDelay = millis();
state = S_PAUSE;
}
break;
case S_PAUSE: // non blocking waiting for a period between animations
if (millis() - timeStartDelay >= eSeq[index].timePause)
state = S_IDLE;
break;
default:
state = S_IDLE;
break;
}
}
void Display::loop() {
char minuteProgressIndicator;
char timeBuffer[10];
minuteProgressIndicator = currentTime->getSeconds()/6.7 + 1; // char code 1-8 show vertical bar
if (minuteProgressIndicator > 9) minuteProgressIndicator = 9;
snprintf(timeBuffer, 10, "%c %2d:%02d", minuteProgressIndicator, currentTime->getHours(), currentTime->getMinutes());
// standard procedure to display
static uint32_t last_clock_refresh = 0;
static uint32_t lastTimeClockNameShown = 0;
static boolean showingClockName = false;
if (showingClockName) {
if (millis() - lastTimeClockNameShown > clockNameDurationTime_ms) {
// stop showingClockName
showingClockName = false;
}
} else {
if ((millis() - lastTimeClockNameShown > displayClockNameEvery_ms)
&& (currentTime->getSeconds() < 60-doNotShowClockNameBeforeAndAfterMinuteChange_s)
&& (currentTime->getSeconds() > doNotShowClockNameBeforeAndAfterMinuteChange_s)) {
// re-initialize, that fixes display problems due to electrical relais feedbacks
reInitializeDisplay();
P.setIntensity(2);
P.print(currentTime->getClockName());
lastTimeClockNameShown = millis();
showingClockName = true;
} else {
// showing clock
if (millis() - last_clock_refresh > displayRefresh_ms) {
// re-initialize, that fixes display problems due to electrical relais feedbacks
reInitializeDisplay();
P.setIntensity(1);
P.print(timeBuffer);
last_clock_refresh = millis();
}
}
}
}
void Display::reInitializeDisplay() {
#define REINIT_AFTER_ms 5000
#define AVOID_REINIT_BEFORE_AND_AFTER_FULLMINUTE_FOR_s 3
if (last_reinit_ts == 0) last_reinit_ts = millis();
if (millis() - last_reinit_ts > REINIT_AFTER_ms
&& currentTime->getSeconds() < 60 - AVOID_REINIT_BEFORE_AND_AFTER_FULLMINUTE_FOR_s
&& currentTime->getSeconds() > AVOID_REINIT_BEFORE_AND_AFTER_FULLMINUTE_FOR_s) {
P.begin();
last_reinit_ts = millis();
}
}
void Display::print(const char *s) { P.print(s); }
void Display::print(const String s) { P.print(s); }

40
src/Display.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef DISPLAY_h_included
#define DISPLAY_h_included
#include <Arduino.h>
#include "Clock.h"
class Display {
public:
Display(Clock *real, Clock *model):
last_reinit_ts(0),
realTime(real),
modelTime(model),
displayClockNameEvery_ms(16000),
clockNameDurationTime_ms(1200),
doNotShowClockNameBeforeAndAfterMinuteChange_s(2),
displayRefresh_ms(200)
{
showRealTime();
};
void begin();
void loop();
void loopEyeAnimation();
void reInitializeDisplay();
void showRealTime() { showRealTimeFlag=true; currentTime = realTime; };
void showModelTime() { showRealTimeFlag=false; currentTime = modelTime; };
void setClockNameDurationTime_ms(unsigned long val) { clockNameDurationTime_ms=val; };
void setClockNameDisplayEvery_ms(unsigned long val) { displayClockNameEvery_ms=val; };
void print(const char *s);
void print(const String s);
protected:
unsigned long last_reinit_ts;
Clock *realTime, *modelTime, *currentTime;
boolean showRealTimeFlag;
unsigned long displayClockNameEvery_ms;
unsigned long clockNameDurationTime_ms;
unsigned int doNotShowClockNameBeforeAndAfterMinuteChange_s;
unsigned long displayRefresh_ms;
};
#endif

View File

@ -4,74 +4,55 @@
#define COMPDATE __DATE__ __TIME__
#define APP_VERSION "0.2.18"
// Button pin on the esp for selecting modes. 0 for Generic devices!
#define MODEBUTTON D3
#define RELAY1_PIN D1
#define RELAY2_PIN D2
#define DISPLAY_CLK_PIN D5
#define DISPLAY_DATA_PIN D7
#define DISPLAY_CS_PIN D6
#define VERTICAL_BAR_STARTS_TOP false
#define DEBUG_RELAYS false
#define DEBUG_DISPLAY false
#define STARTUP1_ANIMATION_DURATION_ms 2000
#define STARTUP2_ANIMATION_DURATION_ms 33000
#include <Arduino.h>
#include <IOTAppStory.h>
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <NTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include "MD_RobotEyes.h"
#include <FS.h>
#include "WebServer.h"
#include <ESP8266HTTPClient.h>
#include "Relays.h"
#include "Clock.h"
#include "Display.h"
// Button pin on the esp for selecting modes. 0 for Generic devices!
#define MODEBUTTON D3
#define RELAY1_PIN D1
#define RELAY2_PIN D2
#define DEBUG_RELAYS false
#define DEBUG_DISPLAY false
#define STARTUP1_ANIMATION_DURATION_ms 2000
#define STARTUP2_ANIMATION_DURATION_ms 33000
IOTAppStory IAS(COMPDATE, MODEBUTTON);
String deviceName = "wemosMatrixDisplay";
String chipId;
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DISPLAY_DEVICES 4
Clock realTime("real");
Clock modelTime("Fremo");
MD_Parola P = MD_Parola(HARDWARE_TYPE, DISPLAY_DATA_PIN, DISPLAY_CLK_PIN, DISPLAY_CS_PIN, MAX_DISPLAY_DEVICES);
MD_RobotEyes E;
Relays R;
WebServer W(&R, &realTime, &modelTime);
Display D(&realTime, &modelTime);
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000);
#define mkstring(x) #x
// Field default values
char *clockName = "FREMO";
char *clockSpeed_modelMsPerRealSec_String = "250";
int clockSpeed_modelMsPerRealSec = 250;
char *relay1Pin_String = "D1";
char *relay2Pin_String = "D2";
int relay1Pin = D1, relay2Pin = D2;
char *relay1Pin_String = mkstring(RELAY1_PIN);
char *relay2Pin_String = mkstring(RELAY2_PIN);
int relay1Pin = RELAY1_PIN, relay2Pin = RELAY2_PIN;
char *relayHoldTime_ms_String = "200";
char *relayMinOffTime_ms_String = "100";
unsigned int displayRefresh_ms = 200;
// Clock Display Config Parameter
static char * displayClockNameEvery_ms_String = "16000";
static char * displayClockNameDuration_ms_String = "1200";
static uint32_t displayClockNameEvery_ms = 16000;
static uint32_t displayClockNameDuration_ms = 1200;
static uint16_t doNotShowClockNameBeforeAndAfterMinuteChange_s = 2;
static uint16_t hours, minutes, seconds;
static char minuteProgressIndicator;
static char timeBuffer[10];
void setupIAS(void) {
#if defined ESP8266
@ -100,41 +81,41 @@ void setupIAS(void) {
IAS.onModeButtonShortPress([]() {
Serial.println(F(" If mode button is released, I will enter firmware update mode."));
Serial.println(F("*----------------------------------------------------------------------*"));
P.print("|updt");
D.print("|updt");
});
IAS.onModeButtonLongPress([]() {
Serial.println(F(" If mode button is released, I will enter configuration mode."));
Serial.println(F("*----------------------------------------------------------------------*"));
P.print("|cfg");
D.print("|cfg");
});
IAS.onFirstBoot([]() {
Serial.println(F(" Manual reset necessary after serial upload!"));
Serial.println(F("*----------------------------------------------------------------------*"));
P.print("|rst");
D.print("|rst");
ESP.reset();
});
IAS.onConfigMode([]() {
P.print(" WiFi");
D.print(" WiFi");
delay(400);
P.print(":" + chipId);
D.print(":" + chipId);
Serial.print(F("Entered config mode for Wifi, device=")); Serial.println(chipId);
});
IAS.onFirmwareUpdateCheck([]() {
// P.print("chk upd");
// D.print("chk upd");
Serial.println(F("Firmware update check"));
});
IAS.onFirmwareUpdateDownload([]() {
P.print("dl+instl");
D.print("dl+instl");
Serial.println(F("Download and install new firmware"));
});
IAS.onFirmwareUpdateError([]() {
// P.print("Err fwu");
// D.print("Err fwu");
Serial.println(F("Firmware update error"));
});
@ -148,71 +129,24 @@ void setupIAS(void) {
// Please change it to at least 2 hours in production
IAS.setCallHomeInterval(120);
IAS.callHome(true /*SPIFFS-check*/);
clockSpeed_modelMsPerRealSec = atoi(clockSpeed_modelMsPerRealSec_String);
modelTime.setSpeed_modelMsPerRealSecond(atoi(clockSpeed_modelMsPerRealSec_String));
relay1Pin = IAS.dPinConv(relay1Pin_String);
relay2Pin = IAS.dPinConv(relay2Pin_String);
R.setHoldTime_ms(atoi(relayHoldTime_ms_String));
R.setMinOffTime_ms(atoi(relayMinOffTime_ms_String));
displayClockNameEvery_ms = atoi(displayClockNameEvery_ms_String);
displayClockNameDuration_ms = atoi(displayClockNameDuration_ms_String);
D.setClockNameDisplayEvery_ms(atoi(displayClockNameEvery_ms_String));
D.setClockNameDurationTime_ms(atoi(displayClockNameDuration_ms_String));
Serial.println(F("Configuration used:"));
Serial.print(F("Relay1 Pin: ")); Serial.println(relay1Pin);
Serial.print(F("Relay2 Pin: ")); Serial.println(relay2Pin);
Serial.print(F("Clock speed: ")); Serial.print(clockSpeed_modelMsPerRealSec); Serial.println(F(" model ms per real sec"));
Serial.print(F("Relay hold time (ms): ")); Serial.println(relayHoldTime_ms_String);
Serial.print(F("Relay min off time (ms): ")); Serial.println(relayMinOffTime_ms_String);
Serial.print(F("Clock speed (model ms per real time s): ")); Serial.println(clockSpeed_modelMsPerRealSec);
Serial.print(F("Show clock name every (ms): ")); Serial.println(displayClockNameEvery_ms);
Serial.print(F("Show clock name for (ms): ")); Serial.println(displayClockNameDuration_ms);
Serial.print(F("Clock speed (model ms per real time s): ")); Serial.println(clockSpeed_modelMsPerRealSec_String);
Serial.print(F("Show clock name every (ms): ")); Serial.println(displayClockNameEvery_ms_String);
Serial.print(F("Show clock name for (ms): ")); Serial.println(displayClockNameDuration_ms_String);
}
static MD_MAX72XX *graphicDisplay = NULL;
void setupDisplay() {
int charCode;
#if VERTICAL_BAR_STARTS_TOP
static uint8_t verticalBarFont[] = {
1, 0x00, /* blank */
1, 0x01, /* 1 dot */
1, 0x03, /* 2 dots */
1, 0x07,
1, 0x0f,
1, 0x1f,
1, 0x3f,
1, 0x7f,
1, 0xff, /* vertical bar completely set */
}; // columns from right to left, each byte is a single column
#else
static uint8_t verticalBarFont[] = {
1, 0x00, /* blank */
1, 0x80, /* 1 dot */
1, 0xc0, /* 2 dots */
1, 0xe0,
1, 0xf0,
1, 0xf8,
1, 0xfc,
1, 0xfe,
1, 0xff, /* vertical bar completely set */
}; // columns from right to left, each byte is a single column
#endif
static uint8_t newZero[] = {0x05, 0x3e, 0x41, 0x41, 0x41, 0x3e, 0x00};
P.begin();
// P.setZoneEffect(0, true, PA_FLIP_LR);
graphicDisplay = P.getGraphicObject();
E.begin(graphicDisplay);
P.setIntensity(1);
for (charCode=1; charCode<=9; ++charCode) {
P.addChar(charCode, verticalBarFont+2*(charCode-1));
}
char intro[] = {':', '-', ')', ' ', 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00};
// replace the 0 characters, we do not like the "slash"
P.addChar('0', newZero);
P.print(intro);
}
void setupFS() {
if(!SPIFFS.begin()){
@ -224,7 +158,7 @@ void setupFS() {
void setup(void)
{
Serial.println(F("setup():"));
setupDisplay();
D.begin();
setupFS();
setupIAS();
W.begin();
@ -240,97 +174,6 @@ static unsigned long lastTimeOutput_ms = 0;
#define TIME_BETWEEN_REALTIME_UPDATE_ms 60000
typedef struct
{
char name[7];
MD_RobotEyes::emotion_t e;
uint16_t timePause; // in milliseconds
} sampleItem_t;
static const sampleItem_t eSeq[] =
{
{ "Nutral", MD_RobotEyes::E_NEUTRAL, 1000 },
{ "Blink" , MD_RobotEyes::E_BLINK, 1000 },
{ "Wink" , MD_RobotEyes::E_WINK, 1000 },
{ "Left" , MD_RobotEyes::E_LOOK_L, 1000 },
{ "Right" , MD_RobotEyes::E_LOOK_R, 1000 },
{ "Up" , MD_RobotEyes::E_LOOK_U, 1000 },
{ "Down" , MD_RobotEyes::E_LOOK_D, 1000 },
{ "Angry" , MD_RobotEyes::E_ANGRY, 1000 },
{ "Sad" , MD_RobotEyes::E_SAD, 1000 },
{ "Evil" , MD_RobotEyes::E_EVIL, 1000 },
{ "Evil2" , MD_RobotEyes::E_EVIL2, 1000 },
{ "Squint", MD_RobotEyes::E_SQUINT, 1000 },
{ "Dead" , MD_RobotEyes::E_DEAD, 1000 },
{ "ScanV" , MD_RobotEyes::E_SCAN_UD, 1000 },
{ "ScanH" , MD_RobotEyes::E_SCAN_LR, 1000 },
};
#define DISPLAY_ANIM_NAME false
void loopStartupAnimation() {
// show startup animation
boolean animationFinished = false;
static uint32_t timeStartDelay;
static uint8_t index = ARRAY_SIZE(eSeq);
static enum { S_IDLE, S_TEXT, S_ANIM, S_PAUSE } state = S_IDLE;
animationFinished = E.runAnimation();
switch (state)
{
case S_IDLE:
index++;
if (index >= ARRAY_SIZE(eSeq))
index = 0;
P.displayClear();
#if DISPLAY_ANIM_NAME
E.setText(eSeq[index].name);
#endif
state = S_TEXT;
break;
case S_TEXT: // wait for the text to finish
if (animationFinished) // text animation is finished
{
E.setAnimation(eSeq[index].e, true);
state = S_ANIM;
}
break;
case S_ANIM: // checking animation is completed
if (animationFinished) // animation is finished
{
timeStartDelay = millis();
state = S_PAUSE;
}
break;
case S_PAUSE: // non blocking waiting for a period between animations
if (millis() - timeStartDelay >= eSeq[index].timePause)
state = S_IDLE;
break;
default:
state = S_IDLE;
break;
}
}
void reInitializeDisplay() {
static unsigned long last_reinit_ts = 0;
#define REINIT_AFTER_ms 5000
#define AVOID_REINIT_BEFORE_AND_AFTER_FULLMINUTE_FOR_s 3
if (last_reinit_ts == 0) last_reinit_ts = millis();
if (millis() - last_reinit_ts > REINIT_AFTER_ms
&& seconds < 60 - AVOID_REINIT_BEFORE_AND_AFTER_FULLMINUTE_FOR_s
&& seconds > AVOID_REINIT_BEFORE_AND_AFTER_FULLMINUTE_FOR_s) {
P.begin();
last_reinit_ts = millis();
}
}
void loop(void)
{
static int lastMinutes = 0;
@ -358,59 +201,22 @@ void loop(void)
return;
}
if (millis() < firstLoop_ts + STARTUP1_ANIMATION_DURATION_ms + STARTUP2_ANIMATION_DURATION_ms) {
loopStartupAnimation();
D.loopEyeAnimation();
return;
}
if (timeClientInitialized) {
hours = timeClient.getHours();
minutes = timeClient.getMinutes();
seconds = timeClient.getSeconds();
realTime.setTime(timeClient.getHours(), timeClient.getMinutes(), timeClient.getSeconds());
} else {
hours = (millis() / 60 * 60 * 1000) % 24;
minutes = (millis() / 60 * 1000) % 60;
seconds = (millis() / 1000) % 60;
realTime.setTime((millis() / 60 * 60 * 1000) % 24, (millis() / 60 * 1000) % 60, (millis() / 1000) % 60);
}
minuteProgressIndicator = seconds/6.7 + 1; // char code 1-8 show vertical bar
if (minuteProgressIndicator > 9) minuteProgressIndicator = 9;
snprintf(timeBuffer, 10, "%c %2d:%02d", minuteProgressIndicator, hours, minutes);
// standard procedure to display
static uint32_t last_clock_refresh = 0;
static uint32_t lastTimeClockNameShown = 0;
static boolean showingClockName = false;
if (showingClockName) {
if (millis() - lastTimeClockNameShown > displayClockNameDuration_ms) {
// stop showingClockName
showingClockName = false;
}
} else {
if ((millis() - lastTimeClockNameShown > displayClockNameEvery_ms)
&& (seconds < 60-doNotShowClockNameBeforeAndAfterMinuteChange_s)
&& (seconds > doNotShowClockNameBeforeAndAfterMinuteChange_s)) {
//P.begin(); // re-initialize, that fixes display problems due to electrical relais feedbacks
reInitializeDisplay();
P.setIntensity(2);
P.print(clockName);
lastTimeClockNameShown = millis();
showingClockName = true;
} else {
// showing clock
if (millis() - last_clock_refresh > displayRefresh_ms) {
// P.begin(); // re-initialize, that fixes display problems due to electrical relais feedbacks
reInitializeDisplay();
P.setIntensity(1);
P.print(timeBuffer);
last_clock_refresh = millis();
}
}
}
D.loop();
// toggle relays
if (lastMinutes != minutes) {
if (lastMinutes != realTime.getMinutes()) {
R.toggle();
lastMinutes = minutes;
lastMinutes = realTime.getMinutes();
}
R.loop();