esp-clock/include/display.h

280 lines
7.6 KiB
C++

#pragma once
#include <Adafruit_LEDBackpack.h> // Support for the Backpack FeatherWing
#include <Adafruit_GFX.h> // Adafruit's graphics library
#include <Adafruit_I2CDevice.h>
#include <SPI.h>
#include "ntp_time.h"
#include "bme.h"
#define DISPLAY_ADDRESS 0x70
#define MILLISECONDS(value) value*TASK_MILLISECOND
#define SECONDS(value) value*TASK_SECOND
#define MINUTES(value) value*TASK_MINUTE
#define DISPLAY_SENSOR_ITERATIONS 2+1
#define DISPLAY_DELAY (SECONDS(2))
typedef void (*callback_t)();
template <typename T>
struct CallbackAware {
void registerCallback(T f) {
callback = f;
}
protected:
T callback;
};
namespace Display {
void displayTime();
void displayDate();
void displayTemp();
void displayHumidity();
void drawTime();
void drawColon(bool);
Task tDisplayTime(MILLISECONDS(500), TASK_FOREVER, displayTime, &ts, false,
[]() {
drawTime();
return true;
},
[]() {
drawColon(false);
});
Task tDisplaySensor(SECONDS(5), DISPLAY_SENSOR_ITERATIONS, nullptr, &ts, false,
[]() {
if (!tDisplayTime.isEnabled()) return false;
tDisplayTime.disable();
tDisplaySensor.setCallback(displayTemp);
return true;
},
[]() {
tDisplaySensor.setIterations((DISPLAY_SENSOR_ITERATIONS - 1) * 2 + 1);
tDisplayTime.enable();
});
Task tDisplayDate(SECONDS(5), TASK_ONCE + 1, displayDate, &ts, false,
[]() {
if (!tDisplayTime.isEnabled()) return false;
tDisplayTime.disable();
return true;
},
[]() {
tDisplayTime.enable();
});
struct HourFormat : public CallbackAware<callback_t> {
operator bool() {
return format24;
}
void operator=(bool value) {
format24 = value;
tDisplayTime.restart();
if (callback) callback();
}
private:
bool format24 = false;
} hourFormat24;
// Create display object
Adafruit_7segment clockDisplay = Adafruit_7segment();
struct Brightness : public CallbackAware<callback_t> {
static constexpr uint8 MIN = 0;
static constexpr uint8 MAX = 15;
static constexpr uint8 DAY = 11;
static constexpr uint8 NIGHT = MIN;
void operator=(uint8 value) {
current = value % (MAX + 1);
clockDisplay.setBrightness(current);
if (callback) callback();
}
operator uint8() {
return current;
}
private:
uint8 current = NIGHT;
} brightness;
namespace Timer {
typedef void (*remaining_callback_t)(int8);
struct : public CallbackAware<remaining_callback_t> {
void operator=(int8 value) {
initial = remaining = value;
}
operator int8() {
return remaining;
}
void start() {
remaining = initial + 1;
}
void decrease() {
remaining--;
if (callback) callback(remaining);
}
bool atBeginning() {
return initial == remaining;
}
private:
int8 initial = 0, remaining = 0;
} timer;
Task tDisplayTimer(SECONDS(10), TASK_ONCE + 1,
[]{
if (timer >= 0) {
clockDisplay.print(timer, DEC);
clockDisplay.writeDisplay();
}
}, &ts, false,
[]{
if (!tDisplayTime.isEnabled()) return false;
tDisplayTime.disable();
return true;
},
[]{
tDisplayTimer.setIterations(TASK_ONCE + 1);
tDisplayTime.enable();
});
Task tTimer(MINUTES(1), TASK_FOREVER,
[]{
static constexpr uint8 threshold = 16;
timer.decrease();
if (timer.atBeginning()) {
if (timer <= threshold) tDisplayTimer.setIterations(TASK_FOREVER);
tDisplayTimer.restart();
} else if (timer == threshold) {
tDisplayTimer.setIterations(TASK_FOREVER);
tDisplayTimer.restart();
}
}, &ts, false,
[]{
timer.start();
return true;
},
[]{
tDisplayTimer.disable();
});
void start() {
tTimer.restart();
}
void stop() {
tTimer.disable();
}
}
void drawTime() {
int displayHour = hourFormat24 ? hour() : hourFormat12();
int displayMinute = minute();
int displayValue = displayHour * 100 + displayMinute;
// Print the time on the display
clockDisplay.print(displayValue, DEC);
// Add zero padding when in 24 hour mode and it's midnight.
// In this case the print function above won't have leading 0's
// which can look confusing. Go in and explicitly add these zeros.
if (displayHour == 0) {
clockDisplay.writeDigitNum(1, 0);
if (displayMinute < 10) {
clockDisplay.writeDigitNum(3, 0);
}
}
}
void drawColon(bool colonOn) {
if (colonOn) {
static int currentHour = -1;
if (currentHour != hour()) {
currentHour = hour();
if (currentHour == 4) {
Ntp::tUpdateTime.restart();
}
if (currentHour == 8) {
brightness = Brightness::DAY;
Wifi::tConnect.enable();
}
if (currentHour == 17) {
brightness = Brightness::NIGHT;
}
}
static int currentMin = -1;
if (currentMin != minute()) {
currentMin = minute();
drawTime();
}
}
clockDisplay.drawColon(colonOn);
}
void displayTime() {
static bool colonOn = true;
drawColon(colonOn);
clockDisplay.writeDisplay();
colonOn = !colonOn;
}
void displayTemp() {
clockDisplay.printFloat(Bme::data.temp, 2);
clockDisplay.writeDigitAscii(4, 'c');
clockDisplay.writeDisplay();
tDisplaySensor.setCallback(&displayHumidity);
}
void displayHumidity() {
clockDisplay.printFloat(Bme::data.humidity, 2);
clockDisplay.writeDigitAscii(4, '%');
clockDisplay.writeDisplay();
tDisplaySensor.setCallback(&displayTemp);
}
void displayValue(uint8 value) {
tDisplayTime.disable();
clockDisplay.print(value, HEX);
clockDisplay.writeDisplay();
tDisplayTime.enableDelayed(DISPLAY_DELAY);
}
void displayText(const char c[]) {
tDisplayTime.disable();
clockDisplay.println(c);
clockDisplay.writeDisplay();
tDisplayTime.enableDelayed(DISPLAY_DELAY);
}
unordered_map<char, const char*> daysOfWeek = {
{1, "So"},
{2, "Mo"},
{3, "Di"},
{4, "Mi"},
{5, "Do"},
{6, "Fr"},
{7, "Sa"},
};
void displayDate() {
char date[5];
sprintf(date, "%2s%2u", daysOfWeek[weekday()], day());
clockDisplay.println(date);
clockDisplay.writeDisplay();
}
void setup() {
clockDisplay.begin(DISPLAY_ADDRESS);
clockDisplay.setBrightness(brightness);
tDisplayTime.enable();
}
}