This commit is contained in:
parent
1b843ce4b4
commit
90c66be515
19 changed files with 413 additions and 162 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
.pio
|
.pio
|
||||||
.vscode/**
|
.vscode/**
|
||||||
|
src/secrets.h
|
||||||
|
|
|
@ -5,7 +5,7 @@ WatchFace watchy;
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
Serial.begin(9600);
|
Serial.begin(9600);
|
||||||
watchy.Init();
|
watchy.Wake();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
|
|
|
@ -1,27 +1,33 @@
|
||||||
#include "WatchFace.h"
|
#include "WatchFace.h"
|
||||||
#include "SevenSegment.h"
|
#include "WatchFacePages/Clock.h"
|
||||||
#include "Icons.h"
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <Fonts/FreeSans9pt7b.h>
|
|
||||||
#include <Fonts/FreeMonoBold9pt7b.h>
|
|
||||||
#include <Fonts/FreeMonoBold12pt7b.h>
|
|
||||||
|
|
||||||
RTC_DATA_ATTR bool WatchFace::m_menuSetup = false;
|
|
||||||
RTC_DATA_ATTR bool WatchFace::m_inMenu = false;
|
RTC_DATA_ATTR bool WatchFace::m_inMenu = false;
|
||||||
RTC_DATA_ATTR int WatchFace::m_tzOffset = TZ_OFFSET;
|
RTC_DATA_ATTR Menu WatchFace::m_menu;
|
||||||
Menu WatchFace::m_menu;
|
RTC_DATA_ATTR uint8_t WatchFace::m_watchFacePage;
|
||||||
|
|
||||||
void WatchFace::Setup() // Called after hardware is set up
|
void WatchFace::InitBoot()
|
||||||
{
|
{
|
||||||
if (!m_menuSetup) {
|
|
||||||
m_menu.Init(m_display);
|
m_menu.Init(m_display);
|
||||||
}
|
SetupVolatileMenuStuff();
|
||||||
|
m_menu.Reset();
|
||||||
|
|
||||||
|
ConnectWiFi();
|
||||||
|
SyncNTPTime();
|
||||||
|
DisconnectWiFi();
|
||||||
|
|
||||||
|
for (auto & page : m_pages) {
|
||||||
|
page->InitBoot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchFace::InitWake()
|
||||||
|
{
|
||||||
SetupVolatileMenuStuff();
|
SetupVolatileMenuStuff();
|
||||||
|
|
||||||
if (!m_menuSetup) {
|
for (auto & page : m_pages) {
|
||||||
m_menu.Reset();
|
page->InitWake();
|
||||||
m_menuSetup = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,68 +92,7 @@ void WatchFace::DrawWatchFace(bool partialRefresh)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_display.setFullWindow();
|
m_pages[0]->DrawPage(partialRefresh);
|
||||||
|
|
||||||
m_display.fillScreen(GxEPD_WHITE);
|
|
||||||
DrawBatteryIcon();
|
|
||||||
|
|
||||||
tmElements_t currentTime;
|
|
||||||
m_RTC.Get(currentTime, m_tzOffset);
|
|
||||||
SevenSegment sevenSegment(30, 60, 6, 5, 5);
|
|
||||||
|
|
||||||
if (currentTime.Hour < 10) {
|
|
||||||
sevenSegment.DrawDigit(m_display, 0, 15, 75, GxEPD_BLACK);
|
|
||||||
} else {
|
|
||||||
sevenSegment.DrawDigit(m_display, currentTime.Hour / 10, 20, 75, GxEPD_BLACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
sevenSegment.DrawDigit(m_display, currentTime.Hour % 10, 60, 75, GxEPD_BLACK);
|
|
||||||
|
|
||||||
if (currentTime.Minute < 10) {
|
|
||||||
sevenSegment.DrawDigit(m_display, 0, 110, 75, GxEPD_BLACK);
|
|
||||||
} else {
|
|
||||||
sevenSegment.DrawDigit(m_display, currentTime.Minute / 10, 110, 75, GxEPD_BLACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
sevenSegment.DrawDigit(m_display, currentTime.Minute % 10, 150, 75, GxEPD_BLACK);
|
|
||||||
|
|
||||||
m_display.fillRect(97, 90, 5, 5, GxEPD_BLACK);
|
|
||||||
m_display.fillRect(97, 110, 5, 5, GxEPD_BLACK);
|
|
||||||
|
|
||||||
m_display.display(partialRefresh);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
int x = 200 - 85;
|
|
||||||
int intLevel = (int)std::round(100.0f * level);
|
|
||||||
if (intLevel == 100) {
|
|
||||||
x -= 13;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
m_display.setCursor(x, 22);
|
|
||||||
m_display.setTextColor(GxEPD_BLACK);
|
|
||||||
m_display.print(intLevel);
|
|
||||||
m_display.print("%");
|
|
||||||
|
|
||||||
m_display.setFont(&FreeMonoBold12pt7b);
|
|
||||||
m_display.drawBitmap(10, 177, Icons::steps, 19, 23, GxEPD_BLACK);
|
|
||||||
m_display.setCursor(40, 195);
|
|
||||||
m_display.print(GetSteps());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WatchFace::SetupVolatileMenuStuff()
|
void WatchFace::SetupVolatileMenuStuff()
|
||||||
|
@ -269,7 +214,7 @@ void WatchFace::MenuNTPSyncSelected()
|
||||||
ConnectWiFi();
|
ConnectWiFi();
|
||||||
SyncNTPTime();
|
SyncNTPTime();
|
||||||
DisconnectWiFi();
|
DisconnectWiFi();
|
||||||
m_RTC.Resync();
|
m_features.rtc.Resync();
|
||||||
|
|
||||||
if (m_inMenu) {
|
if (m_inMenu) {
|
||||||
m_inMenu = false;
|
m_inMenu = false;
|
||||||
|
@ -280,7 +225,7 @@ void WatchFace::MenuNTPSyncSelected()
|
||||||
|
|
||||||
void WatchFace::MenuTimeZoneSelected(int tzOffset)
|
void WatchFace::MenuTimeZoneSelected(int tzOffset)
|
||||||
{
|
{
|
||||||
m_tzOffset = tzOffset;
|
m_features.storage.SetTzOffset(tzOffset);
|
||||||
|
|
||||||
if (m_inMenu) {
|
if (m_inMenu) {
|
||||||
m_inMenu = false;
|
m_inMenu = false;
|
||||||
|
@ -291,7 +236,7 @@ void WatchFace::MenuTimeZoneSelected(int tzOffset)
|
||||||
|
|
||||||
void WatchFace::MenuConfirmResetSteps()
|
void WatchFace::MenuConfirmResetSteps()
|
||||||
{
|
{
|
||||||
ResetSteps();
|
m_features.stepCounter.ResetSteps();
|
||||||
|
|
||||||
if (m_inMenu) {
|
if (m_inMenu) {
|
||||||
m_inMenu = false;
|
m_inMenu = false;
|
||||||
|
|
|
@ -2,21 +2,26 @@
|
||||||
|
|
||||||
#include "Watchy.h"
|
#include "Watchy.h"
|
||||||
#include "Menu.h"
|
#include "Menu.h"
|
||||||
|
#include "WatchFacePages/Clock.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class WatchFace : public Watchy
|
class WatchFace : public Watchy
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void Setup() override; // Called after hardware is set up
|
void InitBoot() override; // Called once when the watch starts up
|
||||||
|
void InitWake() override; // Called every time the watch wakes from sleep
|
||||||
void HandleButtonPress(uint64_t buttonMask) override;
|
void HandleButtonPress(uint64_t buttonMask) override;
|
||||||
void HandleDoubleTap() override;
|
void HandleDoubleTap() override;
|
||||||
void HandleTilt() override;
|
void HandleTilt() override;
|
||||||
void DrawWatchFace(bool partialRefresh = false) override;
|
void DrawWatchFace(bool partialRefresh = false) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RTC_DATA_ATTR static bool m_menuSetup;
|
|
||||||
RTC_DATA_ATTR static bool m_inMenu;
|
RTC_DATA_ATTR static bool m_inMenu;
|
||||||
RTC_DATA_ATTR static Menu m_menu;
|
RTC_DATA_ATTR static Menu m_menu;
|
||||||
RTC_DATA_ATTR static int m_tzOffset;
|
RTC_DATA_ATTR static uint8_t m_watchFacePage;
|
||||||
|
std::vector<std::shared_ptr<WatchFacePages::Page>> m_pages = {
|
||||||
|
std::make_shared<WatchFacePages::Clock>(m_display, m_features)
|
||||||
|
};
|
||||||
|
|
||||||
void SetupVolatileMenuStuff();
|
void SetupVolatileMenuStuff();
|
||||||
void DrawBatteryIcon();
|
void DrawBatteryIcon();
|
||||||
|
|
81
src/WatchFacePages/Clock.cpp
Normal file
81
src/WatchFacePages/Clock.cpp
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
#include "Clock.h"
|
||||||
|
#include "../SevenSegment.h"
|
||||||
|
#include "../Icons.h"
|
||||||
|
#include <Fonts/FreeSans9pt7b.h>
|
||||||
|
#include <Fonts/FreeMonoBold9pt7b.h>
|
||||||
|
#include <Fonts/FreeMonoBold12pt7b.h>
|
||||||
|
|
||||||
|
WatchFacePages::Clock::Clock(WatchyDisplay & display, WatchFeatures::WatchFeatures & features)
|
||||||
|
: m_display(display), m_features(features)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchFacePages::Clock::InitBoot()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchFacePages::Clock::InitWake()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchFacePages::Clock::DrawPage(bool partialRefresh)
|
||||||
|
{
|
||||||
|
m_display.setFullWindow();
|
||||||
|
|
||||||
|
m_display.fillScreen(GxEPD_WHITE);
|
||||||
|
DrawBatteryIcon();
|
||||||
|
|
||||||
|
tmElements_t currentTime;
|
||||||
|
m_features.rtc.Get(currentTime);
|
||||||
|
int tzOffset = m_features.storage.GetTzOffset();
|
||||||
|
m_features.rtc.OffsetTime(currentTime, tzOffset);
|
||||||
|
SevenSegment sevenSegment(30, 60, 6, 5, 5);
|
||||||
|
|
||||||
|
if (currentTime.Hour < 10) {
|
||||||
|
sevenSegment.DrawDigit(m_display, 0, 15, 75, GxEPD_BLACK);
|
||||||
|
} else {
|
||||||
|
sevenSegment.DrawDigit(m_display, currentTime.Hour / 10, 20, 75, GxEPD_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
sevenSegment.DrawDigit(m_display, currentTime.Hour % 10, 60, 75, GxEPD_BLACK);
|
||||||
|
|
||||||
|
if (currentTime.Minute < 10) {
|
||||||
|
sevenSegment.DrawDigit(m_display, 0, 110, 75, GxEPD_BLACK);
|
||||||
|
} else {
|
||||||
|
sevenSegment.DrawDigit(m_display, currentTime.Minute / 10, 110, 75, GxEPD_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
sevenSegment.DrawDigit(m_display, currentTime.Minute % 10, 150, 75, GxEPD_BLACK);
|
||||||
|
|
||||||
|
m_display.fillRect(97, 90, 5, 5, GxEPD_BLACK);
|
||||||
|
m_display.fillRect(97, 110, 5, 5, GxEPD_BLACK);
|
||||||
|
|
||||||
|
m_display.display(partialRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchFacePages::Clock::DrawBatteryIcon()
|
||||||
|
{
|
||||||
|
m_display.fillRect(200 - 48, 5, 43, 23, GxEPD_BLACK);
|
||||||
|
m_display.fillRect(200 - 46, 7, 39, 19, GxEPD_WHITE);
|
||||||
|
int intLevel = m_features.battery.GetPercentage();
|
||||||
|
uint8_t level = intLevel / 100.0f;
|
||||||
|
|
||||||
|
m_display.fillRect(200 - 44, 9, (int)std::round(35.0f * level), 15, GxEPD_BLACK);
|
||||||
|
|
||||||
|
int x = 200 - 85;
|
||||||
|
if (intLevel == 100) {
|
||||||
|
x -= 13;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
m_display.setCursor(x, 22);
|
||||||
|
m_display.setTextColor(GxEPD_BLACK);
|
||||||
|
m_display.print(intLevel);
|
||||||
|
m_display.print("%");
|
||||||
|
|
||||||
|
m_display.setFont(&FreeMonoBold12pt7b);
|
||||||
|
m_display.drawBitmap(10, 177, Icons::steps, 19, 23, GxEPD_BLACK);
|
||||||
|
m_display.setCursor(40, 195);
|
||||||
|
m_display.print(m_features.stepCounter.GetSteps());
|
||||||
|
}
|
25
src/WatchFacePages/Clock.h
Normal file
25
src/WatchFacePages/Clock.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Page.h"
|
||||||
|
#include "../WatchyDisplay.h"
|
||||||
|
#include "../WatchFeatures/WatchFeatures.h"
|
||||||
|
|
||||||
|
namespace WatchFacePages
|
||||||
|
{
|
||||||
|
class Clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WatchFacePages::Clock : public WatchFacePages::Page
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Clock(WatchyDisplay & display, WatchFeatures::WatchFeatures & features);
|
||||||
|
void InitBoot() override;
|
||||||
|
void InitWake() override;
|
||||||
|
void DrawPage(bool partialRefresh = false) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DrawBatteryIcon();
|
||||||
|
|
||||||
|
WatchyDisplay & m_display;
|
||||||
|
WatchFeatures::WatchFeatures & m_features;
|
||||||
|
};
|
15
src/WatchFacePages/Page.h
Normal file
15
src/WatchFacePages/Page.h
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace WatchFacePages
|
||||||
|
{
|
||||||
|
class Page;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WatchFacePages::Page
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Page() {};
|
||||||
|
virtual void InitBoot() {};
|
||||||
|
virtual void InitWake() {};
|
||||||
|
virtual void DrawPage(bool partialRefresh = false) = 0;
|
||||||
|
};
|
29
src/WatchFeatures/Battery.cpp
Normal file
29
src/WatchFeatures/Battery.cpp
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#include "../config.h"
|
||||||
|
#include "Battery.h"
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
WatchFeatures::Battery::Battery()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
float WatchFeatures::Battery::GetVoltage()
|
||||||
|
{
|
||||||
|
return analogReadMilliVolts(BATT_ADC_PIN) / 1000.0f * 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t WatchFeatures::Battery::GetPercentage()
|
||||||
|
{
|
||||||
|
float voltage = GetVoltage();
|
||||||
|
|
||||||
|
float level = (GetVoltage() - 3.6f) / 0.6f;
|
||||||
|
if (level < 0.0f) {
|
||||||
|
level = 0.0f;
|
||||||
|
} else if (level > 1.0f) {
|
||||||
|
level = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
level = 0.5f - sin(asin(1.0f - 2.0f * level) / 3.0f);
|
||||||
|
|
||||||
|
return std::round(level * 100.0f);
|
||||||
|
}
|
16
src/WatchFeatures/Battery.h
Normal file
16
src/WatchFeatures/Battery.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace WatchFeatures
|
||||||
|
{
|
||||||
|
class Battery;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WatchFeatures::Battery
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Battery();
|
||||||
|
float GetVoltage();
|
||||||
|
uint8_t GetPercentage();
|
||||||
|
};
|
|
@ -1,21 +1,14 @@
|
||||||
#include "WatchyRTC.h"
|
#include "RTC.h"
|
||||||
|
#include <EEPROM.h>
|
||||||
|
|
||||||
#if (UPDATE_INTERVAL > 255)
|
#if (UPDATE_INTERVAL > 255)
|
||||||
#error "UPDATE_INTERVAL must be either a multiple of 60, or less than 256 seconds"
|
#error "UPDATE_INTERVAL must be either a multiple of 60, or less than 256 seconds"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
RTC_DATA_ATTR bool WatchyRTC::m_timerSet = false;
|
RTC_DATA_ATTR bool WatchFeatures::RTC::m_timerSet = false;
|
||||||
RTC_DATA_ATTR bool WatchyRTC::m_initialTimer = true;
|
RTC_DATA_ATTR bool WatchFeatures::RTC::m_initialTimer = true;
|
||||||
|
|
||||||
WatchyRTC::WatchyRTC()
|
void WatchFeatures::RTC::Get(tmElements_t & tm)
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void WatchyRTC::Init()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void WatchyRTC::Get(tmElements_t & tm, int offsetInSeconds)
|
|
||||||
{
|
{
|
||||||
rtc_pcf.getDateTime();
|
rtc_pcf.getDateTime();
|
||||||
tm.Year = y2kYearToTm(rtc_pcf.getYear());
|
tm.Year = y2kYearToTm(rtc_pcf.getYear());
|
||||||
|
@ -25,10 +18,9 @@ void WatchyRTC::Get(tmElements_t & tm, int offsetInSeconds)
|
||||||
tm.Hour = rtc_pcf.getHour();
|
tm.Hour = rtc_pcf.getHour();
|
||||||
tm.Minute = rtc_pcf.getMinute();
|
tm.Minute = rtc_pcf.getMinute();
|
||||||
tm.Second = rtc_pcf.getSecond();
|
tm.Second = rtc_pcf.getSecond();
|
||||||
OffsetTime(tm, offsetInSeconds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WatchyRTC::Set(tmElements_t tm)
|
void WatchFeatures::RTC::Set(tmElements_t tm)
|
||||||
{
|
{
|
||||||
time_t t = makeTime(tm); // make and break to calculate tm.Wday
|
time_t t = makeTime(tm); // make and break to calculate tm.Wday
|
||||||
breakTime(t, tm);
|
breakTime(t, tm);
|
||||||
|
@ -36,14 +28,14 @@ void WatchyRTC::Set(tmElements_t tm)
|
||||||
rtc_pcf.setDateTime(tm.Day, tm.Wday - 1, tm.Month, 0, tmYearToY2k(tm.Year), tm.Hour, tm.Minute, tm.Second);
|
rtc_pcf.setDateTime(tm.Day, tm.Wday - 1, tm.Month, 0, tmYearToY2k(tm.Year), tm.Hour, tm.Minute, tm.Second);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WatchyRTC::SetTimer()
|
void WatchFeatures::RTC::SetTimer()
|
||||||
{
|
{
|
||||||
if (!m_timerSet) {
|
if (!m_timerSet) {
|
||||||
Resync();
|
Resync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WatchyRTC::OffsetTime(tmElements_t & tm, int offsetInSeconds)
|
void WatchFeatures::RTC::OffsetTime(tmElements_t & tm, int offsetInSeconds)
|
||||||
{
|
{
|
||||||
int year = tm.Year;
|
int year = tm.Year;
|
||||||
int month = tm.Month;
|
int month = tm.Month;
|
||||||
|
@ -108,7 +100,7 @@ void WatchyRTC::OffsetTime(tmElements_t & tm, int offsetInSeconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement more advanced wakeup logic, i.e. > 255 seconds that are not a multiple of 60
|
// TODO: implement more advanced wakeup logic, i.e. > 255 seconds that are not a multiple of 60
|
||||||
bool WatchyRTC::CheckWakeup()
|
bool WatchFeatures::RTC::CheckWakeup()
|
||||||
{
|
{
|
||||||
if(m_initialTimer) {
|
if(m_initialTimer) {
|
||||||
m_initialTimer = false;
|
m_initialTimer = false;
|
||||||
|
@ -132,7 +124,7 @@ bool WatchyRTC::CheckWakeup()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WatchyRTC::Resync()
|
void WatchFeatures::RTC::Resync()
|
||||||
{
|
{
|
||||||
rtc_pcf.getDateTime();
|
rtc_pcf.getDateTime();
|
||||||
// Sleep just long enough to get to a multiple of the update interval, makes updates happen on exact turn of the minute
|
// Sleep just long enough to get to a multiple of the update interval, makes updates happen on exact turn of the minute
|
|
@ -6,17 +6,17 @@
|
||||||
#include <TimeLib.h>
|
#include <TimeLib.h>
|
||||||
#include <Rtc_Pcf8563.h>
|
#include <Rtc_Pcf8563.h>
|
||||||
|
|
||||||
#define RTC_PCF_ADDR 0x51
|
namespace WatchFeatures
|
||||||
#define YEAR_OFFSET_PCF 2000
|
{
|
||||||
|
class RTC;
|
||||||
|
}
|
||||||
|
|
||||||
class WatchyRTC {
|
class WatchFeatures::RTC {
|
||||||
public:
|
public:
|
||||||
Rtc_Pcf8563 rtc_pcf;
|
Rtc_Pcf8563 rtc_pcf;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WatchyRTC();
|
void Get(tmElements_t & tm);
|
||||||
void Init();
|
|
||||||
void Get(tmElements_t & tm, int offsetInSeconds = 0);
|
|
||||||
void Set(tmElements_t tm);
|
void Set(tmElements_t tm);
|
||||||
void SetTimer();
|
void SetTimer();
|
||||||
bool CheckWakeup(); // Checks to really wake up or not, also resets the timer after the initial sleep
|
bool CheckWakeup(); // Checks to really wake up or not, also resets the timer after the initial sleep
|
16
src/WatchFeatures/StepCounter.cpp
Normal file
16
src/WatchFeatures/StepCounter.cpp
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#include "StepCounter.h"
|
||||||
|
|
||||||
|
WatchFeatures::StepCounter::StepCounter(BMA423 & sensor) :
|
||||||
|
m_sensor(sensor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t WatchFeatures::StepCounter::GetSteps()
|
||||||
|
{
|
||||||
|
return m_sensor.getCounter();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchFeatures::StepCounter::ResetSteps()
|
||||||
|
{
|
||||||
|
m_sensor.resetStepCounter();
|
||||||
|
}
|
20
src/WatchFeatures/StepCounter.h
Normal file
20
src/WatchFeatures/StepCounter.h
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "../bma.h"
|
||||||
|
|
||||||
|
namespace WatchFeatures
|
||||||
|
{
|
||||||
|
class StepCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WatchFeatures::StepCounter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
StepCounter(BMA423 & sensor);
|
||||||
|
uint64_t GetSteps();
|
||||||
|
void ResetSteps();
|
||||||
|
|
||||||
|
private:
|
||||||
|
BMA423 & m_sensor;
|
||||||
|
};
|
58
src/WatchFeatures/Storage.cpp
Normal file
58
src/WatchFeatures/Storage.cpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#include "../config.h"
|
||||||
|
#include "Storage.h"
|
||||||
|
#include <EEPROM.h>
|
||||||
|
|
||||||
|
WatchFeatures::Storage::Storage()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchFeatures::Storage::InitBoot()
|
||||||
|
{
|
||||||
|
EEPROM.begin(512);
|
||||||
|
if (EEPROM.read(EEPROM_LOCATION_MAGIC) != EEPROM_MAGIC1
|
||||||
|
|| EEPROM.read(EEPROM_LOCATION_MAGIC + 1) != EEPROM_MAGIC2)
|
||||||
|
{
|
||||||
|
Serial.println("Initializing EEPROM");
|
||||||
|
EEPROM.write(EEPROM_LOCATION_MAGIC, EEPROM_MAGIC1);
|
||||||
|
EEPROM.write(EEPROM_LOCATION_MAGIC + 1, EEPROM_MAGIC2);
|
||||||
|
uint16_t version = EEPROM_VERSION;
|
||||||
|
EEPROM.write(EEPROM_LOCATION_VERSION, version & 0xFF);
|
||||||
|
EEPROM.write(EEPROM_LOCATION_VERSION + 1, version >> 8);
|
||||||
|
|
||||||
|
int offset = DEFAULT_TZ_OFFSET;
|
||||||
|
EEPROM.write(EEPROM_LOCATION_TZ_OFFSET, offset & 0xFF);
|
||||||
|
EEPROM.write(EEPROM_LOCATION_TZ_OFFSET + 1, (offset >> 8) & 0xFF);
|
||||||
|
EEPROM.write(EEPROM_LOCATION_TZ_OFFSET + 2, (offset >> 16) & 0xFF);
|
||||||
|
EEPROM.write(EEPROM_LOCATION_TZ_OFFSET + 3, (offset >> 24) & 0xFF);
|
||||||
|
EEPROM.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t version = EEPROM.read(EEPROM_LOCATION_VERSION) | (EEPROM.read(EEPROM_LOCATION_VERSION + 1) << 8);
|
||||||
|
if (version != EEPROM_VERSION)
|
||||||
|
{
|
||||||
|
// TODO: Handle version mismatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchFeatures::Storage::InitWake()
|
||||||
|
{
|
||||||
|
EEPROM.begin(512);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t WatchFeatures::Storage::GetTzOffset()
|
||||||
|
{
|
||||||
|
return EEPROM.read(EEPROM_LOCATION_TZ_OFFSET)
|
||||||
|
| (EEPROM.read(EEPROM_LOCATION_TZ_OFFSET + 1) << 8)
|
||||||
|
| (EEPROM.read(EEPROM_LOCATION_TZ_OFFSET + 2) << 16)
|
||||||
|
| (EEPROM.read(EEPROM_LOCATION_TZ_OFFSET + 3) << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchFeatures::Storage::SetTzOffset(int offset)
|
||||||
|
{
|
||||||
|
EEPROM.write(EEPROM_LOCATION_TZ_OFFSET, offset & 0xFF);
|
||||||
|
EEPROM.write(EEPROM_LOCATION_TZ_OFFSET + 1, (offset >> 8) & 0xFF);
|
||||||
|
EEPROM.write(EEPROM_LOCATION_TZ_OFFSET + 2, (offset >> 16) & 0xFF);
|
||||||
|
EEPROM.write(EEPROM_LOCATION_TZ_OFFSET + 3, (offset >> 24) & 0xFF);
|
||||||
|
EEPROM.commit();
|
||||||
|
}
|
16
src/WatchFeatures/Storage.h
Normal file
16
src/WatchFeatures/Storage.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace WatchFeatures
|
||||||
|
{
|
||||||
|
class Storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WatchFeatures::Storage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Storage();
|
||||||
|
void InitBoot();
|
||||||
|
void InitWake();
|
||||||
|
int GetTzOffset();
|
||||||
|
void SetTzOffset(int offset);
|
||||||
|
};
|
24
src/WatchFeatures/WatchFeatures.h
Normal file
24
src/WatchFeatures/WatchFeatures.h
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Battery.h"
|
||||||
|
#include "StepCounter.h"
|
||||||
|
#include "RTC.h"
|
||||||
|
#include "Storage.h"
|
||||||
|
|
||||||
|
namespace WatchFeatures
|
||||||
|
{
|
||||||
|
struct WatchFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WatchFeatures::WatchFeatures
|
||||||
|
{
|
||||||
|
WatchFeatures(BMA423 & sensor)
|
||||||
|
: battery(), stepCounter(sensor), rtc()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Battery battery;
|
||||||
|
StepCounter stepCounter;
|
||||||
|
RTC rtc;
|
||||||
|
Storage storage;
|
||||||
|
};
|
|
@ -3,44 +3,41 @@
|
||||||
|
|
||||||
WatchyDisplayBase Watchy::m_displayBase;
|
WatchyDisplayBase Watchy::m_displayBase;
|
||||||
WatchyDisplay Watchy::m_display(Watchy::m_displayBase);
|
WatchyDisplay Watchy::m_display(Watchy::m_displayBase);
|
||||||
WatchyRTC Watchy::m_RTC;
|
|
||||||
|
|
||||||
RTC_DATA_ATTR BMA423 Watchy::m_sensor;
|
RTC_DATA_ATTR BMA423 Watchy::m_sensor;
|
||||||
RTC_DATA_ATTR bool g_displayFullInit = true;
|
RTC_DATA_ATTR bool g_displayFullInit = true;
|
||||||
|
|
||||||
Watchy::Watchy()
|
Watchy::Watchy()
|
||||||
|
: m_features(m_sensor)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void Watchy::Init()
|
void Watchy::Wake()
|
||||||
{
|
{
|
||||||
esp_sleep_wakeup_cause_t wakeup_reason;
|
esp_sleep_wakeup_cause_t wakeup_reason;
|
||||||
wakeup_reason = esp_sleep_get_wakeup_cause();
|
wakeup_reason = esp_sleep_get_wakeup_cause();
|
||||||
|
|
||||||
if (wakeup_reason == ESP_SLEEP_WAKEUP_EXT0) {
|
if (wakeup_reason == ESP_SLEEP_WAKEUP_EXT0) {
|
||||||
if(!m_RTC.CheckWakeup()) {
|
if(!m_features.rtc.CheckWakeup()) {
|
||||||
DeepSleep();
|
DeepSleep();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Wire.begin(SDA, SCL);
|
|
||||||
m_display.epd2.selectSPI(SPI, SPISettings(20000000, MSBFIRST, SPI_MODE0));
|
|
||||||
m_display.init(0, g_displayFullInit, 10, true);
|
|
||||||
SetBusyCallback();
|
|
||||||
|
|
||||||
Setup();
|
|
||||||
|
|
||||||
switch(wakeup_reason) {
|
switch(wakeup_reason) {
|
||||||
case ESP_SLEEP_WAKEUP_EXT0:
|
case ESP_SLEEP_WAKEUP_EXT0:
|
||||||
{
|
{
|
||||||
// RTC interrupt
|
// RTC interrupt
|
||||||
|
InitWakeInternal();
|
||||||
ShowWatchFace(true);
|
ShowWatchFace(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_SLEEP_WAKEUP_EXT1:
|
case ESP_SLEEP_WAKEUP_EXT1:
|
||||||
{
|
{
|
||||||
|
// Button press or accelerometer interrupt
|
||||||
|
InitWakeInternal();
|
||||||
uint64_t wakeupBit = esp_sleep_get_ext1_wakeup_status();
|
uint64_t wakeupBit = esp_sleep_get_ext1_wakeup_status();
|
||||||
if (wakeupBit & ACC_INT_MASK) {
|
if (wakeupBit & ACC_INT_MASK) {
|
||||||
|
// Accelerometer interrupt
|
||||||
m_sensor.getINT();
|
m_sensor.getINT();
|
||||||
uint8_t irqMask = m_sensor.getIRQMASK();
|
uint8_t irqMask = m_sensor.getIRQMASK();
|
||||||
|
|
||||||
|
@ -54,24 +51,14 @@ void Watchy::Init()
|
||||||
|
|
||||||
m_sensor.getINT();
|
m_sensor.getINT();
|
||||||
} else if (wakeupBit & BTN_PIN_MASK) {
|
} else if (wakeupBit & BTN_PIN_MASK) {
|
||||||
|
|
||||||
// Button press
|
// Button press
|
||||||
HandleButtonPress(wakeupBit);
|
HandleButtonPress(wakeupBit);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Button press
|
|
||||||
HandleButtonPress(wakeupBit);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
BmaConfig();
|
InitBootInternal();
|
||||||
m_RTC.Init();
|
|
||||||
ConnectWiFi();
|
|
||||||
SyncNTPTime();
|
|
||||||
DisconnectWiFi();
|
|
||||||
|
|
||||||
ShowWatchFace(false);
|
ShowWatchFace(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -102,7 +89,7 @@ void Watchy::DeepSleep()
|
||||||
BTN_PIN_MASK | ACC_INT_MASK,
|
BTN_PIN_MASK | ACC_INT_MASK,
|
||||||
ESP_EXT1_WAKEUP_ANY_HIGH);
|
ESP_EXT1_WAKEUP_ANY_HIGH);
|
||||||
|
|
||||||
m_RTC.SetTimer();
|
m_features.rtc.SetTimer();
|
||||||
|
|
||||||
esp_deep_sleep_start();
|
esp_deep_sleep_start();
|
||||||
}
|
}
|
||||||
|
@ -125,21 +112,6 @@ void Watchy::VibeMotor(uint8_t intervalMs, uint8_t length)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float Watchy::GetBatteryVoltage()
|
|
||||||
{
|
|
||||||
return analogReadMilliVolts(BATT_ADC_PIN) / 1000.0f * 2.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Watchy::GetSteps()
|
|
||||||
{
|
|
||||||
return m_sensor.getCounter();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::ResetSteps()
|
|
||||||
{
|
|
||||||
m_sensor.resetStepCounter();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::ConnectWiFi()
|
void Watchy::ConnectWiFi()
|
||||||
{
|
{
|
||||||
if(WiFi.begin(WIFI_SSID, WIFI_PASS) == WL_CONNECT_FAILED) {
|
if(WiFi.begin(WIFI_SSID, WIFI_PASS) == WL_CONNECT_FAILED) {
|
||||||
|
@ -158,14 +130,14 @@ void Watchy::ConnectWiFi()
|
||||||
void Watchy::SyncNTPTime()
|
void Watchy::SyncNTPTime()
|
||||||
{
|
{
|
||||||
WiFiUDP ntpUDP;
|
WiFiUDP ntpUDP;
|
||||||
// GMT offset should be 0, RTC class will adjust to local time
|
// GMT offset should be 0, since RTC is set to UTC
|
||||||
NTPClient timeClient(ntpUDP, NTP_SERVER, 0);
|
NTPClient timeClient(ntpUDP, NTP_SERVER, 0);
|
||||||
timeClient.begin();
|
timeClient.begin();
|
||||||
bool success = timeClient.forceUpdate();
|
bool success = timeClient.forceUpdate();
|
||||||
if (success) {
|
if (success) {
|
||||||
tmElements_t tm;
|
tmElements_t tm;
|
||||||
breakTime((time_t)timeClient.getEpochTime(), tm);
|
breakTime((time_t)timeClient.getEpochTime(), tm);
|
||||||
m_RTC.Set(tm);
|
m_features.rtc.Set(tm);
|
||||||
} else {
|
} else {
|
||||||
Serial.begin(9600);
|
Serial.begin(9600);
|
||||||
Serial.println("Failed to get NTP time");
|
Serial.println("Failed to get NTP time");
|
||||||
|
@ -187,6 +159,29 @@ void Watchy::ClearBusyCallback()
|
||||||
m_display.epd2.setBusyCallback(nullptr);
|
m_display.epd2.setBusyCallback(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Watchy::InitBootInternal()
|
||||||
|
{
|
||||||
|
Wire.begin(SDA, SCL);
|
||||||
|
m_display.epd2.selectSPI(SPI, SPISettings(20000000, MSBFIRST, SPI_MODE0));
|
||||||
|
m_display.init(0, g_displayFullInit, 10, true);
|
||||||
|
SetBusyCallback();
|
||||||
|
|
||||||
|
BmaConfig();
|
||||||
|
m_features.storage.InitBoot();
|
||||||
|
InitBoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Watchy::InitWakeInternal()
|
||||||
|
{
|
||||||
|
Wire.begin(SDA, SCL);
|
||||||
|
m_display.epd2.selectSPI(SPI, SPISettings(20000000, MSBFIRST, SPI_MODE0));
|
||||||
|
m_display.init(0, g_displayFullInit, 10, true);
|
||||||
|
SetBusyCallback();
|
||||||
|
|
||||||
|
m_features.storage.InitWake();
|
||||||
|
InitWake();
|
||||||
|
}
|
||||||
|
|
||||||
void Watchy::SetBusyCallback()
|
void Watchy::SetBusyCallback()
|
||||||
{
|
{
|
||||||
m_display.epd2.setBusyCallback(DisplayBusyCallback);
|
m_display.epd2.setBusyCallback(DisplayBusyCallback);
|
||||||
|
@ -309,3 +304,4 @@ void Watchy::BmaConfig()
|
||||||
m_sensor.enableWakeupInterrupt();
|
m_sensor.enableWakeupInterrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
17
src/Watchy.h
17
src/Watchy.h
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "WatchyDisplay.h"
|
#include "WatchyDisplay.h"
|
||||||
#include "WatchyRTC.h"
|
#include "WatchFeatures/WatchFeatures.h"
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <WiFiUdp.h>
|
#include <WiFiUdp.h>
|
||||||
#include <NTPClient.h>
|
#include <NTPClient.h>
|
||||||
|
@ -12,12 +12,10 @@ class Watchy
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Watchy();
|
Watchy();
|
||||||
void Init();
|
void Wake();
|
||||||
void DeepSleep();
|
void DeepSleep();
|
||||||
void VibeMotor(uint8_t intervalMs = 100, uint8_t length = 20);
|
void VibeMotor(uint8_t intervalMs = 100, uint8_t length = 20);
|
||||||
float GetBatteryVoltage();
|
float GetBatteryVoltage();
|
||||||
uint64_t GetSteps();
|
|
||||||
void ResetSteps();
|
|
||||||
void ConnectWiFi();
|
void ConnectWiFi();
|
||||||
void SyncNTPTime();
|
void SyncNTPTime();
|
||||||
void DisconnectWiFi();
|
void DisconnectWiFi();
|
||||||
|
@ -26,9 +24,8 @@ public:
|
||||||
void ClearBusyCallback();
|
void ClearBusyCallback();
|
||||||
void SetBusyCallback();
|
void SetBusyCallback();
|
||||||
|
|
||||||
// Called after hardware is setup
|
virtual void InitBoot() {}; // Called on first boot
|
||||||
virtual void Setup() = 0;
|
virtual void InitWake() {}; // Called every time the watch wakes from sleep
|
||||||
|
|
||||||
virtual void HandleButtonPress(uint64_t buttonMask) = 0;
|
virtual void HandleButtonPress(uint64_t buttonMask) = 0;
|
||||||
virtual void HandleDoubleTap() {}
|
virtual void HandleDoubleTap() {}
|
||||||
virtual void HandleTilt() {}
|
virtual void HandleTilt() {}
|
||||||
|
@ -36,13 +33,15 @@ public:
|
||||||
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void InitBootInternal();
|
||||||
|
void InitWakeInternal();
|
||||||
|
|
||||||
static void DisplayBusyCallback(const void *);
|
static void DisplayBusyCallback(const void *);
|
||||||
|
|
||||||
static WatchyDisplayBase m_displayBase;
|
static WatchyDisplayBase m_displayBase;
|
||||||
static WatchyDisplay m_display;
|
static WatchyDisplay m_display;
|
||||||
static WatchyRTC m_RTC;
|
|
||||||
|
|
||||||
static RTC_DATA_ATTR BMA423 m_sensor;
|
static RTC_DATA_ATTR BMA423 m_sensor;
|
||||||
|
WatchFeatures::WatchFeatures m_features;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void BmaConfig();
|
void BmaConfig();
|
||||||
|
|
19
src/config.h
19
src/config.h
|
@ -13,6 +13,8 @@
|
||||||
#define ACC_INT_2_PIN 12
|
#define ACC_INT_2_PIN 12
|
||||||
#define VIB_MOTOR_PIN 13
|
#define VIB_MOTOR_PIN 13
|
||||||
#define RTC_INT_PIN 27
|
#define RTC_INT_PIN 27
|
||||||
|
#define RTC_PCF_ADDR 0x51
|
||||||
|
#define YEAR_OFFSET_PCF 2000
|
||||||
|
|
||||||
#define MENU_BTN_MASK GPIO_SEL_26
|
#define MENU_BTN_MASK GPIO_SEL_26
|
||||||
#define BACK_BTN_MASK GPIO_SEL_25
|
#define BACK_BTN_MASK GPIO_SEL_25
|
||||||
|
@ -24,9 +26,20 @@
|
||||||
#define DISPLAY_WIDTH 200
|
#define DISPLAY_WIDTH 200
|
||||||
#define DISPLAY_HEIGHT 200
|
#define DISPLAY_HEIGHT 200
|
||||||
|
|
||||||
#define WIFI_SSID "<ssid>"
|
#define EEPROM_LOCATION_MAGIC 0
|
||||||
#define WIFI_PASS "<pass>"
|
#define EEPROM_LOCATION_VERSION 2
|
||||||
#define TZ_OFFSET 3600 * 3
|
#define EEPROM_LOCATION_TZ_OFFSET 4
|
||||||
|
|
||||||
|
#define EEPROM_MAGIC1 0xf0
|
||||||
|
#define EEPROM_MAGIC2 0x0d
|
||||||
|
#define EEPROM_VERSION 1
|
||||||
|
|
||||||
|
#include "secrets.h"
|
||||||
|
#if !defined(WIFI_SSID) || !defined(WIFI_PASS)
|
||||||
|
#error "Please define WIFI_SSID and WIFI_PASS in secrets.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DEFAULT_TZ_OFFSET 3600 * 3
|
||||||
|
|
||||||
#define NTP_SERVER "pool.ntp.org"
|
#define NTP_SERVER "pool.ntp.org"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue