Initial commit

This commit is contained in:
Lewis Jackson 2023-05-29 13:03:05 +03:00
commit 62bcff8485
17 changed files with 1035 additions and 0 deletions

12
src/Main.cpp Normal file
View file

@ -0,0 +1,12 @@
#include "WatchFace.h"
WatchFace watchy;
void setup()
{
watchy.Init();
}
void loop()
{
}

76
src/SevenSegment.cpp Normal file
View file

@ -0,0 +1,76 @@
#include "SevenSegment.h"
SevenSegment::SevenSegment(uint16_t digitWidth, uint16_t digitHeight, uint16_t digitGap, uint16_t segmentThickness, uint16_t segmentGap)
: m_digitWidth(digitWidth), m_digitHeight(digitHeight), m_digitGap(digitGap), m_segmentThickness(segmentThickness), m_segmentGap(segmentGap)
{
}
void SevenSegment::DrawDigit(GxEPD2_BW<WatchyDisplayBase, WatchyDisplayBase::HEIGHT> & display, uint8_t digit, uint16_t x, uint16_t y, uint8_t color)
{
uint16_t digitX = x;
uint16_t digitY = y;
// top top-right bottom-right bottom bottom-left top-left middle
bool drawSegments0[7] = { true, true, true, true, true, true, false };
bool drawSegments1[7] = { false, true, true, false, false, false, false };
bool drawSegments2[7] = { true, true, false, true, true, false, true };
bool drawSegments3[7] = { true, true, true, true, false, false, true };
bool drawSegments4[7] = { false, true, true, false, false, true, true };
bool drawSegments5[7] = { true, false, true, true, false, true, true };
bool drawSegments6[7] = { false, false, true, true, true, true, true };
bool drawSegments7[7] = { true, true, true, false, false, false, false };
bool drawSegments8[7] = { true, true, true, true, true, true, true };
bool drawSegments9[7] = { true, true, true, true, false, true, true };
bool * drawSegments[10] = { drawSegments0, drawSegments1, drawSegments2, drawSegments3, drawSegments4, drawSegments5, drawSegments6, drawSegments7, drawSegments8, drawSegments9 };
bool * segments = drawSegments[digit];
// A
if (segments[0]) {
display.fillRect(digitX + m_segmentThickness, digitY, m_digitWidth - 2 * m_segmentThickness, m_segmentThickness, color);
} else {
display.fillRect(digitX + m_segmentThickness, digitY, m_digitWidth - 2 * m_segmentThickness, m_segmentThickness, color ^ 0xFF);
}
// B
if (segments[1]) {
display.fillRect(digitX + m_digitWidth - m_segmentThickness, digitY + m_segmentThickness, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color);
} else {
display.fillRect(digitX + m_digitWidth - m_segmentThickness, digitY + m_segmentThickness, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color ^ 0xFF);
}
// C
if (segments[2]) {
display.fillRect(digitX + m_digitWidth - m_segmentThickness, digitY + m_digitHeight / 2, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color);
} else {
display.fillRect(digitX + m_digitWidth - m_segmentThickness, digitY + m_digitHeight / 2, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color ^ 0xFF);
}
// D
if (segments[3]) {
display.fillRect(digitX + m_segmentThickness, digitY + m_digitHeight - m_segmentThickness * 2, m_digitWidth - 2 * m_segmentThickness, m_segmentThickness, color);
} else {
display.fillRect(digitX + m_segmentThickness, digitY + m_digitHeight - m_segmentThickness * 2, m_digitWidth - 2 * m_segmentThickness, m_segmentThickness, color ^ 0xFF);
}
// E
if (segments[4]) {
display.fillRect(digitX, digitY + m_digitHeight / 2, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color);
} else {
display.fillRect(digitX, digitY + m_digitHeight / 2, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color ^ 0xFF);
}
// F
if (segments[5]) {
display.fillRect(digitX, digitY + m_segmentThickness, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color);
} else {
display.fillRect(digitX, digitY + m_segmentThickness, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color ^ 0xFF);
}
// G
if (segments[6]) {
display.fillRect(digitX + m_segmentThickness, digitY + m_digitHeight / 2 - m_segmentThickness, m_digitWidth - 2 * m_segmentThickness, m_segmentThickness, color);
} else {
display.fillRect(digitX + m_segmentThickness, digitY + m_digitHeight / 2 - m_segmentThickness, m_digitWidth - 2 * m_segmentThickness, m_segmentThickness, color ^ 0xFF);
}
}

14
src/SevenSegment.h Normal file
View file

@ -0,0 +1,14 @@
#pragma once
#include <stdint.h>
#include "WatchyDisplay.h"
class SevenSegment
{
public:
SevenSegment(uint16_t digitWidth, uint16_t digitHeight, uint16_t digitGap, uint16_t segmentThickness, uint16_t segmentGap);
void DrawDigit(GxEPD2_BW<WatchyDisplayBase, WatchyDisplayBase::HEIGHT> & display, uint8_t digit, uint16_t x, uint16_t y, uint8_t color);
private:
uint16_t m_digitWidth, m_digitHeight, m_digitGap, m_segmentThickness, m_segmentGap;
};

50
src/WatchFace.cpp Normal file
View file

@ -0,0 +1,50 @@
#include "WatchFace.h"
#include "SevenSegment.h"
#include <cmath>
void WatchFace::DrawWatchFace()
{
m_display.fillScreen(GxEPD_WHITE);
DrawBatteryIcon();
tmElements_t currentTime;
m_RTC.read(currentTime, TZ_OFFSET);
SevenSegment sevenSegment(30, 60, 6, 5, 5);
if (currentTime.Hour < 10) {
sevenSegment.DrawDigit(m_display, 0, 10, 75, GxEPD_BLACK);
} else {
sevenSegment.DrawDigit(m_display, currentTime.Hour / 10, 10, 75, GxEPD_BLACK);
}
sevenSegment.DrawDigit(m_display, currentTime.Hour % 10, 50, 75, GxEPD_BLACK);
if (currentTime.Minute < 10) {
sevenSegment.DrawDigit(m_display, 0, 100, 75, GxEPD_BLACK);
} else {
sevenSegment.DrawDigit(m_display, currentTime.Minute / 10, 100, 75, GxEPD_BLACK);
}
sevenSegment.DrawDigit(m_display, currentTime.Minute % 10, 140, 75, GxEPD_BLACK);
m_display.fillRect(87, 90, 5, 5, GxEPD_BLACK);
m_display.fillRect(87, 110, 5, 5, GxEPD_BLACK);
// sevenSegment.DrawDigit(m_display, 9, 10, 75, GxEPD_BLACK);
}
void WatchFace::DrawBatteryIcon()
{
m_display.fillRect(200 - 48, 5, 43, 23, GxEPD_BLACK);
m_display.fillRect(200 - 46, 7, 39, 19, GxEPD_WHITE);
float VBAT = GetBatteryVoltage();
float level = (VBAT - 3.6f) / 0.6f;
if (level > 1.0f) {
level = 1.0f;
} else if (level < 0.0f) {
level = 0.0f;
}
level = 0.5f - sin(asin(1.0f - 2.0f * level) / 3.0f);
m_display.fillRect(200 - 44, 9, (int)std::round(35.0f * level), 15, GxEPD_BLACK);
}

12
src/WatchFace.h Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include "Watchy.h"
class WatchFace : public Watchy
{
public:
void DrawWatchFace();
private:
void DrawBatteryIcon();
};

143
src/Watchy.cpp Normal file
View file

@ -0,0 +1,143 @@
#include "Watchy.h"
WatchyDisplayBase Watchy::m_displayBase;
GxEPD2_BW<WatchyDisplayBase, WatchyDisplayBase::HEIGHT> Watchy::m_display(Watchy::m_displayBase);
WatchyRTC Watchy::m_RTC;
RTC_DATA_ATTR bool g_displayFullInit = true;
Watchy::Watchy()
{
}
void Watchy::Init()
{
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
Wire.begin(SDA, SCL);
m_RTC.init();
m_display.epd2.selectSPI(SPI, SPISettings(20000000, MSBFIRST, SPI_MODE0));
m_display.init(0, g_displayFullInit, 10, true);
m_display.epd2.setBusyCallback(DisplayBusyCallback);
switch(wakeup_reason) {
case ESP_SLEEP_WAKEUP_EXT0:
// RTC interrupt
ShowWatchFace(true);
break;
case ESP_SLEEP_WAKEUP_EXT1:
// Button press
break;
default:
ConnectWiFi();
SyncNTPTime();
DisconnectWiFi();
m_display.clearScreen(0xFF);
ShowWatchFace(false);
// pinMode(VIB_MOTOR_PIN, OUTPUT);
// bool motorOn = false;
// for (int i = 0; i < 4; i++) {
// motorOn = !motorOn;
// digitalWrite(VIB_MOTOR_PIN, motorOn);
// delay(75);
// }
break;
}
DeepSleep();
}
void Watchy::DeepSleep()
{
m_display.hibernate();
if (g_displayFullInit) { // For some reason, seems to be enabled on first boot
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
}
g_displayFullInit = false; // Notify not to init it again
m_RTC.clearAlarm(); // resets the alarm flag in the RTC
// Set GPIOs 0-39 to input to avoid power leaking out
const uint64_t ignore = 0b11110001000000110000100111000010; // Ignore some GPIOs due to resets
for (int i = 0; i < GPIO_NUM_MAX; i++) {
if ((ignore >> i) & 0b1)
continue;
pinMode(i, INPUT);
}
esp_sleep_enable_ext0_wakeup((gpio_num_t)RTC_INT_PIN,
0); // enable deep sleep wake on RTC interrupt
esp_sleep_enable_ext1_wakeup(
BTN_PIN_MASK,
ESP_EXT1_WAKEUP_ANY_HIGH); // enable deep sleep wake on button press
esp_deep_sleep_start();
}
void Watchy::DisplayBusyCallback(const void *)
{
gpio_wakeup_enable((gpio_num_t)DISPLAY_BUSY, GPIO_INTR_LOW_LEVEL);
esp_sleep_enable_gpio_wakeup();
esp_light_sleep_start();
}
void Watchy::VibeMotor(uint8_t intervalMs, uint8_t length)
{
pinMode(VIB_MOTOR_PIN, OUTPUT);
bool motorOn = false;
for (int i = 0; i < length; i++) {
motorOn = !motorOn;
digitalWrite(VIB_MOTOR_PIN, motorOn);
delay(intervalMs);
}
}
float Watchy::GetBatteryVoltage()
{
return analogReadMilliVolts(BATT_ADC_PIN) / 1000.0f * 2.0f;
}
void Watchy::ConnectWiFi()
{
if(WiFi.begin(WIFI_SSID, WIFI_PASS) == WL_CONNECT_FAILED) {
Serial.begin(9600);
Serial.println("Failed to connect to WiFi");
return;
}
if(WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.begin(9600);
Serial.println("Failed to connect to WiFi");
return;
}
}
void Watchy::SyncNTPTime()
{
WiFiUDP ntpUDP;
// GMT offset should be, RTC class will adjust to local time
NTPClient timeClient(ntpUDP, "pool.ntp.org", 0);
timeClient.begin();
bool success = timeClient.forceUpdate();
if (!success) {
Serial.begin(9600);
Serial.println("Failed to get NTP time");
}
tmElements_t tm;
breakTime((time_t)timeClient.getEpochTime(), tm);
m_RTC.set(tm);
}
void Watchy::DisconnectWiFi()
{
WiFi.mode(WIFI_OFF);
}
void Watchy::ShowWatchFace(bool partialRefresh)
{
m_display.setFullWindow();
DrawWatchFace();
m_display.display(partialRefresh);
}

