Initial commit
This commit is contained in:
commit
62bcff8485
17 changed files with 1035 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.pio
|
||||||
|
.vscode/**
|
39
include/README
Normal file
39
include/README
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the usual convention is to give header files names that end with `.h'.
|
||||||
|
It is most portable to use only letters, digits, dashes, and underscores in
|
||||||
|
header file names, and at most one dot.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
46
lib/README
Normal file
46
lib/README
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a an own separate directory
|
||||||
|
("lib/your_library_name/[here are source files]").
|
||||||
|
|
||||||
|
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
and a contents of `src/main.c`:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
23
platformio.ini
Normal file
23
platformio.ini
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:watchy]
|
||||||
|
platform = espressif32
|
||||||
|
board = esp32dev
|
||||||
|
framework = arduino
|
||||||
|
lib_deps =
|
||||||
|
adafruit/Adafruit GFX Library@^1.11.5
|
||||||
|
GxEPD2
|
||||||
|
Time
|
||||||
|
Rtc_Pcf8563
|
||||||
|
NTPClient
|
||||||
|
|
||||||
|
lib_ldf_mode = deep+
|
||||||
|
board_build.partitions = min_spiffs.csv
|
12
src/Main.cpp
Normal file
12
src/Main.cpp
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#include "WatchFace.h"
|
||||||
|
|
||||||
|
WatchFace watchy;
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
watchy.Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
}
|
76
src/SevenSegment.cpp
Normal file
76
src/SevenSegment.cpp
Normal 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
14
src/SevenSegment.h
Normal 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
50
src/WatchFace.cpp
Normal 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
12
src/WatchFace.h
Normal 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
143
src/Watchy.cpp
Normal 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
31
src/Watchy.h
Normal 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
357
src/WatchyDisplay.cpp
Normal 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
50
src/WatchyDisplay.h
Normal 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
112
src/WatchyRTC.cpp
Normal 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
30
src/WatchyRTC.h
Normal 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
27
src/config.h
Normal 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
|
11
test/README
Normal file
11
test/README
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
This directory is intended for PlatformIO Test Runner and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
Loading…
Reference in a new issue