mirror of https://github.com/sqfmi/Watchy.git
Trying to fork harder.
parent
cd17d2046d
commit
66a4c9b84e
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2020 SQFMI
|
Copyright (c) 2022 SQFMI
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
#include "DSEG7_Classic_Regular_39.h"
|
#include "DSEG7_Classic_Regular_39.h"
|
||||||
#include "icons.h"
|
#include "icons.h"
|
||||||
|
|
||||||
class Watchy7SEG : public Watchy{
|
class Watchy7SEG : public WatchyExpanded{
|
||||||
using Watchy::Watchy;
|
using WatchyExpanded::WatchyExpanded;
|
||||||
public:
|
public:
|
||||||
void drawWatchFace();
|
void drawWatchFace();
|
||||||
void drawTime();
|
void drawTime();
|
||||||
|
@ -19,4 +19,4 @@ class Watchy7SEG : public Watchy{
|
||||||
void drawBattery();
|
void drawBattery();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
#include <Watchy.h>
|
#include <Watchy.h>
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
Watchy watchy(settings);
|
WatchyExpanded watchy(settings);
|
||||||
|
|
||||||
void setup(){
|
void setup(){
|
||||||
watchy.init();
|
watchy.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop(){}
|
void loop(){}
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
#include <Watchy.h>
|
#include <Watchy.h>
|
||||||
#include "Px437_IBM_BIOS5pt7b.h"
|
#include "Px437_IBM_BIOS5pt7b.h"
|
||||||
|
|
||||||
class WatchyDOS : public Watchy{
|
class WatchyDOS : public WatchyExpanded{
|
||||||
using Watchy::Watchy;
|
using WatchyExpanded::WatchyExpanded;
|
||||||
public:
|
public:
|
||||||
void drawWatchFace();
|
void drawWatchFace();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
#include <Watchy.h>
|
#include <Watchy.h>
|
||||||
#include "macpaint.h"
|
#include "macpaint.h"
|
||||||
|
|
||||||
class WatchyMacPaint : public Watchy{
|
class WatchyMacPaint : public WatchyExpanded{
|
||||||
using Watchy::Watchy;
|
using WatchyExpanded::WatchyExpanded;
|
||||||
public:
|
public:
|
||||||
void drawWatchFace();
|
void drawWatchFace();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
#include <Watchy.h>
|
#include <Watchy.h>
|
||||||
#include "pokemon.h"
|
#include "pokemon.h"
|
||||||
|
|
||||||
class WatchyPokemon : public Watchy{
|
class WatchyPokemon : public WatchyExpanded{
|
||||||
using Watchy::Watchy;
|
using WatchyExpanded::WatchyExpanded;
|
||||||
public:
|
public:
|
||||||
void drawWatchFace();
|
void drawWatchFace();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// STARRY HORIZON for Watchy by SQFMI
|
// STARRY HORIZON for WatchyExpanded by SQFMI
|
||||||
// Copyright 2021 Dan Delany dan.delany@gmail.com
|
// Copyright 2021 Dan Delany dan.delany@gmail.com
|
||||||
// Released under free MIT License : https://github.com/dandelany/watchy-faces/blob/main/LICENSE
|
// Released under free MIT License : https://github.com/dandelany/watchy-faces/blob/main/LICENSE
|
||||||
|
|
||||||
|
@ -49,9 +49,9 @@ struct xyPoint rotatePointAround(int x, int y, int ox, int oy, double angle) {
|
||||||
return newPoint;
|
return newPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
class StarryHorizon : public Watchy {
|
class StarryHorizon : public WatchyExpanded {
|
||||||
public:
|
public:
|
||||||
StarryHorizon(const watchySettings& s) : Watchy(s) {
|
StarryHorizon(const watchySettings& s) : WatchyExpanded(s) {
|
||||||
// uncomment to re-generate stars
|
// uncomment to re-generate stars
|
||||||
// initStars();
|
// initStars();
|
||||||
}
|
}
|
||||||
|
@ -147,5 +147,5 @@ void setup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
// this should never run, Watchy deep sleeps after init();
|
// this should never run, WatchyExpanded deep sleeps after init();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
#include <Watchy.h>
|
#include <Watchy.h>
|
||||||
#include "tetris.h"
|
#include "tetris.h"
|
||||||
|
|
||||||
class WatchyTetris : public Watchy{
|
class WatchyTetris : public WatchyExpanded{
|
||||||
using Watchy::Watchy;
|
using WatchyExpanded::WatchyExpanded;
|
||||||
public:
|
public:
|
||||||
WatchyTetris();
|
WatchyTetris();
|
||||||
void drawWatchFace();
|
void drawWatchFace();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "Watchy",
|
"name": "WatchyExpanded",
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"description": "Watchy - An Open Source E-Paper Watch by SQFMI",
|
"description": "Watchy - An Open Source E-Paper Watch by SQFMI",
|
||||||
"authors": [
|
"authors": [
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
name=Watchy
|
name=WatchyExpanded
|
||||||
version=1.4.0
|
version=2022.4.10
|
||||||
author=SQFMI
|
author=Michael-Paul Moore
|
||||||
maintainer=SQFMI
|
maintainer=MichaelPaul Moore
|
||||||
sentence=Watchy - An Open Source E-Paper Watch by SQFMI
|
sentence=Watchy - An Open Source E-Paper Watch by SQFMI
|
||||||
paragraph=This library contains drivers and code samples for Watchy
|
paragraph=This library contains drivers for WatchyExpanded
|
||||||
category=Other
|
category=Other
|
||||||
url=https://watchy.sqfmi.com
|
url=https://watchy.sqfmi.com
|
||||||
architectures=esp32
|
architectures=esp32
|
||||||
|
|
|
@ -93,7 +93,7 @@ BLE::~BLE(void)
|
||||||
|
|
||||||
//
|
//
|
||||||
// begin
|
// begin
|
||||||
bool BLE::begin(const char* localName = "Watchy BLE OTA") {
|
bool BLE::begin(const char* localName = "WatchyExpanded BLE OTA") {
|
||||||
// Create the BLE Device
|
// Create the BLE Device
|
||||||
BLEDevice::init(localName);
|
BLEDevice::init(localName);
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ bool BLE::begin(const char* localName = "Watchy BLE OTA") {
|
||||||
|
|
||||||
uint8_t hardwareVersion[5] = {HARDWARE_VERSION_MAJOR, HARDWARE_VERSION_MINOR, SOFTWARE_VERSION_MAJOR, SOFTWARE_VERSION_MINOR, SOFTWARE_VERSION_PATCH};
|
uint8_t hardwareVersion[5] = {HARDWARE_VERSION_MAJOR, HARDWARE_VERSION_MINOR, SOFTWARE_VERSION_MAJOR, SOFTWARE_VERSION_MINOR, SOFTWARE_VERSION_PATCH};
|
||||||
pVersionCharacteristic->setValue((uint8_t*)hardwareVersion, 5);
|
pVersionCharacteristic->setValue((uint8_t*)hardwareVersion, 5);
|
||||||
pWatchFaceNameCharacteristic->setValue("Watchy 7 Segment");
|
pWatchFaceNameCharacteristic->setValue("WatchyExpanded 7 Segment");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -150,4 +150,4 @@ int BLE::updateStatus(){
|
||||||
|
|
||||||
int BLE::howManyBytes(){
|
int BLE::howManyBytes(){
|
||||||
return bytesReceived;
|
return bytesReceived;
|
||||||
}
|
}
|
||||||
|
|
956
src/Watchy.cpp
956
src/Watchy.cpp
|
@ -1,956 +0,0 @@
|
||||||
#include "Watchy.h"
|
|
||||||
|
|
||||||
WatchyRTC Watchy::RTC;
|
|
||||||
GxEPD2_BW<GxEPD2_154_D67, GxEPD2_154_D67::HEIGHT> Watchy::display(GxEPD2_154_D67(DISPLAY_CS, DISPLAY_DC, DISPLAY_RES, DISPLAY_BUSY));
|
|
||||||
|
|
||||||
RTC_DATA_ATTR int guiState;
|
|
||||||
RTC_DATA_ATTR int menuIndex;
|
|
||||||
RTC_DATA_ATTR BMA423 sensor;
|
|
||||||
RTC_DATA_ATTR bool WIFI_CONFIGURED;
|
|
||||||
RTC_DATA_ATTR bool BLE_CONFIGURED;
|
|
||||||
RTC_DATA_ATTR weatherData currentWeather;
|
|
||||||
RTC_DATA_ATTR int weatherIntervalCounter = -1;
|
|
||||||
RTC_DATA_ATTR bool displayFullInit = true;
|
|
||||||
|
|
||||||
void Watchy::init(String datetime){
|
|
||||||
esp_sleep_wakeup_cause_t wakeup_reason;
|
|
||||||
wakeup_reason = esp_sleep_get_wakeup_cause(); //get wake up reason
|
|
||||||
Wire.begin(SDA, SCL); //init i2c
|
|
||||||
RTC.init();
|
|
||||||
|
|
||||||
// Init the display here for all cases, if unused, it will do nothing
|
|
||||||
display.init(0, displayFullInit, 10, true); // 10ms by spec, and fast pulldown reset
|
|
||||||
display.epd2.setBusyCallback(displayBusyCallback);
|
|
||||||
|
|
||||||
switch (wakeup_reason)
|
|
||||||
{
|
|
||||||
case ESP_SLEEP_WAKEUP_EXT0: //RTC Alarm
|
|
||||||
if(guiState == WATCHFACE_STATE){
|
|
||||||
RTC.read(currentTime);
|
|
||||||
showWatchFace(true); //partial updates on tick
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ESP_SLEEP_WAKEUP_EXT1: //button Press
|
|
||||||
handleButtonPress();
|
|
||||||
break;
|
|
||||||
default: //reset
|
|
||||||
RTC.config(datetime);
|
|
||||||
_bmaConfig();
|
|
||||||
RTC.read(currentTime);
|
|
||||||
showWatchFace(false); //full update on reset
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
deepSleep();
|
|
||||||
}
|
|
||||||
|
|
||||||
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::deepSleep(){
|
|
||||||
display.hibernate();
|
|
||||||
displayFullInit = false; // Notify not to init it again
|
|
||||||
RTC.clearAlarm(); //resets the alarm flag in the RTC
|
|
||||||
// Set pins 0-39 to input to avoid power leaking out
|
|
||||||
for(int i=0; i<40; i++) {
|
|
||||||
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::handleButtonPress(){
|
|
||||||
uint64_t wakeupBit = esp_sleep_get_ext1_wakeup_status();
|
|
||||||
//Menu Button
|
|
||||||
if (wakeupBit & MENU_BTN_MASK){
|
|
||||||
if(guiState == WATCHFACE_STATE){//enter menu state if coming from watch face
|
|
||||||
showMenu(menuIndex, false);
|
|
||||||
}else if(guiState == MAIN_MENU_STATE){//if already in menu, then select menu item
|
|
||||||
switch(menuIndex)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
showAbout();
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
showBuzz();
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
showAccelerometer();
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
setTime();
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
setupWifi();
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
showUpdateFW();
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
showSyncNTP();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}else if(guiState == FW_UPDATE_STATE){
|
|
||||||
updateFWBegin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Back Button
|
|
||||||
else if (wakeupBit & BACK_BTN_MASK){
|
|
||||||
if(guiState == MAIN_MENU_STATE){//exit to watch face if already in menu
|
|
||||||
RTC.read(currentTime);
|
|
||||||
showWatchFace(false);
|
|
||||||
}else if(guiState == APP_STATE){
|
|
||||||
showMenu(menuIndex, false);//exit to menu if already in app
|
|
||||||
}else if(guiState == FW_UPDATE_STATE){
|
|
||||||
showMenu(menuIndex, false);//exit to menu if already in app
|
|
||||||
}else if(guiState == WATCHFACE_STATE){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Up Button
|
|
||||||
else if (wakeupBit & UP_BTN_MASK){
|
|
||||||
if(guiState == MAIN_MENU_STATE){//increment menu index
|
|
||||||
menuIndex--;
|
|
||||||
if(menuIndex < 0){
|
|
||||||
menuIndex = MENU_LENGTH - 1;
|
|
||||||
}
|
|
||||||
showMenu(menuIndex, true);
|
|
||||||
}else if(guiState == WATCHFACE_STATE){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Down Button
|
|
||||||
else if (wakeupBit & DOWN_BTN_MASK){
|
|
||||||
if(guiState == MAIN_MENU_STATE){//decrement menu index
|
|
||||||
menuIndex++;
|
|
||||||
if(menuIndex > MENU_LENGTH - 1){
|
|
||||||
menuIndex = 0;
|
|
||||||
}
|
|
||||||
showMenu(menuIndex, true);
|
|
||||||
}else if(guiState == WATCHFACE_STATE){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/***************** fast menu *****************/
|
|
||||||
bool timeout = false;
|
|
||||||
long lastTimeout = millis();
|
|
||||||
pinMode(MENU_BTN_PIN, INPUT);
|
|
||||||
pinMode(BACK_BTN_PIN, INPUT);
|
|
||||||
pinMode(UP_BTN_PIN, INPUT);
|
|
||||||
pinMode(DOWN_BTN_PIN, INPUT);
|
|
||||||
while(!timeout){
|
|
||||||
if(millis() - lastTimeout > 5000){
|
|
||||||
timeout = true;
|
|
||||||
}else{
|
|
||||||
if(digitalRead(MENU_BTN_PIN) == 1){
|
|
||||||
lastTimeout = millis();
|
|
||||||
if(guiState == MAIN_MENU_STATE){//if already in menu, then select menu item
|
|
||||||
switch(menuIndex)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
showAbout();
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
showBuzz();
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
showAccelerometer();
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
setTime();
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
setupWifi();
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
showUpdateFW();
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
showSyncNTP();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}else if(guiState == FW_UPDATE_STATE){
|
|
||||||
updateFWBegin();
|
|
||||||
}
|
|
||||||
}else if(digitalRead(BACK_BTN_PIN) == 1){
|
|
||||||
lastTimeout = millis();
|
|
||||||
if(guiState == MAIN_MENU_STATE){//exit to watch face if already in menu
|
|
||||||
RTC.read(currentTime);
|
|
||||||
showWatchFace(false);
|
|
||||||
break; //leave loop
|
|
||||||
}else if(guiState == APP_STATE){
|
|
||||||
showMenu(menuIndex, false);//exit to menu if already in app
|
|
||||||
}else if(guiState == FW_UPDATE_STATE){
|
|
||||||
showMenu(menuIndex, false);//exit to menu if already in app
|
|
||||||
}
|
|
||||||
}else if(digitalRead(UP_BTN_PIN) == 1){
|
|
||||||
lastTimeout = millis();
|
|
||||||
if(guiState == MAIN_MENU_STATE){//increment menu index
|
|
||||||
menuIndex--;
|
|
||||||
if(menuIndex < 0){
|
|
||||||
menuIndex = MENU_LENGTH - 1;
|
|
||||||
}
|
|
||||||
showFastMenu(menuIndex);
|
|
||||||
}
|
|
||||||
}else if(digitalRead(DOWN_BTN_PIN) == 1){
|
|
||||||
lastTimeout = millis();
|
|
||||||
if(guiState == MAIN_MENU_STATE){//decrement menu index
|
|
||||||
menuIndex++;
|
|
||||||
if(menuIndex > MENU_LENGTH - 1){
|
|
||||||
menuIndex = 0;
|
|
||||||
}
|
|
||||||
showFastMenu(menuIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::showMenu(byte menuIndex, bool partialRefresh){
|
|
||||||
display.setFullWindow();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
|
|
||||||
int16_t x1, y1;
|
|
||||||
uint16_t w, h;
|
|
||||||
int16_t yPos;
|
|
||||||
|
|
||||||
const char *menuItems[] = {"About Watchy", "Vibrate Motor", "Show Accelerometer", "Set Time", "Setup WiFi", "Update Firmware", "Sync NTP"};
|
|
||||||
for(int i=0; i<MENU_LENGTH; i++){
|
|
||||||
yPos = MENU_HEIGHT+(MENU_HEIGHT*i);
|
|
||||||
display.setCursor(0, yPos);
|
|
||||||
if(i == menuIndex){
|
|
||||||
display.getTextBounds(menuItems[i], 0, yPos, &x1, &y1, &w, &h);
|
|
||||||
display.fillRect(x1-1, y1-10, 200, h+15, GxEPD_WHITE);
|
|
||||||
display.setTextColor(GxEPD_BLACK);
|
|
||||||
display.println(menuItems[i]);
|
|
||||||
}else{
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.println(menuItems[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
display.display(partialRefresh);
|
|
||||||
|
|
||||||
guiState = MAIN_MENU_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::showFastMenu(byte menuIndex){
|
|
||||||
display.setFullWindow();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
|
|
||||||
int16_t x1, y1;
|
|
||||||
uint16_t w, h;
|
|
||||||
int16_t yPos;
|
|
||||||
|
|
||||||
const char *menuItems[] = {"About Watchy", "Vibrate Motor", "Show Accelerometer", "Set Time", "Setup WiFi", "Update Firmware", "Sync NTP"};
|
|
||||||
for(int i=0; i<MENU_LENGTH; i++){
|
|
||||||
yPos = MENU_HEIGHT+(MENU_HEIGHT*i);
|
|
||||||
display.setCursor(0, yPos);
|
|
||||||
if(i == menuIndex){
|
|
||||||
display.getTextBounds(menuItems[i], 0, yPos, &x1, &y1, &w, &h);
|
|
||||||
display.fillRect(x1-1, y1-10, 200, h+15, GxEPD_WHITE);
|
|
||||||
display.setTextColor(GxEPD_BLACK);
|
|
||||||
display.println(menuItems[i]);
|
|
||||||
}else{
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.println(menuItems[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
display.display(true);
|
|
||||||
|
|
||||||
guiState = MAIN_MENU_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::showAbout(){
|
|
||||||
display.setFullWindow();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.setCursor(0, 20);
|
|
||||||
|
|
||||||
display.print("LibVer: ");
|
|
||||||
display.println(WATCHY_LIB_VER);
|
|
||||||
|
|
||||||
const char *RTC_HW[3] = { "<UNKNOWN>", "DS3231", "PCF8563" };
|
|
||||||
display.print("RTC: ");
|
|
||||||
display.println(RTC_HW[RTC.rtcType]); //0 = UNKNOWN, 1 = DS3231, 2 = PCF8563
|
|
||||||
|
|
||||||
display.print("Batt: ");
|
|
||||||
float voltage = getBatteryVoltage();
|
|
||||||
display.print(voltage);
|
|
||||||
display.println("V");
|
|
||||||
|
|
||||||
display.display(false); //full refresh
|
|
||||||
|
|
||||||
guiState = APP_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::showBuzz(){
|
|
||||||
display.setFullWindow();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.setCursor(70, 80);
|
|
||||||
display.println("Buzz!");
|
|
||||||
display.display(false); //full refresh
|
|
||||||
vibMotor();
|
|
||||||
showMenu(menuIndex, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::vibMotor(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::setTime(){
|
|
||||||
|
|
||||||
guiState = APP_STATE;
|
|
||||||
|
|
||||||
RTC.read(currentTime);
|
|
||||||
|
|
||||||
int8_t minute = currentTime.Minute;
|
|
||||||
int8_t hour = currentTime.Hour;
|
|
||||||
int8_t day = currentTime.Day;
|
|
||||||
int8_t month = currentTime.Month;
|
|
||||||
int8_t year = tmYearToY2k(currentTime.Year);
|
|
||||||
|
|
||||||
int8_t setIndex = SET_HOUR;
|
|
||||||
|
|
||||||
int8_t blink = 0;
|
|
||||||
|
|
||||||
pinMode(DOWN_BTN_PIN, INPUT);
|
|
||||||
pinMode(UP_BTN_PIN, INPUT);
|
|
||||||
pinMode(MENU_BTN_PIN, INPUT);
|
|
||||||
pinMode(BACK_BTN_PIN, INPUT);
|
|
||||||
|
|
||||||
display.setFullWindow();
|
|
||||||
|
|
||||||
while(1){
|
|
||||||
|
|
||||||
if(digitalRead(MENU_BTN_PIN) == 1){
|
|
||||||
setIndex++;
|
|
||||||
if(setIndex > SET_DAY){
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(digitalRead(BACK_BTN_PIN) == 1){
|
|
||||||
if(setIndex != SET_HOUR){
|
|
||||||
setIndex--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
blink = 1 - blink;
|
|
||||||
|
|
||||||
if(digitalRead(DOWN_BTN_PIN) == 1){
|
|
||||||
blink = 1;
|
|
||||||
switch(setIndex){
|
|
||||||
case SET_HOUR:
|
|
||||||
hour == 23 ? (hour = 0) : hour++;
|
|
||||||
break;
|
|
||||||
case SET_MINUTE:
|
|
||||||
minute == 59 ? (minute = 0) : minute++;
|
|
||||||
break;
|
|
||||||
case SET_YEAR:
|
|
||||||
year == 99 ? (year = 0) : year++;
|
|
||||||
break;
|
|
||||||
case SET_MONTH:
|
|
||||||
month == 12 ? (month = 1) : month++;
|
|
||||||
break;
|
|
||||||
case SET_DAY:
|
|
||||||
day == 31 ? (day = 1) : day++;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(digitalRead(UP_BTN_PIN) == 1){
|
|
||||||
blink = 1;
|
|
||||||
switch(setIndex){
|
|
||||||
case SET_HOUR:
|
|
||||||
hour == 0 ? (hour = 23) : hour--;
|
|
||||||
break;
|
|
||||||
case SET_MINUTE:
|
|
||||||
minute == 0 ? (minute = 59) : minute--;
|
|
||||||
break;
|
|
||||||
case SET_YEAR:
|
|
||||||
year == 0 ? (year = 99) : year--;
|
|
||||||
break;
|
|
||||||
case SET_MONTH:
|
|
||||||
month == 1 ? (month = 12) : month--;
|
|
||||||
break;
|
|
||||||
case SET_DAY:
|
|
||||||
day == 1 ? (day = 31) : day--;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.setFont(&DSEG7_Classic_Bold_53);
|
|
||||||
|
|
||||||
display.setCursor(5, 80);
|
|
||||||
if(setIndex == SET_HOUR){//blink hour digits
|
|
||||||
display.setTextColor(blink ? GxEPD_WHITE : GxEPD_BLACK);
|
|
||||||
}
|
|
||||||
if(hour < 10){
|
|
||||||
display.print("0");
|
|
||||||
}
|
|
||||||
display.print(hour);
|
|
||||||
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.print(":");
|
|
||||||
|
|
||||||
display.setCursor(108, 80);
|
|
||||||
if(setIndex == SET_MINUTE){//blink minute digits
|
|
||||||
display.setTextColor(blink ? GxEPD_WHITE : GxEPD_BLACK);
|
|
||||||
}
|
|
||||||
if(minute < 10){
|
|
||||||
display.print("0");
|
|
||||||
}
|
|
||||||
display.print(minute);
|
|
||||||
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
display.setCursor(45, 150);
|
|
||||||
if(setIndex == SET_YEAR){//blink minute digits
|
|
||||||
display.setTextColor(blink ? GxEPD_WHITE : GxEPD_BLACK);
|
|
||||||
}
|
|
||||||
display.print(2000+year);
|
|
||||||
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.print("/");
|
|
||||||
|
|
||||||
if(setIndex == SET_MONTH){//blink minute digits
|
|
||||||
display.setTextColor(blink ? GxEPD_WHITE : GxEPD_BLACK);
|
|
||||||
}
|
|
||||||
if(month < 10){
|
|
||||||
display.print("0");
|
|
||||||
}
|
|
||||||
display.print(month);
|
|
||||||
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.print("/");
|
|
||||||
|
|
||||||
if(setIndex == SET_DAY){//blink minute digits
|
|
||||||
display.setTextColor(blink ? GxEPD_WHITE : GxEPD_BLACK);
|
|
||||||
}
|
|
||||||
if(day < 10){
|
|
||||||
display.print("0");
|
|
||||||
}
|
|
||||||
display.print(day);
|
|
||||||
display.display(true); //partial refresh
|
|
||||||
}
|
|
||||||
|
|
||||||
tmElements_t tm;
|
|
||||||
tm.Month = month;
|
|
||||||
tm.Day = day;
|
|
||||||
tm.Year = y2kYearToTm(year);
|
|
||||||
tm.Hour = hour;
|
|
||||||
tm.Minute = minute;
|
|
||||||
tm.Second = 0;
|
|
||||||
|
|
||||||
RTC.set(tm);
|
|
||||||
|
|
||||||
showMenu(menuIndex, false);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::showAccelerometer(){
|
|
||||||
display.setFullWindow();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
|
|
||||||
Accel acc;
|
|
||||||
|
|
||||||
long previousMillis = 0;
|
|
||||||
long interval = 200;
|
|
||||||
|
|
||||||
guiState = APP_STATE;
|
|
||||||
|
|
||||||
pinMode(BACK_BTN_PIN, INPUT);
|
|
||||||
|
|
||||||
while(1){
|
|
||||||
|
|
||||||
unsigned long currentMillis = millis();
|
|
||||||
|
|
||||||
if(digitalRead(BACK_BTN_PIN) == 1){
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(currentMillis - previousMillis > interval){
|
|
||||||
previousMillis = currentMillis;
|
|
||||||
// Get acceleration data
|
|
||||||
bool res = sensor.getAccel(acc);
|
|
||||||
uint8_t direction = sensor.getDirection();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setCursor(0, 30);
|
|
||||||
if(res == false) {
|
|
||||||
display.println("getAccel FAIL");
|
|
||||||
}else{
|
|
||||||
display.print(" X:"); display.println(acc.x);
|
|
||||||
display.print(" Y:"); display.println(acc.y);
|
|
||||||
display.print(" Z:"); display.println(acc.z);
|
|
||||||
|
|
||||||
display.setCursor(30, 130);
|
|
||||||
switch(direction){
|
|
||||||
case DIRECTION_DISP_DOWN:
|
|
||||||
display.println("FACE DOWN");
|
|
||||||
break;
|
|
||||||
case DIRECTION_DISP_UP:
|
|
||||||
display.println("FACE UP");
|
|
||||||
break;
|
|
||||||
case DIRECTION_BOTTOM_EDGE:
|
|
||||||
display.println("BOTTOM EDGE");
|
|
||||||
break;
|
|
||||||
case DIRECTION_TOP_EDGE:
|
|
||||||
display.println("TOP EDGE");
|
|
||||||
break;
|
|
||||||
case DIRECTION_RIGHT_EDGE:
|
|
||||||
display.println("RIGHT EDGE");
|
|
||||||
break;
|
|
||||||
case DIRECTION_LEFT_EDGE:
|
|
||||||
display.println("LEFT EDGE");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
display.println("ERROR!!!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
display.display(true); //full refresh
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showMenu(menuIndex, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::showWatchFace(bool partialRefresh){
|
|
||||||
display.setFullWindow();
|
|
||||||
drawWatchFace();
|
|
||||||
display.display(partialRefresh); //partial refresh
|
|
||||||
guiState = WATCHFACE_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::drawWatchFace(){
|
|
||||||
display.setFont(&DSEG7_Classic_Bold_53);
|
|
||||||
display.setCursor(5, 53+60);
|
|
||||||
if(currentTime.Hour < 10){
|
|
||||||
display.print("0");
|
|
||||||
}
|
|
||||||
display.print(currentTime.Hour);
|
|
||||||
display.print(":");
|
|
||||||
if(currentTime.Minute < 10){
|
|
||||||
display.print("0");
|
|
||||||
}
|
|
||||||
display.println(currentTime.Minute);
|
|
||||||
}
|
|
||||||
|
|
||||||
weatherData Watchy::getWeatherData(){
|
|
||||||
return getWeatherData(settings.cityID, settings.weatherUnit, settings.weatherLang, settings.weatherURL, settings.weatherAPIKey, settings.weatherUpdateInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
weatherData Watchy::getWeatherData(String cityID, String units, String lang, String url, String apiKey, uint8_t updateInterval){
|
|
||||||
currentWeather.isMetric = units == String("metric");
|
|
||||||
if(weatherIntervalCounter < 0){ //-1 on first run, set to updateInterval
|
|
||||||
weatherIntervalCounter = updateInterval;
|
|
||||||
}
|
|
||||||
if(weatherIntervalCounter >= updateInterval){ //only update if WEATHER_UPDATE_INTERVAL has elapsed i.e. 30 minutes
|
|
||||||
if(connectWiFi()){
|
|
||||||
HTTPClient http; //Use Weather API for live data if WiFi is connected
|
|
||||||
http.setConnectTimeout(3000);//3 second max timeout
|
|
||||||
String weatherQueryURL = url + cityID + String("&units=") + units + String("&lang=") + lang + String("&appid=") + apiKey;
|
|
||||||
http.begin(weatherQueryURL.c_str());
|
|
||||||
int httpResponseCode = http.GET();
|
|
||||||
if(httpResponseCode == 200) {
|
|
||||||
String payload = http.getString();
|
|
||||||
JSONVar responseObject = JSON.parse(payload);
|
|
||||||
currentWeather.temperature = int(responseObject["main"]["temp"]);
|
|
||||||
currentWeather.weatherConditionCode = int(responseObject["weather"][0]["id"]);
|
|
||||||
currentWeather.weatherDescription = responseObject["weather"][0]["main"];
|
|
||||||
}else{
|
|
||||||
//http error
|
|
||||||
}
|
|
||||||
http.end();
|
|
||||||
//turn off radios
|
|
||||||
WiFi.mode(WIFI_OFF);
|
|
||||||
btStop();
|
|
||||||
}else{//No WiFi, use internal temperature sensor
|
|
||||||
uint8_t temperature = sensor.readTemperature(); //celsius
|
|
||||||
if(!currentWeather.isMetric){
|
|
||||||
temperature = temperature * 9. / 5. + 32.; //fahrenheit
|
|
||||||
}
|
|
||||||
currentWeather.temperature = temperature;
|
|
||||||
currentWeather.weatherConditionCode = 800;
|
|
||||||
}
|
|
||||||
weatherIntervalCounter = 0;
|
|
||||||
}else{
|
|
||||||
weatherIntervalCounter++;
|
|
||||||
}
|
|
||||||
return currentWeather;
|
|
||||||
}
|
|
||||||
|
|
||||||
float Watchy::getBatteryVoltage(){
|
|
||||||
if(RTC.rtcType == DS3231){
|
|
||||||
return analogReadMilliVolts(BATT_ADC_PIN) / 1000.0f * 2.0f; // Battery voltage goes through a 1/2 divider.
|
|
||||||
}else{
|
|
||||||
return analogReadMilliVolts(BATT_ADC_PIN) / 1000.0f * 2.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t Watchy::_readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len)
|
|
||||||
{
|
|
||||||
Wire.beginTransmission(address);
|
|
||||||
Wire.write(reg);
|
|
||||||
Wire.endTransmission();
|
|
||||||
Wire.requestFrom((uint8_t)address, (uint8_t)len);
|
|
||||||
uint8_t i = 0;
|
|
||||||
while (Wire.available()) {
|
|
||||||
data[i++] = Wire.read();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t Watchy::_writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len)
|
|
||||||
{
|
|
||||||
Wire.beginTransmission(address);
|
|
||||||
Wire.write(reg);
|
|
||||||
Wire.write(data, len);
|
|
||||||
return (0 != Wire.endTransmission());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::_bmaConfig(){
|
|
||||||
|
|
||||||
if (sensor.begin(_readRegister, _writeRegister, delay) == false) {
|
|
||||||
//fail to init BMA
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accel parameter structure
|
|
||||||
Acfg cfg;
|
|
||||||
/*!
|
|
||||||
Output data rate in Hz, Optional parameters:
|
|
||||||
- BMA4_OUTPUT_DATA_RATE_0_78HZ
|
|
||||||
- BMA4_OUTPUT_DATA_RATE_1_56HZ
|
|
||||||
- BMA4_OUTPUT_DATA_RATE_3_12HZ
|
|
||||||
- BMA4_OUTPUT_DATA_RATE_6_25HZ
|
|
||||||
- BMA4_OUTPUT_DATA_RATE_12_5HZ
|
|
||||||
- BMA4_OUTPUT_DATA_RATE_25HZ
|
|
||||||
- BMA4_OUTPUT_DATA_RATE_50HZ
|
|
||||||
- BMA4_OUTPUT_DATA_RATE_100HZ
|
|
||||||
- BMA4_OUTPUT_DATA_RATE_200HZ
|
|
||||||
- BMA4_OUTPUT_DATA_RATE_400HZ
|
|
||||||
- BMA4_OUTPUT_DATA_RATE_800HZ
|
|
||||||
- BMA4_OUTPUT_DATA_RATE_1600HZ
|
|
||||||
*/
|
|
||||||
cfg.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
|
|
||||||
/*!
|
|
||||||
G-range, Optional parameters:
|
|
||||||
- BMA4_ACCEL_RANGE_2G
|
|
||||||
- BMA4_ACCEL_RANGE_4G
|
|
||||||
- BMA4_ACCEL_RANGE_8G
|
|
||||||
- BMA4_ACCEL_RANGE_16G
|
|
||||||
*/
|
|
||||||
cfg.range = BMA4_ACCEL_RANGE_2G;
|
|
||||||
/*!
|
|
||||||
Bandwidth parameter, determines filter configuration, Optional parameters:
|
|
||||||
- BMA4_ACCEL_OSR4_AVG1
|
|
||||||
- BMA4_ACCEL_OSR2_AVG2
|
|
||||||
- BMA4_ACCEL_NORMAL_AVG4
|
|
||||||
- BMA4_ACCEL_CIC_AVG8
|
|
||||||
- BMA4_ACCEL_RES_AVG16
|
|
||||||
- BMA4_ACCEL_RES_AVG32
|
|
||||||
- BMA4_ACCEL_RES_AVG64
|
|
||||||
- BMA4_ACCEL_RES_AVG128
|
|
||||||
*/
|
|
||||||
cfg.bandwidth = BMA4_ACCEL_NORMAL_AVG4;
|
|
||||||
|
|
||||||
/*! Filter performance mode , Optional parameters:
|
|
||||||
- BMA4_CIC_AVG_MODE
|
|
||||||
- BMA4_CONTINUOUS_MODE
|
|
||||||
*/
|
|
||||||
cfg.perf_mode = BMA4_CONTINUOUS_MODE;
|
|
||||||
|
|
||||||
// Configure the BMA423 accelerometer
|
|
||||||
sensor.setAccelConfig(cfg);
|
|
||||||
|
|
||||||
// Enable BMA423 accelerometer
|
|
||||||
// Warning : Need to use feature, you must first enable the accelerometer
|
|
||||||
// Warning : Need to use feature, you must first enable the accelerometer
|
|
||||||
sensor.enableAccel();
|
|
||||||
|
|
||||||
struct bma4_int_pin_config config ;
|
|
||||||
config.edge_ctrl = BMA4_LEVEL_TRIGGER;
|
|
||||||
config.lvl = BMA4_ACTIVE_HIGH;
|
|
||||||
config.od = BMA4_PUSH_PULL;
|
|
||||||
config.output_en = BMA4_OUTPUT_ENABLE;
|
|
||||||
config.input_en = BMA4_INPUT_DISABLE;
|
|
||||||
// The correct trigger interrupt needs to be configured as needed
|
|
||||||
sensor.setINTPinConfig(config, BMA4_INTR1_MAP);
|
|
||||||
|
|
||||||
struct bma423_axes_remap remap_data;
|
|
||||||
remap_data.x_axis = 1;
|
|
||||||
remap_data.x_axis_sign = 0xFF;
|
|
||||||
remap_data.y_axis = 0;
|
|
||||||
remap_data.y_axis_sign = 0xFF;
|
|
||||||
remap_data.z_axis = 2;
|
|
||||||
remap_data.z_axis_sign = 0xFF;
|
|
||||||
// Need to raise the wrist function, need to set the correct axis
|
|
||||||
sensor.setRemapAxes(&remap_data);
|
|
||||||
|
|
||||||
// Enable BMA423 isStepCounter feature
|
|
||||||
sensor.enableFeature(BMA423_STEP_CNTR, true);
|
|
||||||
// Enable BMA423 isTilt feature
|
|
||||||
sensor.enableFeature(BMA423_TILT, true);
|
|
||||||
// Enable BMA423 isDoubleClick feature
|
|
||||||
sensor.enableFeature(BMA423_WAKEUP, true);
|
|
||||||
|
|
||||||
// Reset steps
|
|
||||||
sensor.resetStepCounter();
|
|
||||||
|
|
||||||
// Turn on feature interrupt
|
|
||||||
sensor.enableStepCountInterrupt();
|
|
||||||
sensor.enableTiltInterrupt();
|
|
||||||
// It corresponds to isDoubleClick interrupt
|
|
||||||
sensor.enableWakeupInterrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::setupWifi(){
|
|
||||||
display.epd2.setBusyCallback(0); //temporarily disable lightsleep on busy
|
|
||||||
WiFiManager wifiManager;
|
|
||||||
wifiManager.resetSettings();
|
|
||||||
wifiManager.setTimeout(WIFI_AP_TIMEOUT);
|
|
||||||
wifiManager.setAPCallback(_configModeCallback);
|
|
||||||
display.setFullWindow();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
if(!wifiManager.autoConnect(WIFI_AP_SSID)) {//WiFi setup failed
|
|
||||||
display.println("Setup failed &");
|
|
||||||
display.println("timed out!");
|
|
||||||
}else{
|
|
||||||
display.println("Connected to");
|
|
||||||
display.println(WiFi.SSID());
|
|
||||||
}
|
|
||||||
display.display(false); //full refresh
|
|
||||||
//turn off radios
|
|
||||||
WiFi.mode(WIFI_OFF);
|
|
||||||
btStop();
|
|
||||||
display.epd2.setBusyCallback(displayBusyCallback); //enable lightsleep on busy
|
|
||||||
guiState = APP_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::_configModeCallback (WiFiManager *myWiFiManager) {
|
|
||||||
display.setFullWindow();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.setCursor(0, 30);
|
|
||||||
display.println("Connect to");
|
|
||||||
display.print("SSID: ");
|
|
||||||
display.println(WIFI_AP_SSID);
|
|
||||||
display.print("IP: ");
|
|
||||||
display.println(WiFi.softAPIP());
|
|
||||||
display.display(false); //full refresh
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Watchy::connectWiFi(){
|
|
||||||
if(WL_CONNECT_FAILED == WiFi.begin()){//WiFi not setup, you can also use hard coded credentials with WiFi.begin(SSID,PASS);
|
|
||||||
WIFI_CONFIGURED = false;
|
|
||||||
}else{
|
|
||||||
if(WL_CONNECTED == WiFi.waitForConnectResult()){//attempt to connect for 10s
|
|
||||||
WIFI_CONFIGURED = true;
|
|
||||||
}else{//connection failed, time out
|
|
||||||
WIFI_CONFIGURED = false;
|
|
||||||
//turn off radios
|
|
||||||
WiFi.mode(WIFI_OFF);
|
|
||||||
btStop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return WIFI_CONFIGURED;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::showUpdateFW(){
|
|
||||||
display.setFullWindow();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.setCursor(0, 30);
|
|
||||||
display.println("Please visit");
|
|
||||||
display.println("watchy.sqfmi.com");
|
|
||||||
display.println("with a Bluetooth");
|
|
||||||
display.println("enabled device");
|
|
||||||
display.println(" ");
|
|
||||||
display.println("Press menu button");
|
|
||||||
display.println("again when ready");
|
|
||||||
display.println(" ");
|
|
||||||
display.println("Keep USB powered");
|
|
||||||
display.display(false); //full refresh
|
|
||||||
|
|
||||||
guiState = FW_UPDATE_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::updateFWBegin(){
|
|
||||||
display.setFullWindow();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.setCursor(0, 30);
|
|
||||||
display.println("Bluetooth Started");
|
|
||||||
display.println(" ");
|
|
||||||
display.println("Watchy BLE OTA");
|
|
||||||
display.println(" ");
|
|
||||||
display.println("Waiting for");
|
|
||||||
display.println("connection...");
|
|
||||||
display.display(false); //full refresh
|
|
||||||
|
|
||||||
BLE BT;
|
|
||||||
BT.begin("Watchy BLE OTA");
|
|
||||||
int prevStatus = -1;
|
|
||||||
int currentStatus;
|
|
||||||
|
|
||||||
while(1){
|
|
||||||
currentStatus = BT.updateStatus();
|
|
||||||
if(prevStatus != currentStatus || prevStatus == 1){
|
|
||||||
if(currentStatus == 0){
|
|
||||||
display.setFullWindow();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.setCursor(0, 30);
|
|
||||||
display.println("BLE Connected!");
|
|
||||||
display.println(" ");
|
|
||||||
display.println("Waiting for");
|
|
||||||
display.println("upload...");
|
|
||||||
display.display(false); //full refresh
|
|
||||||
}
|
|
||||||
if(currentStatus == 1){
|
|
||||||
display.setFullWindow();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.setCursor(0, 30);
|
|
||||||
display.println("Downloading");
|
|
||||||
display.println("firmware:");
|
|
||||||
display.println(" ");
|
|
||||||
display.print(BT.howManyBytes());
|
|
||||||
display.println(" bytes");
|
|
||||||
display.display(true); //partial refresh
|
|
||||||
}
|
|
||||||
if(currentStatus == 2){
|
|
||||||
display.setFullWindow();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.setCursor(0, 30);
|
|
||||||
display.println("Download");
|
|
||||||
display.println("completed!");
|
|
||||||
display.println(" ");
|
|
||||||
display.println("Rebooting...");
|
|
||||||
display.display(false); //full refresh
|
|
||||||
|
|
||||||
delay(2000);
|
|
||||||
esp_restart();
|
|
||||||
}
|
|
||||||
if(currentStatus == 4){
|
|
||||||
display.setFullWindow();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.setCursor(0, 30);
|
|
||||||
display.println("BLE Disconnected!");
|
|
||||||
display.println(" ");
|
|
||||||
display.println("exiting...");
|
|
||||||
display.display(false); //full refresh
|
|
||||||
delay(1000);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
prevStatus = currentStatus;
|
|
||||||
}
|
|
||||||
delay(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
//turn off radios
|
|
||||||
WiFi.mode(WIFI_OFF);
|
|
||||||
btStop();
|
|
||||||
showMenu(menuIndex, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Watchy::showSyncNTP(){
|
|
||||||
display.setFullWindow();
|
|
||||||
display.fillScreen(GxEPD_BLACK);
|
|
||||||
display.setFont(&FreeMonoBold9pt7b);
|
|
||||||
display.setTextColor(GxEPD_WHITE);
|
|
||||||
display.setCursor(0, 30);
|
|
||||||
display.println("Syncing NTP... ");
|
|
||||||
display.display(false); //full refresh
|
|
||||||
if(connectWiFi()){
|
|
||||||
if(syncNTP()){
|
|
||||||
display.println("NTP Sync Success\n");
|
|
||||||
display.println("Current Time Is:");
|
|
||||||
|
|
||||||
RTC.read(currentTime);
|
|
||||||
|
|
||||||
display.print(tmYearToCalendar(currentTime.Year));
|
|
||||||
display.print("/");
|
|
||||||
display.print(currentTime.Month);
|
|
||||||
display.print("/");
|
|
||||||
display.print(currentTime.Day);
|
|
||||||
display.print(" - ");
|
|
||||||
|
|
||||||
if(currentTime.Hour < 10){
|
|
||||||
display.print("0");
|
|
||||||
}
|
|
||||||
display.print(currentTime.Hour);
|
|
||||||
display.print(":");
|
|
||||||
if(currentTime.Minute < 10){
|
|
||||||
display.print("0");
|
|
||||||
}
|
|
||||||
display.println(currentTime.Minute);
|
|
||||||
}else{
|
|
||||||
display.println("NTP Sync Failed");
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
display.println("WiFi Not Configured");
|
|
||||||
}
|
|
||||||
display.display(true); //full refresh
|
|
||||||
delay(3000);
|
|
||||||
showMenu(menuIndex, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Watchy::syncNTP(){ //NTP sync - call after connecting to WiFi and remember to turn it back off
|
|
||||||
return syncNTP(settings.gmtOffset, settings.dstOffset, settings.ntpServer.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Watchy::syncNTP(long gmt, int dst, String ntpServer){ //NTP sync - call after connecting to WiFi and remember to turn it back off
|
|
||||||
WiFiUDP ntpUDP;
|
|
||||||
NTPClient timeClient(ntpUDP, ntpServer.c_str(), gmt);
|
|
||||||
timeClient.begin();
|
|
||||||
if(!timeClient.forceUpdate()){
|
|
||||||
return false; //NTP sync failed
|
|
||||||
}
|
|
||||||
tmElements_t tm;
|
|
||||||
breakTime((time_t)timeClient.getEpochTime(), tm);
|
|
||||||
RTC.set(tm);
|
|
||||||
return true;
|
|
||||||
}
|
|
87
src/Watchy.h
87
src/Watchy.h
|
@ -1,87 +0,0 @@
|
||||||
#ifndef WATCHY_H
|
|
||||||
#define WATCHY_H
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <WiFiManager.h>
|
|
||||||
#include <HTTPClient.h>
|
|
||||||
#include <NTPClient.h>
|
|
||||||
#include <WiFiUdp.h>
|
|
||||||
#include <Arduino_JSON.h>
|
|
||||||
#include <GxEPD2_BW.h>
|
|
||||||
#include <Wire.h>
|
|
||||||
#include <Fonts/FreeMonoBold9pt7b.h>
|
|
||||||
#include "DSEG7_Classic_Bold_53.h"
|
|
||||||
#include "WatchyRTC.h"
|
|
||||||
#include "BLE.h"
|
|
||||||
#include "bma.h"
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
typedef struct weatherData{
|
|
||||||
int8_t temperature;
|
|
||||||
int16_t weatherConditionCode;
|
|
||||||
bool isMetric;
|
|
||||||
String weatherDescription;
|
|
||||||
}weatherData;
|
|
||||||
|
|
||||||
typedef struct watchySettings{
|
|
||||||
//Weather Settings
|
|
||||||
String cityID;
|
|
||||||
String weatherAPIKey;
|
|
||||||
String weatherURL;
|
|
||||||
String weatherUnit;
|
|
||||||
String weatherLang;
|
|
||||||
int8_t weatherUpdateInterval;
|
|
||||||
//NTP Settings
|
|
||||||
String ntpServer;
|
|
||||||
int gmtOffset;
|
|
||||||
int dstOffset;
|
|
||||||
}watchySettings;
|
|
||||||
|
|
||||||
class Watchy {
|
|
||||||
public:
|
|
||||||
static WatchyRTC RTC;
|
|
||||||
static GxEPD2_BW<GxEPD2_154_D67, GxEPD2_154_D67::HEIGHT> display;
|
|
||||||
tmElements_t currentTime;
|
|
||||||
watchySettings settings;
|
|
||||||
public:
|
|
||||||
explicit Watchy(const watchySettings& s) : settings(s){} //constructor
|
|
||||||
void init(String datetime = "");
|
|
||||||
void deepSleep();
|
|
||||||
static void displayBusyCallback(const void*);
|
|
||||||
float getBatteryVoltage();
|
|
||||||
void vibMotor(uint8_t intervalMs = 100, uint8_t length = 20);
|
|
||||||
|
|
||||||
void handleButtonPress();
|
|
||||||
void showMenu(byte menuIndex, bool partialRefresh);
|
|
||||||
void showFastMenu(byte menuIndex);
|
|
||||||
void showAbout();
|
|
||||||
void showBuzz();
|
|
||||||
void showAccelerometer();
|
|
||||||
void showUpdateFW();
|
|
||||||
void showSyncNTP();
|
|
||||||
bool syncNTP();
|
|
||||||
bool syncNTP(long gmt, int dst, String ntpServer);
|
|
||||||
void setTime();
|
|
||||||
void setupWifi();
|
|
||||||
bool connectWiFi();
|
|
||||||
weatherData getWeatherData();
|
|
||||||
weatherData getWeatherData(String cityID, String units, String lang, String url, String apiKey, uint8_t updateInterval);
|
|
||||||
void updateFWBegin();
|
|
||||||
|
|
||||||
void showWatchFace(bool partialRefresh);
|
|
||||||
virtual void drawWatchFace(); //override this method for different watch faces
|
|
||||||
|
|
||||||
private:
|
|
||||||
void _bmaConfig();
|
|
||||||
static void _configModeCallback(WiFiManager *myWiFiManager);
|
|
||||||
static uint16_t _readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len);
|
|
||||||
static uint16_t _writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len);
|
|
||||||
};
|
|
||||||
|
|
||||||
extern RTC_DATA_ATTR int guiState;
|
|
||||||
extern RTC_DATA_ATTR int menuIndex;
|
|
||||||
extern RTC_DATA_ATTR BMA423 sensor;
|
|
||||||
extern RTC_DATA_ATTR bool WIFI_CONFIGURED;
|
|
||||||
extern RTC_DATA_ATTR bool BLE_CONFIGURED;
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -0,0 +1,959 @@
|
||||||
|
#include "WatchyExpanded.h"
|
||||||
|
|
||||||
|
WatchyRTC WatchyExpanded::RTC;
|
||||||
|
GxEPD2_BW<GxEPD2_154_D67, GxEPD2_154_D67::HEIGHT> WatchyExpanded::display(GxEPD2_154_D67(DISPLAY_CS, DISPLAY_DC, DISPLAY_RES, DISPLAY_BUSY));
|
||||||
|
|
||||||
|
RTC_DATA_ATTR int guiState;
|
||||||
|
RTC_DATA_ATTR int menuIndex;
|
||||||
|
RTC_DATA_ATTR BMA423 sensor;
|
||||||
|
RTC_DATA_ATTR bool WIFI_CONFIGURED;
|
||||||
|
RTC_DATA_ATTR bool BLE_CONFIGURED;
|
||||||
|
RTC_DATA_ATTR weatherData currentWeather;
|
||||||
|
RTC_DATA_ATTR int weatherIntervalCounter = -1;
|
||||||
|
RTC_DATA_ATTR bool displayFullInit = true;
|
||||||
|
|
||||||
|
void WatchyExpanded::init(String datetime)
|
||||||
|
{
|
||||||
|
esp_sleep_wakeup_cause_t wakeup_reason;
|
||||||
|
wakeup_reason = esp_sleep_get_wakeup_cause(); //get wake up reason
|
||||||
|
Wire.begin(SDA, SCL); //init i2c
|
||||||
|
RTC.init();
|
||||||
|
|
||||||
|
// Init the display here for all cases, if unused, it will do nothing
|
||||||
|
display.init(0, displayFullInit, 10, true); // 10ms by spec, and fast pulldown reset
|
||||||
|
display.epd2.setBusyCallback(displayBusyCallback);
|
||||||
|
|
||||||
|
switch (wakeup_reason)
|
||||||
|
{
|
||||||
|
case ESP_SLEEP_WAKEUP_EXT0: //RTC Alarm
|
||||||
|
if(guiState == WATCHFACE_STATE){
|
||||||
|
RTC.read(currentTime);
|
||||||
|
showWatchFace(true); //partial updates on tick
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ESP_SLEEP_WAKEUP_EXT1: //button Press
|
||||||
|
handleButtonPress();
|
||||||
|
break;
|
||||||
|
default: //reset
|
||||||
|
RTC.config(datetime);
|
||||||
|
_bmaConfig();
|
||||||
|
RTC.read(currentTime);
|
||||||
|
showWatchFace(false); //full update on reset
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
deepSleep();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::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 WatchyExpanded::deepSleep()
|
||||||
|
{
|
||||||
|
display.hibernate();
|
||||||
|
displayFullInit = false; // Notify not to init it again
|
||||||
|
RTC.clearAlarm(); //resets the alarm flag in the RTC
|
||||||
|
// Set pins 0-39 to input to avoid power leaking out
|
||||||
|
for(int i=0; i<40; i++) {
|
||||||
|
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 WatchyExpanded::handleButtonPress(){
|
||||||
|
uint64_t wakeupBit = esp_sleep_get_ext1_wakeup_status();
|
||||||
|
//Menu Button
|
||||||
|
if (wakeupBit & MENU_BTN_MASK){
|
||||||
|
if(guiState == WATCHFACE_STATE){//enter menu state if coming from watch face
|
||||||
|
showMenu(menuIndex, false);
|
||||||
|
}else if(guiState == MAIN_MENU_STATE){//if already in menu, then select menu item
|
||||||
|
switch(menuIndex)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
showAbout();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
showBuzz();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
showAccelerometer();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
setTime();
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
setupWifi();
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
showUpdateFW();
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
showSyncNTP();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}else if(guiState == FW_UPDATE_STATE){
|
||||||
|
updateFWBegin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Back Button
|
||||||
|
else if (wakeupBit & BACK_BTN_MASK){
|
||||||
|
if(guiState == MAIN_MENU_STATE){//exit to watch face if already in menu
|
||||||
|
RTC.read(currentTime);
|
||||||
|
showWatchFace(false);
|
||||||
|
}else if(guiState == APP_STATE){
|
||||||
|
showMenu(menuIndex, false);//exit to menu if already in app
|
||||||
|
}else if(guiState == FW_UPDATE_STATE){
|
||||||
|
showMenu(menuIndex, false);//exit to menu if already in app
|
||||||
|
}else if(guiState == WATCHFACE_STATE){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Up Button
|
||||||
|
else if (wakeupBit & UP_BTN_MASK){
|
||||||
|
if(guiState == MAIN_MENU_STATE){//increment menu index
|
||||||
|
menuIndex--;
|
||||||
|
if(menuIndex < 0){
|
||||||
|
menuIndex = MENU_LENGTH - 1;
|
||||||
|
}
|
||||||
|
showMenu(menuIndex, true);
|
||||||
|
}else if(guiState == WATCHFACE_STATE){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Down Button
|
||||||
|
else if (wakeupBit & DOWN_BTN_MASK){
|
||||||
|
if(guiState == MAIN_MENU_STATE){//decrement menu index
|
||||||
|
menuIndex++;
|
||||||
|
if(menuIndex > MENU_LENGTH - 1){
|
||||||
|
menuIndex = 0;
|
||||||
|
}
|
||||||
|
showMenu(menuIndex, true);
|
||||||
|
}else if(guiState == WATCHFACE_STATE){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************** fast menu *****************/
|
||||||
|
bool timeout = false;
|
||||||
|
long lastTimeout = millis();
|
||||||
|
pinMode(MENU_BTN_PIN, INPUT);
|
||||||
|
pinMode(BACK_BTN_PIN, INPUT);
|
||||||
|
pinMode(UP_BTN_PIN, INPUT);
|
||||||
|
pinMode(DOWN_BTN_PIN, INPUT);
|
||||||
|
while(!timeout){
|
||||||
|
if(millis() - lastTimeout > 5000){
|
||||||
|
timeout = true;
|
||||||
|
}else{
|
||||||
|
if(digitalRead(MENU_BTN_PIN) == 1){
|
||||||
|
lastTimeout = millis();
|
||||||
|
if(guiState == MAIN_MENU_STATE){//if already in menu, then select menu item
|
||||||
|
switch(menuIndex)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
showAbout();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
showBuzz();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
showAccelerometer();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
setTime();
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
setupWifi();
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
showUpdateFW();
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
showSyncNTP();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}else if(guiState == FW_UPDATE_STATE){
|
||||||
|
updateFWBegin();
|
||||||
|
}
|
||||||
|
}else if(digitalRead(BACK_BTN_PIN) == 1){
|
||||||
|
lastTimeout = millis();
|
||||||
|
if(guiState == MAIN_MENU_STATE){//exit to watch face if already in menu
|
||||||
|
RTC.read(currentTime);
|
||||||
|
showWatchFace(false);
|
||||||
|
break; //leave loop
|
||||||
|
}else if(guiState == APP_STATE){
|
||||||
|
showMenu(menuIndex, false);//exit to menu if already in app
|
||||||
|
}else if(guiState == FW_UPDATE_STATE){
|
||||||
|
showMenu(menuIndex, false);//exit to menu if already in app
|
||||||
|
}
|
||||||
|
}else if(digitalRead(UP_BTN_PIN) == 1){
|
||||||
|
lastTimeout = millis();
|
||||||
|
if(guiState == MAIN_MENU_STATE){//increment menu index
|
||||||
|
menuIndex--;
|
||||||
|
if(menuIndex < 0){
|
||||||
|
menuIndex = MENU_LENGTH - 1;
|
||||||
|
}
|
||||||
|
showFastMenu(menuIndex);
|
||||||
|
}
|
||||||
|
}else if(digitalRead(DOWN_BTN_PIN) == 1){
|
||||||
|
lastTimeout = millis();
|
||||||
|
if(guiState == MAIN_MENU_STATE){//decrement menu index
|
||||||
|
menuIndex++;
|
||||||
|
if(menuIndex > MENU_LENGTH - 1){
|
||||||
|
menuIndex = 0;
|
||||||
|
}
|
||||||
|
showFastMenu(menuIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::showMenu(byte menuIndex, bool partialRefresh){
|
||||||
|
display.setFullWindow();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
|
||||||
|
int16_t x1, y1;
|
||||||
|
uint16_t w, h;
|
||||||
|
int16_t yPos;
|
||||||
|
|
||||||
|
const char *menuItems[] = {"About Watchy", "Vibrate Motor", "Show Accelerometer", "Set Time", "Setup WiFi", "Update Firmware", "Sync NTP"};
|
||||||
|
for(int i=0; i<MENU_LENGTH; i++){
|
||||||
|
yPos = MENU_HEIGHT+(MENU_HEIGHT*i);
|
||||||
|
display.setCursor(0, yPos);
|
||||||
|
if(i == menuIndex){
|
||||||
|
display.getTextBounds(menuItems[i], 0, yPos, &x1, &y1, &w, &h);
|
||||||
|
display.fillRect(x1-1, y1-10, 200, h+15, GxEPD_WHITE);
|
||||||
|
display.setTextColor(GxEPD_BLACK);
|
||||||
|
display.println(menuItems[i]);
|
||||||
|
}else{
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.println(menuItems[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
display.display(partialRefresh);
|
||||||
|
|
||||||
|
guiState = MAIN_MENU_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::showFastMenu(byte menuIndex){
|
||||||
|
display.setFullWindow();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
|
||||||
|
int16_t x1, y1;
|
||||||
|
uint16_t w, h;
|
||||||
|
int16_t yPos;
|
||||||
|
|
||||||
|
const char *menuItems[] = {"About Watchy", "Vibrate Motor", "Show Accelerometer", "Set Time", "Setup WiFi", "Update Firmware", "Sync NTP"};
|
||||||
|
for(int i=0; i<MENU_LENGTH; i++){
|
||||||
|
yPos = MENU_HEIGHT+(MENU_HEIGHT*i);
|
||||||
|
display.setCursor(0, yPos);
|
||||||
|
if(i == menuIndex){
|
||||||
|
display.getTextBounds(menuItems[i], 0, yPos, &x1, &y1, &w, &h);
|
||||||
|
display.fillRect(x1-1, y1-10, 200, h+15, GxEPD_WHITE);
|
||||||
|
display.setTextColor(GxEPD_BLACK);
|
||||||
|
display.println(menuItems[i]);
|
||||||
|
}else{
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.println(menuItems[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
display.display(true);
|
||||||
|
|
||||||
|
guiState = MAIN_MENU_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::showAbout(){
|
||||||
|
display.setFullWindow();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.setCursor(0, 20);
|
||||||
|
|
||||||
|
display.print("LibVer: ");
|
||||||
|
display.println(WATCHY_LIB_VER);
|
||||||
|
|
||||||
|
const char *RTC_HW[3] = { "<UNKNOWN>", "DS3231", "PCF8563" };
|
||||||
|
display.print("RTC: ");
|
||||||
|
display.println(RTC_HW[RTC.rtcType]); //0 = UNKNOWN, 1 = DS3231, 2 = PCF8563
|
||||||
|
|
||||||
|
display.print("Batt: ");
|
||||||
|
float voltage = getBatteryVoltage();
|
||||||
|
display.print(voltage);
|
||||||
|
display.println("V");
|
||||||
|
|
||||||
|
display.display(false); //full refresh
|
||||||
|
|
||||||
|
guiState = APP_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::showBuzz(){
|
||||||
|
display.setFullWindow();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.setCursor(70, 80);
|
||||||
|
display.println("Buzz!");
|
||||||
|
display.display(false); //full refresh
|
||||||
|
vibMotor();
|
||||||
|
showMenu(menuIndex, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::vibMotor(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::setTime(){
|
||||||
|
|
||||||
|
guiState = APP_STATE;
|
||||||
|
|
||||||
|
RTC.read(currentTime);
|
||||||
|
|
||||||
|
int8_t minute = currentTime.Minute;
|
||||||
|
int8_t hour = currentTime.Hour;
|
||||||
|
int8_t day = currentTime.Day;
|
||||||
|
int8_t month = currentTime.Month;
|
||||||
|
int8_t year = tmYearToY2k(currentTime.Year);
|
||||||
|
|
||||||
|
int8_t setIndex = SET_HOUR;
|
||||||
|
|
||||||
|
int8_t blink = 0;
|
||||||
|
|
||||||
|
pinMode(DOWN_BTN_PIN, INPUT);
|
||||||
|
pinMode(UP_BTN_PIN, INPUT);
|
||||||
|
pinMode(MENU_BTN_PIN, INPUT);
|
||||||
|
pinMode(BACK_BTN_PIN, INPUT);
|
||||||
|
|
||||||
|
display.setFullWindow();
|
||||||
|
|
||||||
|
while(1){
|
||||||
|
|
||||||
|
if(digitalRead(MENU_BTN_PIN) == 1){
|
||||||
|
setIndex++;
|
||||||
|
if(setIndex > SET_DAY){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(digitalRead(BACK_BTN_PIN) == 1){
|
||||||
|
if(setIndex != SET_HOUR){
|
||||||
|
setIndex--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blink = 1 - blink;
|
||||||
|
|
||||||
|
if(digitalRead(DOWN_BTN_PIN) == 1){
|
||||||
|
blink = 1;
|
||||||
|
switch(setIndex){
|
||||||
|
case SET_HOUR:
|
||||||
|
hour == 23 ? (hour = 0) : hour++;
|
||||||
|
break;
|
||||||
|
case SET_MINUTE:
|
||||||
|
minute == 59 ? (minute = 0) : minute++;
|
||||||
|
break;
|
||||||
|
case SET_YEAR:
|
||||||
|
year == 99 ? (year = 0) : year++;
|
||||||
|
break;
|
||||||
|
case SET_MONTH:
|
||||||
|
month == 12 ? (month = 1) : month++;
|
||||||
|
break;
|
||||||
|
case SET_DAY:
|
||||||
|
day == 31 ? (day = 1) : day++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(digitalRead(UP_BTN_PIN) == 1){
|
||||||
|
blink = 1;
|
||||||
|
switch(setIndex){
|
||||||
|
case SET_HOUR:
|
||||||
|
hour == 0 ? (hour = 23) : hour--;
|
||||||
|
break;
|
||||||
|
case SET_MINUTE:
|
||||||
|
minute == 0 ? (minute = 59) : minute--;
|
||||||
|
break;
|
||||||
|
case SET_YEAR:
|
||||||
|
year == 0 ? (year = 99) : year--;
|
||||||
|
break;
|
||||||
|
case SET_MONTH:
|
||||||
|
month == 1 ? (month = 12) : month--;
|
||||||
|
break;
|
||||||
|
case SET_DAY:
|
||||||
|
day == 1 ? (day = 31) : day--;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.setFont(&DSEG7_Classic_Bold_53);
|
||||||
|
|
||||||
|
display.setCursor(5, 80);
|
||||||
|
if(setIndex == SET_HOUR){//blink hour digits
|
||||||
|
display.setTextColor(blink ? GxEPD_WHITE : GxEPD_BLACK);
|
||||||
|
}
|
||||||
|
if(hour < 10){
|
||||||
|
display.print("0");
|
||||||
|
}
|
||||||
|
display.print(hour);
|
||||||
|
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.print(":");
|
||||||
|
|
||||||
|
display.setCursor(108, 80);
|
||||||
|
if(setIndex == SET_MINUTE){//blink minute digits
|
||||||
|
display.setTextColor(blink ? GxEPD_WHITE : GxEPD_BLACK);
|
||||||
|
}
|
||||||
|
if(minute < 10){
|
||||||
|
display.print("0");
|
||||||
|
}
|
||||||
|
display.print(minute);
|
||||||
|
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
display.setCursor(45, 150);
|
||||||
|
if(setIndex == SET_YEAR){//blink minute digits
|
||||||
|
display.setTextColor(blink ? GxEPD_WHITE : GxEPD_BLACK);
|
||||||
|
}
|
||||||
|
display.print(2000+year);
|
||||||
|
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.print("/");
|
||||||
|
|
||||||
|
if(setIndex == SET_MONTH){//blink minute digits
|
||||||
|
display.setTextColor(blink ? GxEPD_WHITE : GxEPD_BLACK);
|
||||||
|
}
|
||||||
|
if(month < 10){
|
||||||
|
display.print("0");
|
||||||
|
}
|
||||||
|
display.print(month);
|
||||||
|
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.print("/");
|
||||||
|
|
||||||
|
if(setIndex == SET_DAY){//blink minute digits
|
||||||
|
display.setTextColor(blink ? GxEPD_WHITE : GxEPD_BLACK);
|
||||||
|
}
|
||||||
|
if(day < 10){
|
||||||
|
display.print("0");
|
||||||
|
}
|
||||||
|
display.print(day);
|
||||||
|
display.display(true); //partial refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
tmElements_t tm;
|
||||||
|
tm.Month = month;
|
||||||
|
tm.Day = day;
|
||||||
|
tm.Year = y2kYearToTm(year);
|
||||||
|
tm.Hour = hour;
|
||||||
|
tm.Minute = minute;
|
||||||
|
tm.Second = 0;
|
||||||
|
|
||||||
|
RTC.set(tm);
|
||||||
|
|
||||||
|
showMenu(menuIndex, false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::showAccelerometer(){
|
||||||
|
display.setFullWindow();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
|
||||||
|
Accel acc;
|
||||||
|
|
||||||
|
long previousMillis = 0;
|
||||||
|
long interval = 200;
|
||||||
|
|
||||||
|
guiState = APP_STATE;
|
||||||
|
|
||||||
|
pinMode(BACK_BTN_PIN, INPUT);
|
||||||
|
|
||||||
|
while(1){
|
||||||
|
|
||||||
|
unsigned long currentMillis = millis();
|
||||||
|
|
||||||
|
if(digitalRead(BACK_BTN_PIN) == 1){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(currentMillis - previousMillis > interval){
|
||||||
|
previousMillis = currentMillis;
|
||||||
|
// Get acceleration data
|
||||||
|
bool res = sensor.getAccel(acc);
|
||||||
|
uint8_t direction = sensor.getDirection();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setCursor(0, 30);
|
||||||
|
if(res == false) {
|
||||||
|
display.println("getAccel FAIL");
|
||||||
|
}else{
|
||||||
|
display.print(" X:"); display.println(acc.x);
|
||||||
|
display.print(" Y:"); display.println(acc.y);
|
||||||
|
display.print(" Z:"); display.println(acc.z);
|
||||||
|
|
||||||
|
display.setCursor(30, 130);
|
||||||
|
switch(direction){
|
||||||
|
case DIRECTION_DISP_DOWN:
|
||||||
|
display.println("FACE DOWN");
|
||||||
|
break;
|
||||||
|
case DIRECTION_DISP_UP:
|
||||||
|
display.println("FACE UP");
|
||||||
|
break;
|
||||||
|
case DIRECTION_BOTTOM_EDGE:
|
||||||
|
display.println("BOTTOM EDGE");
|
||||||
|
break;
|
||||||
|
case DIRECTION_TOP_EDGE:
|
||||||
|
display.println("TOP EDGE");
|
||||||
|
break;
|
||||||
|
case DIRECTION_RIGHT_EDGE:
|
||||||
|
display.println("RIGHT EDGE");
|
||||||
|
break;
|
||||||
|
case DIRECTION_LEFT_EDGE:
|
||||||
|
display.println("LEFT EDGE");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
display.println("ERROR!!!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
display.display(true); //full refresh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showMenu(menuIndex, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::showWatchFace(bool partialRefresh){
|
||||||
|
display.setFullWindow();
|
||||||
|
drawWatchFace();
|
||||||
|
display.display(partialRefresh); //partial refresh
|
||||||
|
guiState = WATCHFACE_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::drawWatchFace(){
|
||||||
|
display.setFont(&DSEG7_Classic_Bold_53);
|
||||||
|
display.setCursor(5, 53+60);
|
||||||
|
if(currentTime.Hour < 10){
|
||||||
|
display.print("0");
|
||||||
|
}
|
||||||
|
display.print(currentTime.Hour);
|
||||||
|
display.print(":");
|
||||||
|
if(currentTime.Minute < 10){
|
||||||
|
display.print("0");
|
||||||
|
}
|
||||||
|
display.println(currentTime.Minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
weatherData WatchyExpanded::getWeatherData(){
|
||||||
|
return getWeatherData(settings.cityID, settings.weatherUnit, settings.weatherLang, settings.weatherURL, settings.weatherAPIKey, settings.weatherUpdateInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
weatherData WatchyExpanded::getWeatherData(String cityID, String units, String lang, String url, String apiKey, uint8_t updateInterval){
|
||||||
|
currentWeather.isMetric = units == String("metric");
|
||||||
|
if(weatherIntervalCounter < 0){ //-1 on first run, set to updateInterval
|
||||||
|
weatherIntervalCounter = updateInterval;
|
||||||
|
}
|
||||||
|
if(weatherIntervalCounter >= updateInterval){ //only update if WEATHER_UPDATE_INTERVAL has elapsed i.e. 30 minutes
|
||||||
|
if(connectWiFi()){
|
||||||
|
HTTPClient http; //Use Weather API for live data if WiFi is connected
|
||||||
|
http.setConnectTimeout(3000);//3 second max timeout
|
||||||
|
String weatherQueryURL = url + cityID + String("&units=") + units + String("&lang=") + lang + String("&appid=") + apiKey;
|
||||||
|
http.begin(weatherQueryURL.c_str());
|
||||||
|
int httpResponseCode = http.GET();
|
||||||
|
if(httpResponseCode == 200) {
|
||||||
|
String payload = http.getString();
|
||||||
|
JSONVar responseObject = JSON.parse(payload);
|
||||||
|
currentWeather.temperature = int(responseObject["main"]["temp"]);
|
||||||
|
currentWeather.weatherConditionCode = int(responseObject["weather"][0]["id"]);
|
||||||
|
currentWeather.weatherDescription = responseObject["weather"][0]["main"];
|
||||||
|
}else{
|
||||||
|
//http error
|
||||||
|
}
|
||||||
|
http.end();
|
||||||
|
//turn off radios
|
||||||
|
WiFi.mode(WIFI_OFF);
|
||||||
|
btStop();
|
||||||
|
}else{//No WiFi, use internal temperature sensor
|
||||||
|
uint8_t temperature = sensor.readTemperature(); //celsius
|
||||||
|
if(!currentWeather.isMetric){
|
||||||
|
temperature = temperature * 9. / 5. + 32.; //fahrenheit
|
||||||
|
}
|
||||||
|
currentWeather.temperature = temperature;
|
||||||
|
currentWeather.weatherConditionCode = 800;
|
||||||
|
}
|
||||||
|
weatherIntervalCounter = 0;
|
||||||
|
}else{
|
||||||
|
weatherIntervalCounter++;
|
||||||
|
}
|
||||||
|
return currentWeather;
|
||||||
|
}
|
||||||
|
|
||||||
|
float WatchyExpanded::getBatteryVoltage(){
|
||||||
|
if(RTC.rtcType == DS3231){
|
||||||
|
return analogReadMilliVolts(BATT_ADC_PIN) / 1000.0f * 2.0f; // Battery voltage goes through a 1/2 divider.
|
||||||
|
}else{
|
||||||
|
return analogReadMilliVolts(BATT_ADC_PIN) / 1000.0f * 2.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t WatchyExpanded::_readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len)
|
||||||
|
{
|
||||||
|
Wire.beginTransmission(address);
|
||||||
|
Wire.write(reg);
|
||||||
|
Wire.endTransmission();
|
||||||
|
Wire.requestFrom((uint8_t)address, (uint8_t)len);
|
||||||
|
uint8_t i = 0;
|
||||||
|
while (Wire.available()) {
|
||||||
|
data[i++] = Wire.read();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t WatchyExpanded::_writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len)
|
||||||
|
{
|
||||||
|
Wire.beginTransmission(address);
|
||||||
|
Wire.write(reg);
|
||||||
|
Wire.write(data, len);
|
||||||
|
return (0 != Wire.endTransmission());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::_bmaConfig(){
|
||||||
|
|
||||||
|
if (sensor.begin(_readRegister, _writeRegister, delay) == false) {
|
||||||
|
//fail to init BMA
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accel parameter structure
|
||||||
|
Acfg cfg;
|
||||||
|
/*!
|
||||||
|
Output data rate in Hz, Optional parameters:
|
||||||
|
- BMA4_OUTPUT_DATA_RATE_0_78HZ
|
||||||
|
- BMA4_OUTPUT_DATA_RATE_1_56HZ
|
||||||
|
- BMA4_OUTPUT_DATA_RATE_3_12HZ
|
||||||
|
- BMA4_OUTPUT_DATA_RATE_6_25HZ
|
||||||
|
- BMA4_OUTPUT_DATA_RATE_12_5HZ
|
||||||
|
- BMA4_OUTPUT_DATA_RATE_25HZ
|
||||||
|
- BMA4_OUTPUT_DATA_RATE_50HZ
|
||||||
|
- BMA4_OUTPUT_DATA_RATE_100HZ
|
||||||
|
- BMA4_OUTPUT_DATA_RATE_200HZ
|
||||||
|
- BMA4_OUTPUT_DATA_RATE_400HZ
|
||||||
|
- BMA4_OUTPUT_DATA_RATE_800HZ
|
||||||
|
- BMA4_OUTPUT_DATA_RATE_1600HZ
|
||||||
|
*/
|
||||||
|
cfg.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
|
||||||
|
/*!
|
||||||
|
G-range, Optional parameters:
|
||||||
|
- BMA4_ACCEL_RANGE_2G
|
||||||
|
- BMA4_ACCEL_RANGE_4G
|
||||||
|
- BMA4_ACCEL_RANGE_8G
|
||||||
|
- BMA4_ACCEL_RANGE_16G
|
||||||
|
*/
|
||||||
|
cfg.range = BMA4_ACCEL_RANGE_2G;
|
||||||
|
/*!
|
||||||
|
Bandwidth parameter, determines filter configuration, Optional parameters:
|
||||||
|
- BMA4_ACCEL_OSR4_AVG1
|
||||||
|
- BMA4_ACCEL_OSR2_AVG2
|
||||||
|
- BMA4_ACCEL_NORMAL_AVG4
|
||||||
|
- BMA4_ACCEL_CIC_AVG8
|
||||||
|
- BMA4_ACCEL_RES_AVG16
|
||||||
|
- BMA4_ACCEL_RES_AVG32
|
||||||
|
- BMA4_ACCEL_RES_AVG64
|
||||||
|
- BMA4_ACCEL_RES_AVG128
|
||||||
|
*/
|
||||||
|
cfg.bandwidth = BMA4_ACCEL_NORMAL_AVG4;
|
||||||
|
|
||||||
|
/*! Filter performance mode , Optional parameters:
|
||||||
|
- BMA4_CIC_AVG_MODE
|
||||||
|
- BMA4_CONTINUOUS_MODE
|
||||||
|
*/
|
||||||
|
cfg.perf_mode = BMA4_CONTINUOUS_MODE;
|
||||||
|
|
||||||
|
// Configure the BMA423 accelerometer
|
||||||
|
sensor.setAccelConfig(cfg);
|
||||||
|
|
||||||
|
// Enable BMA423 accelerometer
|
||||||
|
// Warning : Need to use feature, you must first enable the accelerometer
|
||||||
|
// Warning : Need to use feature, you must first enable the accelerometer
|
||||||
|
sensor.enableAccel();
|
||||||
|
|
||||||
|
struct bma4_int_pin_config config ;
|
||||||
|
config.edge_ctrl = BMA4_LEVEL_TRIGGER;
|
||||||
|
config.lvl = BMA4_ACTIVE_HIGH;
|
||||||
|
config.od = BMA4_PUSH_PULL;
|
||||||
|
config.output_en = BMA4_OUTPUT_ENABLE;
|
||||||
|
config.input_en = BMA4_INPUT_DISABLE;
|
||||||
|
// The correct trigger interrupt needs to be configured as needed
|
||||||
|
sensor.setINTPinConfig(config, BMA4_INTR1_MAP);
|
||||||
|
|
||||||
|
struct bma423_axes_remap remap_data;
|
||||||
|
remap_data.x_axis = 1;
|
||||||
|
remap_data.x_axis_sign = 0xFF;
|
||||||
|
remap_data.y_axis = 0;
|
||||||
|
remap_data.y_axis_sign = 0xFF;
|
||||||
|
remap_data.z_axis = 2;
|
||||||
|
remap_data.z_axis_sign = 0xFF;
|
||||||
|
// Need to raise the wrist function, need to set the correct axis
|
||||||
|
sensor.setRemapAxes(&remap_data);
|
||||||
|
|
||||||
|
// Enable BMA423 isStepCounter feature
|
||||||
|
sensor.enableFeature(BMA423_STEP_CNTR, true);
|
||||||
|
// Enable BMA423 isTilt feature
|
||||||
|
sensor.enableFeature(BMA423_TILT, true);
|
||||||
|
// Enable BMA423 isDoubleClick feature
|
||||||
|
sensor.enableFeature(BMA423_WAKEUP, true);
|
||||||
|
|
||||||
|
// Reset steps
|
||||||
|
sensor.resetStepCounter();
|
||||||
|
|
||||||
|
// Turn on feature interrupt
|
||||||
|
sensor.enableStepCountInterrupt();
|
||||||
|
sensor.enableTiltInterrupt();
|
||||||
|
// It corresponds to isDoubleClick interrupt
|
||||||
|
sensor.enableWakeupInterrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::setupWifi(){
|
||||||
|
display.epd2.setBusyCallback(0); //temporarily disable lightsleep on busy
|
||||||
|
WiFiManager wifiManager;
|
||||||
|
wifiManager.resetSettings();
|
||||||
|
wifiManager.setTimeout(WIFI_AP_TIMEOUT);
|
||||||
|
wifiManager.setAPCallback(_configModeCallback);
|
||||||
|
display.setFullWindow();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
if(!wifiManager.autoConnect(WIFI_AP_SSID)) {//WiFi setup failed
|
||||||
|
display.println("Setup failed &");
|
||||||
|
display.println("timed out!");
|
||||||
|
}else{
|
||||||
|
display.println("Connected to");
|
||||||
|
display.println(WiFi.SSID());
|
||||||
|
}
|
||||||
|
display.display(false); //full refresh
|
||||||
|
//turn off radios
|
||||||
|
WiFi.mode(WIFI_OFF);
|
||||||
|
btStop();
|
||||||
|
display.epd2.setBusyCallback(displayBusyCallback); //enable lightsleep on busy
|
||||||
|
guiState = APP_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::_configModeCallback (WiFiManager *myWiFiManager) {
|
||||||
|
display.setFullWindow();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.setCursor(0, 30);
|
||||||
|
display.println("Connect to");
|
||||||
|
display.print("SSID: ");
|
||||||
|
display.println(WIFI_AP_SSID);
|
||||||
|
display.print("IP: ");
|
||||||
|
display.println(WiFi.softAPIP());
|
||||||
|
display.display(false); //full refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WatchyExpanded::connectWiFi(){
|
||||||
|
if(WL_CONNECT_FAILED == WiFi.begin()){//WiFi not setup, you can also use hard coded credentials with WiFi.begin(SSID,PASS);
|
||||||
|
WIFI_CONFIGURED = false;
|
||||||
|
}else{
|
||||||
|
if(WL_CONNECTED == WiFi.waitForConnectResult()){//attempt to connect for 10s
|
||||||
|
WIFI_CONFIGURED = true;
|
||||||
|
}else{//connection failed, time out
|
||||||
|
WIFI_CONFIGURED = false;
|
||||||
|
//turn off radios
|
||||||
|
WiFi.mode(WIFI_OFF);
|
||||||
|
btStop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return WIFI_CONFIGURED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::showUpdateFW(){
|
||||||
|
display.setFullWindow();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.setCursor(0, 30);
|
||||||
|
display.println("Please visit");
|
||||||
|
display.println("watchy.sqfmi.com");
|
||||||
|
display.println("with a Bluetooth");
|
||||||
|
display.println("enabled device");
|
||||||
|
display.println(" ");
|
||||||
|
display.println("Press menu button");
|
||||||
|
display.println("again when ready");
|
||||||
|
display.println(" ");
|
||||||
|
display.println("Keep USB powered");
|
||||||
|
display.display(false); //full refresh
|
||||||
|
|
||||||
|
guiState = FW_UPDATE_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::updateFWBegin(){
|
||||||
|
display.setFullWindow();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.setCursor(0, 30);
|
||||||
|
display.println("Bluetooth Started");
|
||||||
|
display.println(" ");
|
||||||
|
display.println("Watchy BLE OTA");
|
||||||
|
display.println(" ");
|
||||||
|
display.println("Waiting for");
|
||||||
|
display.println("connection...");
|
||||||
|
display.display(false); //full refresh
|
||||||
|
|
||||||
|
BLE BT;
|
||||||
|
BT.begin("Watchy BLE OTA");
|
||||||
|
int prevStatus = -1;
|
||||||
|
int currentStatus;
|
||||||
|
|
||||||
|
while(1){
|
||||||
|
currentStatus = BT.updateStatus();
|
||||||
|
if(prevStatus != currentStatus || prevStatus == 1){
|
||||||
|
if(currentStatus == 0){
|
||||||
|
display.setFullWindow();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.setCursor(0, 30);
|
||||||
|
display.println("BLE Connected!");
|
||||||
|
display.println(" ");
|
||||||
|
display.println("Waiting for");
|
||||||
|
display.println("upload...");
|
||||||
|
display.display(false); //full refresh
|
||||||
|
}
|
||||||
|
if(currentStatus == 1){
|
||||||
|
display.setFullWindow();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.setCursor(0, 30);
|
||||||
|
display.println("Downloading");
|
||||||
|
display.println("firmware:");
|
||||||
|
display.println(" ");
|
||||||
|
display.print(BT.howManyBytes());
|
||||||
|
display.println(" bytes");
|
||||||
|
display.display(true); //partial refresh
|
||||||
|
}
|
||||||
|
if(currentStatus == 2){
|
||||||
|
display.setFullWindow();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.setCursor(0, 30);
|
||||||
|
display.println("Download");
|
||||||
|
display.println("completed!");
|
||||||
|
display.println(" ");
|
||||||
|
display.println("Rebooting...");
|
||||||
|
display.display(false); //full refresh
|
||||||
|
|
||||||
|
delay(2000);
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
|
if(currentStatus == 4){
|
||||||
|
display.setFullWindow();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.setCursor(0, 30);
|
||||||
|
display.println("BLE Disconnected!");
|
||||||
|
display.println(" ");
|
||||||
|
display.println("exiting...");
|
||||||
|
display.display(false); //full refresh
|
||||||
|
delay(1000);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prevStatus = currentStatus;
|
||||||
|
}
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
//turn off radios
|
||||||
|
WiFi.mode(WIFI_OFF);
|
||||||
|
btStop();
|
||||||
|
showMenu(menuIndex, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchyExpanded::showSyncNTP(){
|
||||||
|
display.setFullWindow();
|
||||||
|
display.fillScreen(GxEPD_BLACK);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
display.setTextColor(GxEPD_WHITE);
|
||||||
|
display.setCursor(0, 30);
|
||||||
|
display.println("Syncing NTP... ");
|
||||||
|
display.display(false); //full refresh
|
||||||
|
if(connectWiFi()){
|
||||||
|
if(syncNTP()){
|
||||||
|
display.println("NTP Sync Success\n");
|
||||||
|
display.println("Current Time Is:");
|
||||||
|
|
||||||
|
RTC.read(currentTime);
|
||||||
|
|
||||||
|
display.print(tmYearToCalendar(currentTime.Year));
|
||||||
|
display.print("/");
|
||||||
|
display.print(currentTime.Month);
|
||||||
|
display.print("/");
|
||||||
|
display.print(currentTime.Day);
|
||||||
|
display.print(" - ");
|
||||||
|
|
||||||
|
if(currentTime.Hour < 10){
|
||||||
|
display.print("0");
|
||||||
|
}
|
||||||
|
display.print(currentTime.Hour);
|
||||||
|
display.print(":");
|
||||||
|
if(currentTime.Minute < 10){
|
||||||
|
display.print("0");
|
||||||
|
}
|
||||||
|
display.println(currentTime.Minute);
|
||||||
|
}else{
|
||||||
|
display.println("NTP Sync Failed");
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
display.println("WiFi Not Configured");
|
||||||
|
}
|
||||||
|
display.display(true); //full refresh
|
||||||
|
delay(3000);
|
||||||
|
showMenu(menuIndex, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WatchyExpanded::syncNTP(){ //NTP sync - call after connecting to WiFi and remember to turn it back off
|
||||||
|
return syncNTP(settings.gmtOffset, settings.dstOffset, settings.ntpServer.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WatchyExpanded::syncNTP(long gmt, int dst, String ntpServer){ //NTP sync - call after connecting to WiFi and remember to turn it back off
|
||||||
|
WiFiUDP ntpUDP;
|
||||||
|
NTPClient timeClient(ntpUDP, ntpServer.c_str(), gmt);
|
||||||
|
timeClient.begin();
|
||||||
|
if(!timeClient.forceUpdate()){
|
||||||
|
return false; //NTP sync failed
|
||||||
|
}
|
||||||
|
tmElements_t tm;
|
||||||
|
breakTime((time_t)timeClient.getEpochTime(), tm);
|
||||||
|
RTC.set(tm);
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <WiFiManager.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <NTPClient.h>
|
||||||
|
#include <WiFiUdp.h>
|
||||||
|
#include <Arduino_JSON.h>
|
||||||
|
#include <GxEPD2_BW.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <Fonts/FreeMonoBold9pt7b.h>
|
||||||
|
#include "DSEG7_Classic_Bold_53.h"
|
||||||
|
#include "WatchyRTC.h"
|
||||||
|
#include "BLE.h"
|
||||||
|
#include "bma.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
typedef struct weatherData{
|
||||||
|
int8_t temperature;
|
||||||
|
int16_t weatherConditionCode;
|
||||||
|
bool isMetric;
|
||||||
|
String weatherDescription;
|
||||||
|
}weatherData;
|
||||||
|
|
||||||
|
typedef struct watchySettings{
|
||||||
|
//Weather Settings
|
||||||
|
String cityID;
|
||||||
|
String weatherAPIKey;
|
||||||
|
String weatherURL;
|
||||||
|
String weatherUnit;
|
||||||
|
String weatherLang;
|
||||||
|
int8_t weatherUpdateInterval;
|
||||||
|
//NTP Settings
|
||||||
|
String ntpServer;
|
||||||
|
int gmtOffset;
|
||||||
|
int dstOffset;
|
||||||
|
} watchySettings;
|
||||||
|
|
||||||
|
class WatchyExpanded
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static WatchyRTC RTC;
|
||||||
|
static GxEPD2_BW<GxEPD2_154_D67, GxEPD2_154_D67::HEIGHT> display;
|
||||||
|
tmElements_t currentTime;
|
||||||
|
watchySettings settings;
|
||||||
|
public:
|
||||||
|
explicit WatchyExpanded(const watchySettings& s) : settings(s){} //constructor
|
||||||
|
void init(String datetime = "");
|
||||||
|
void deepSleep();
|
||||||
|
static void displayBusyCallback(const void*);
|
||||||
|
float getBatteryVoltage();
|
||||||
|
void vibMotor(uint8_t intervalMs = 100, uint8_t length = 20);
|
||||||
|
|
||||||
|
void handleButtonPress();
|
||||||
|
void showMenu(byte menuIndex, bool partialRefresh);
|
||||||
|
void showFastMenu(byte menuIndex);
|
||||||
|
void showAbout();
|
||||||
|
void showBuzz();
|
||||||
|
void showAccelerometer();
|
||||||
|
void showUpdateFW();
|
||||||
|
void showSyncNTP();
|
||||||
|
bool syncNTP();
|
||||||
|
bool syncNTP(long gmt, int dst, String ntpServer);
|
||||||
|
void setTime();
|
||||||
|
void setupWifi();
|
||||||
|
bool connectWiFi();
|
||||||
|
weatherData getWeatherData();
|
||||||
|
weatherData getWeatherData(String cityID, String units, String lang, String url, String apiKey, uint8_t updateInterval);
|
||||||
|
void updateFWBegin();
|
||||||
|
|
||||||
|
void showWatchFace(bool partialRefresh);
|
||||||
|
virtual void drawWatchFace(); //override this method for different watch faces
|
||||||
|
|
||||||
|
private:
|
||||||
|
void _bmaConfig();
|
||||||
|
static void _configModeCallback(WiFiManager *myWiFiManager);
|
||||||
|
static uint16_t _readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len);
|
||||||
|
static uint16_t _writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern RTC_DATA_ATTR int guiState;
|
||||||
|
extern RTC_DATA_ATTR int menuIndex;
|
||||||
|
extern RTC_DATA_ATTR BMA423 sensor;
|
||||||
|
extern RTC_DATA_ATTR bool WIFI_CONFIGURED;
|
||||||
|
extern RTC_DATA_ATTR bool BLE_CONFIGURED;
|
Loading…
Reference in New Issue