31
src/Watchy.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include "config.h"
#include "WatchyDisplay.h"
#include "WatchyRTC.h"
#include <WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
class Watchy
{
public:
Watchy();
void Init();
void DeepSleep();
void VibeMotor(uint8_t intervalMs = 100, uint8_t length = 20);
float GetBatteryVoltage();
void ConnectWiFi();
void SyncNTPTime();
void DisconnectWiFi();
void ShowWatchFace(bool partialRefresh = false);
virtual void DrawWatchFace() = 0;
protected:
static void DisplayBusyCallback(const void *);
static WatchyDisplayBase m_displayBase;
static GxEPD2_BW<WatchyDisplayBase, WatchyDisplayBase::HEIGHT> m_display;
static WatchyRTC m_RTC;
};

357
src/WatchyDisplay.cpp Normal file
View file

@ -0,0 +1,357 @@
#include "WatchyDisplay.h"
WatchyDisplayBase::WatchyDisplayBase()
: GxEPD2_EPD(DISPLAY_CS, DISPLAY_DC, DISPLAY_RES, DISPLAY_BUSY, HIGH, 10000000, DISPLAY_WIDTH, DISPLAY_HEIGHT, panel, false, true, true)
{
}
// init controller memory and screen (default white)
void WatchyDisplayBase::clearScreen(uint8_t value)
{
writeScreenBuffer(value);
refresh(true);
if (!_using_partial_mode) {
InitPart();
}
_startTransfer();
TransferCommand(0x24);
for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++) {
_transfer(value);
}
_endTransfer();
// Is this necessary? Come back to this - FIXME
_startTransfer();
TransferCommand(0x26);
for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++) {
_transfer(value);
}
_endTransfer();
_initial_write = false; // initial full screen buffer clean done
}
// init controller memory (default white)
void WatchyDisplayBase::writeScreenBuffer(uint8_t value)
{
if (!_using_partial_mode) {
InitPart();
}
if (_initial_write) {
_startTransfer();
TransferCommand(0x26);
for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++) {
_transfer(value);
}
_endTransfer();
_initial_write = false;
}
}
void WatchyDisplayBase::writeImage(const uint8_t * bitmap, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
{
if (_initial_write) {
if (!_using_partial_mode) {
InitPart();
}
writeScreenBuffer(0x24); // initial full screen buffer clean
_initial_write = false;
}
#if defined(ESP8266) || defined(ESP32)
yield(); // avoid wdt
#endif
int16_t wb = (w + 7) / 8; // width bytes, bitmaps are padded
x -= x % 8; // byte boundary
w = wb * 8; // byte boundary
int16_t x1 = x < 0 ? 0 : x; // limit
int16_t y1 = y < 0 ? 0 : y; // limit
int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x; // limit
int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y; // limit
int16_t dx = x1 - x;
int16_t dy = y1 - y;
w1 -= dx;
h1 -= dy;
if ((w1 <= 0) || (h1 <= 0)) {
return;
}
if (!_using_partial_mode) {
InitPart();
}
SetPartialRamArea(x1, y1, w1, h1);
_startTransfer();
TransferCommand(0x24);
for (int16_t i = 0; i < h1; i++) {
for (int16_t j = 0; j < w1 / 8; j++) {
uint8_t data;
// use wb, h of bitmap for index!
int16_t idx = mirror_y ? j + dx / 8 + ((h - 1 - (i + dy))) * wb : j + dx / 8 + (i + dy) * wb;
if (pgm) {
#if defined(__AVR) || defined(ESP8266) || defined(ESP32)
data = pgm_read_byte(&bitmap[idx]);
#else
data = bitmap[idx];
#endif
} else {
data = bitmap[idx];
}
if (invert) {
data = ~data;
}
_transfer(data);
}
}
_endTransfer();
#if defined(ESP8266) || defined(ESP32)
yield(); // avoid wdt
#endif
}
// screen refresh from controller memory, partial screen
void WatchyDisplayBase::writeImagePart(const uint8_t * bitmap, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
{
if (_initial_write) {
writeScreenBuffer(); // initial full screen buffer clean
}
#if defined(ESP8266) || defined(ESP32)
yield(); // avoid wdt
#endif
if ((w_bitmap < 0) || (h_bitmap < 0) || (w < 0) || (h < 0)) return;
if ((x_part < 0) || (x_part >= w_bitmap)) return;
if ((y_part < 0) || (y_part >= h_bitmap)) return;
int16_t wb_bitmap = (w_bitmap + 7) / 8; // width bytes, bitmaps are padded
x_part -= x_part % 8; // byte boundary
w = w_bitmap - x_part < w ? w_bitmap - x_part : w; // limit
h = h_bitmap - y_part < h ? h_bitmap - y_part : h; // limit
x -= x % 8; // byte boundary
w = 8 * ((w + 7) / 8); // byte boundary, bitmaps are padded
int16_t x1 = x < 0 ? 0 : x; // limit
int16_t y1 = y < 0 ? 0 : y; // limit
int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x; // limit
int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y; // limit
int16_t dx = x1 - x;
int16_t dy = y1 - y;
w1 -= dx;
h1 -= dy;
if ((w1 <= 0) || (h1 <= 0)) {
return;
}
if (!_using_partial_mode) {
InitPart();
}
SetPartialRamArea(x1, y1, w1, h1);
_startTransfer();
TransferCommand(0x24);
for (int16_t i = 0; i < h1; i++)
{
for (int16_t j = 0; j < w1 / 8; j++)
{
uint8_t data;
// use wb_bitmap, h_bitmap of bitmap for index!
int16_t idx = mirror_y ? x_part / 8 + j + dx / 8 + ((h_bitmap - 1 - (y_part + i + dy))) * wb_bitmap : x_part / 8 + j + dx / 8 + (y_part + i + dy) * wb_bitmap;
if (pgm)
{
#if defined(__AVR) || defined(ESP8266) || defined(ESP32)
data = pgm_read_byte(&bitmap[idx]);
#else
data = bitmap[idx];
#endif
}
else
{
data = bitmap[idx];
}
if (invert) data = ~data;
_transfer(data);
}
}
_endTransfer();
#if defined(ESP8266) || defined(ESP32)
yield(); // avoid wdt
#endif
}
// screen refresh from controller memory to full screen
void WatchyDisplayBase::refresh(bool partial_update_mode)
{
if (partial_update_mode) {
refresh(0, 0, WIDTH, HEIGHT);
} else {
if (_using_partial_mode) {
InitFull();
}
UpdateFull();
_initial_refresh = false; // initial full update done
}
}
void WatchyDisplayBase::refresh(int16_t x, int16_t y, int16_t w, int16_t h)
{
if (_initial_refresh) {
return refresh(false); // initial update needs be full update
}
// intersection with screen
int16_t w1 = x < 0 ? w + x : w; // reduce
int16_t h1 = y < 0 ? h + y : h; // reduce
int16_t x1 = x < 0 ? 0 : x; // limit
int16_t y1 = y < 0 ? 0 : y; // limit
w1 = x1 + w1 < int16_t(WIDTH) ? w1 : int16_t(WIDTH) - x1; // limit
h1 = y1 + h1 < int16_t(HEIGHT) ? h1 : int16_t(HEIGHT) - y1; // limit
if ((w1 <= 0) || (h1 <= 0)) {
return;
}
// make x1, w1 multiple of 8
w1 += x1 % 8;
if (w1 % 8 > 0) w1 += 8 - w1 % 8;
x1 -= x1 % 8;
if (!_using_partial_mode) {
InitPart();
}
SetPartialRamArea(x1, y1, w1, h1);
UpdatePart();
}
void WatchyDisplayBase::powerOff()
{
if (_power_is_on) {
_startTransfer();
TransferCommand(0x22);
_transfer(0x83);
TransferCommand(0x20);
_endTransfer();
_waitWhileBusy("_PowerOff", power_off_time);
}
_power_is_on = false;
_using_partial_mode = false;
}
void WatchyDisplayBase::hibernate()
{
if (_rst >= 0) {
_writeCommand(0x10); // deep sleep mode
_writeData(0x1); // enter deep sleep
_hibernating = true;
}
}
void WatchyDisplayBase::powerOn()
{
if (!_power_is_on) {
_startTransfer();
TransferCommand(0x22);
_transfer(0xf8);
TransferCommand(0x20);
_endTransfer();
_waitWhileBusy("_PowerOn", power_on_time);
}
_power_is_on = true;
}
void WatchyDisplayBase::InitFull()
{
InitDisplay();
powerOn();
_using_partial_mode = false;
}
void WatchyDisplayBase::InitPart()
{
InitDisplay();
powerOn();
_using_partial_mode = true;
}
void WatchyDisplayBase::InitDisplay()
{
if (_hibernating) {
_reset();
}
_writeCommand(0x12); // soft reset
_waitWhileBusy("_SoftReset", 10); // 10ms max according to specs
_startTransfer();
TransferCommand(0x01); // Driver output control
_transfer(0xC7);
_transfer(0x00);
_transfer(0x00);
TransferCommand(0x3C); // BorderWavefrom
_transfer(darkBorder ? 0x02 : 0x05);
TransferCommand(0x18); // Read built-in temperature sensor
_transfer(0x80);
_endTransfer();
SetPartialRamArea(0, 0, WIDTH, HEIGHT);
}
void WatchyDisplayBase::UpdateFull()
{
_startTransfer();
TransferCommand(0x22);
_transfer(0xf4);
TransferCommand(0x20);
_endTransfer();
_waitWhileBusy("_Update_Full", full_refresh_time);
}
void WatchyDisplayBase::UpdatePart()
{
_startTransfer();
TransferCommand(0x22);
//_transfer(0xcc); // skip temperature load (-5ms)
_transfer(0xfc);
TransferCommand(0x20);
_endTransfer();
_waitWhileBusy("_Update_Part", partial_refresh_time);
}
void WatchyDisplayBase::TransferCommand(uint8_t value)
{
if (_dc >= 0) {
digitalWrite(_dc, LOW);
}
SPI.transfer(value);
if (_dc >= 0) {
digitalWrite(_dc, HIGH);
}
}
void WatchyDisplayBase::SetPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
_startTransfer();
TransferCommand(0x11); // set ram entry mode
_transfer(0x03); // x increase, y increase : normal mode
TransferCommand(0x44);
_transfer(x / 8);
_transfer((x + w - 1) / 8);
TransferCommand(0x45);
_transfer(y % 256);
_transfer(y / 256);
_transfer((y + h - 1) % 256);
_transfer((y + h - 1) / 256);
TransferCommand(0x4e);
_transfer(x / 8);
TransferCommand(0x4f);
_transfer(y % 256);
_transfer(y / 256);
_endTransfer();
}

50
src/WatchyDisplay.h Normal file
View file

@ -0,0 +1,50 @@
#pragma once
#include "config.h"
#include <GxEPD2_EPD.h>
#include <GxEPD2_BW.h>
class WatchyDisplayBase : public GxEPD2_EPD
{
public:
static const uint16_t WIDTH = DISPLAY_WIDTH;
static const uint16_t HEIGHT = DISPLAY_HEIGHT;
static const uint16_t WIDTH_VISIBLE = WIDTH;
WatchyDisplayBase();
// init controller memory and screen (default white)
void clearScreen(uint8_t value = 0xFF) override;
// init controller memory (default white)
void writeScreenBuffer(uint8_t value = 0xFF) override;
void writeImage(const uint8_t * bitmap, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) override;
// screen refresh from controller memory, partial screen
void writeImagePart(const uint8_t * bitmap, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) override;
// screen refresh from controller memory to full screen
void refresh(bool partial_update_mode = false) override;
void refresh(int16_t x, int16_t y, int16_t w, int16_t h) override;
void powerOff() override;
void hibernate() override;
void powerOn();
static const GxEPD2::Panel panel = GxEPD2::GDEH0154D67;
private:
static const uint16_t power_on_time = 100; // ms, e.g. 95583us
static const uint16_t power_off_time = 150; // ms, e.g. 140621us
static const uint16_t full_refresh_time = 2600; // ms, e.g. 2509602us
static const uint16_t partial_refresh_time = 500; // ms, e.g. 457282us
static const bool darkBorder = false;
void InitFull();
void InitPart();
void InitDisplay();
void UpdateFull();
void UpdatePart();
void TransferCommand(uint8_t value);
void SetPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
};

112
src/WatchyRTC.cpp Normal file
View file

@ -0,0 +1,112 @@
#include "WatchyRTC.h"
WatchyRTC::WatchyRTC()
{
}
void WatchyRTC::init() {
byte error;
Wire.beginTransmission(RTC_PCF_ADDR);
error = Wire.endTransmission();
}
void WatchyRTC::config(String datetime) { // String datetime format is YYYY:MM:DD:HH:MM:SS
_PCFConfig(datetime);
}
void WatchyRTC::clearAlarm() {
int nextAlarmMinute = 0;
rtc_pcf.clearAlarm(); // resets the alarm flag in the RTC
nextAlarmMinute = rtc_pcf.getMinute();
nextAlarmMinute = (nextAlarmMinute == 59) ? 0 : (nextAlarmMinute + 1); // set alarm to trigger 1 minute from now
rtc_pcf.setAlarm(nextAlarmMinute, 99, 99, 99);
}
void WatchyRTC::read(tmElements_t & tm, int offsetInSeconds) {
rtc_pcf.getDate();
tm.Year = y2kYearToTm(rtc_pcf.getYear());
tm.Month = rtc_pcf.getMonth();
tm.Day = rtc_pcf.getDay();
tm.Wday = rtc_pcf.getWeekday() + 1; // TimeLib & DS3231 has Wday range of 1-7, but PCF8563 stores day of week in 0-6 range
tm.Hour = rtc_pcf.getHour();
tm.Minute = rtc_pcf.getMinute();
tm.Second = rtc_pcf.getSecond();
int day = tm.Day;
int hour = tm.Hour;
int minute = tm.Minute;
int second = tm.Second;
// adjust for offset - making some assumptions here: month and year will never change.
second += offsetInSeconds;
if (second >= 60 || second < 0) {
minute += second / 60;
second = second % 60;
}
if (minute >= 60 || minute < 0) {
hour += minute / 60;
minute = minute % 60;
}
if (hour >= 24 || hour < 0) {
day += hour / 24;
hour = hour % 24;
}
tm.Day = day;
tm.Hour = hour;
tm.Minute = minute;
tm.Second = second;
}
void WatchyRTC::set(tmElements_t tm) {
time_t t = makeTime(tm); // make and break to calculate tm.Wday
breakTime(t, tm);
// day, weekday, month, century(1=1900, 0=2000), year(0-99)
rtc_pcf.setDate(tm.Day, tm.Wday - 1, tm.Month, 0, tmYearToY2k(tm.Year));
rtc_pcf.setTime(tm.Hour, tm.Minute, tm.Second);
clearAlarm();
}
void WatchyRTC::_PCFConfig(String datetime) { // String datetime is YYYY:MM:DD:HH:MM:SS
if (datetime != "") {
tmElements_t tm;
tm.Year = CalendarYrToTm(_getValue(datetime, ':', 0).toInt()); // YYYY -
// 1970
tm.Month = _getValue(datetime, ':', 1).toInt();
tm.Day = _getValue(datetime, ':', 2).toInt();
tm.Hour = _getValue(datetime, ':', 3).toInt();
tm.Minute = _getValue(datetime, ':', 4).toInt();
tm.Second = _getValue(datetime, ':', 5).toInt();
time_t t = makeTime(tm); // make and break to calculate tm.Wday
breakTime(t, tm);
// day, weekday, month, century(1=1900, 0=2000), year(0-99)
rtc_pcf.setDate(
tm.Day, tm.Wday - 1, tm.Month, 0,
tmYearToY2k(tm.Year)); // TimeLib & DS3231 has Wday range of 1-7, but
// PCF8563 stores day of week in 0-6 range
// hr, min, sec
rtc_pcf.setTime(tm.Hour, tm.Minute, tm.Second);
}
// on POR event, PCF8563 sets month to 0, which will give an error since
// months are 1-12
clearAlarm();
}
String WatchyRTC::_getValue(String data, char separator, int index) {
int found = 0;
int strIndex[] = {0, -1};
int maxIndex = data.length() - 1;
for (int i = 0; i <= maxIndex && found <= index; i++) {
if (data.charAt(i) == separator || i == maxIndex) {
found++;
strIndex[0] = strIndex[1] + 1;
strIndex[1] = (i == maxIndex) ? i + 1 : i;
}
}
return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}

30
src/WatchyRTC.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef WATCHY_RTC_H
#define WATCHY_RTC_H
#include "config.h"
#include <Arduino.h>
#include <TimeLib.h>
#include <Rtc_Pcf8563.h>
#define RTC_PCF_ADDR 0x51
#define YEAR_OFFSET_PCF 2000
class WatchyRTC {
public:
Rtc_Pcf8563 rtc_pcf;
public:
WatchyRTC();
void init();
void config(String datetime); // String datetime format is YYYY:MM:DD:HH:MM:SS
void clearAlarm();
void read(tmElements_t & tm, int offsetInSeconds = 0);
void set(tmElements_t tm);
private:
void _PCFConfig(String datetime);
int _getDayOfWeek(int d, int m, int y);
String _getValue(String data, char separator, int index);
};
#endif

27
src/config.h Normal file
View file

@ -0,0 +1,27 @@
#define MENU_BTN_PIN 26
#define BACK_BTN_PIN 25
#define DOWN_BTN_PIN 4
#define UP_BTN_PIN 35
#define BATT_ADC_PIN 34
#define DISPLAY_CS 5
#define DISPLAY_RES 9
#define DISPLAY_DC 10
#define DISPLAY_BUSY 19
#define ACC_INT_1_PIN 14
#define ACC_INT_2_PIN 12
#define VIB_MOTOR_PIN 13
#define RTC_INT_PIN 27
#define MENU_BTN_MASK GPIO_SEL_26
#define BACK_BTN_MASK GPIO_SEL_25
#define DOWN_BTN_MASK GPIO_SEL_4
#define UP_BTN_MASK GPIO_SEL_35
#define ACC_INT_MASK GPIO_SEL_14
#define BTN_PIN_MASK MENU_BTN_MASK|BACK_BTN_MASK|UP_BTN_MASK|DOWN_BTN_MASK
#define DISPLAY_WIDTH 200
#define DISPLAY_HEIGHT 200
#define WIFI_SSID "<my ssid>>"
#define WIFI_PASS "<my pass>"
#define TZ_OFFSET 3600 * 3