From 66a4c9b84eb26c75d109319726e157980ba17ece Mon Sep 17 00:00:00 2001 From: Michael-Paul Moore Date: Sun, 10 Apr 2022 09:16:02 -0700 Subject: [PATCH] Trying to fork harder. --- LICENSE | 2 +- examples/WatchFaces/7_SEG/Watchy_7_SEG.h | 6 +- examples/WatchFaces/Basic/Basic.ino | 4 +- examples/WatchFaces/DOS/Watchy_DOS.h | 6 +- .../WatchFaces/MacPaint/Watchy_MacPaint.h | 6 +- examples/WatchFaces/Pokemon/Watchy_Pokemon.h | 6 +- .../StarryHorizon/StarryHorizon.ino | 8 +- examples/WatchFaces/Tetris/Watchy_Tetris.h | 6 +- library.json | 2 +- library.properties | 10 +- src/BLE.cpp | 6 +- src/Watchy.cpp | 956 ----------------- src/Watchy.h | 87 -- src/WatchyExpanded.cpp | 959 ++++++++++++++++++ src/WatchyExpanded.h | 85 ++ 15 files changed, 1075 insertions(+), 1074 deletions(-) delete mode 100644 src/Watchy.cpp delete mode 100644 src/Watchy.h create mode 100644 src/WatchyExpanded.cpp create mode 100644 src/WatchyExpanded.h diff --git a/LICENSE b/LICENSE index 840b6a4..8a2038e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 SQFMI +Copyright (c) 2022 SQFMI Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/examples/WatchFaces/7_SEG/Watchy_7_SEG.h b/examples/WatchFaces/7_SEG/Watchy_7_SEG.h index 0ad48c2..f2275af 100644 --- a/examples/WatchFaces/7_SEG/Watchy_7_SEG.h +++ b/examples/WatchFaces/7_SEG/Watchy_7_SEG.h @@ -8,8 +8,8 @@ #include "DSEG7_Classic_Regular_39.h" #include "icons.h" -class Watchy7SEG : public Watchy{ - using Watchy::Watchy; +class Watchy7SEG : public WatchyExpanded{ + using WatchyExpanded::WatchyExpanded; public: void drawWatchFace(); void drawTime(); @@ -19,4 +19,4 @@ class Watchy7SEG : public Watchy{ void drawBattery(); }; -#endif \ No newline at end of file +#endif diff --git a/examples/WatchFaces/Basic/Basic.ino b/examples/WatchFaces/Basic/Basic.ino index 414a5ce..1ab7ef6 100644 --- a/examples/WatchFaces/Basic/Basic.ino +++ b/examples/WatchFaces/Basic/Basic.ino @@ -1,10 +1,10 @@ #include #include "settings.h" -Watchy watchy(settings); +WatchyExpanded watchy(settings); void setup(){ watchy.init(); } -void loop(){} \ No newline at end of file +void loop(){} diff --git a/examples/WatchFaces/DOS/Watchy_DOS.h b/examples/WatchFaces/DOS/Watchy_DOS.h index 04ce7cc..c0e4e29 100644 --- a/examples/WatchFaces/DOS/Watchy_DOS.h +++ b/examples/WatchFaces/DOS/Watchy_DOS.h @@ -4,10 +4,10 @@ #include #include "Px437_IBM_BIOS5pt7b.h" -class WatchyDOS : public Watchy{ - using Watchy::Watchy; +class WatchyDOS : public WatchyExpanded{ + using WatchyExpanded::WatchyExpanded; public: void drawWatchFace(); }; -#endif \ No newline at end of file +#endif diff --git a/examples/WatchFaces/MacPaint/Watchy_MacPaint.h b/examples/WatchFaces/MacPaint/Watchy_MacPaint.h index a3999ad..076d06d 100644 --- a/examples/WatchFaces/MacPaint/Watchy_MacPaint.h +++ b/examples/WatchFaces/MacPaint/Watchy_MacPaint.h @@ -4,10 +4,10 @@ #include #include "macpaint.h" -class WatchyMacPaint : public Watchy{ - using Watchy::Watchy; +class WatchyMacPaint : public WatchyExpanded{ + using WatchyExpanded::WatchyExpanded; public: void drawWatchFace(); }; -#endif \ No newline at end of file +#endif diff --git a/examples/WatchFaces/Pokemon/Watchy_Pokemon.h b/examples/WatchFaces/Pokemon/Watchy_Pokemon.h index 41eb244..ea642fb 100644 --- a/examples/WatchFaces/Pokemon/Watchy_Pokemon.h +++ b/examples/WatchFaces/Pokemon/Watchy_Pokemon.h @@ -4,10 +4,10 @@ #include #include "pokemon.h" -class WatchyPokemon : public Watchy{ - using Watchy::Watchy; +class WatchyPokemon : public WatchyExpanded{ + using WatchyExpanded::WatchyExpanded; public: void drawWatchFace(); }; -#endif \ No newline at end of file +#endif diff --git a/examples/WatchFaces/StarryHorizon/StarryHorizon.ino b/examples/WatchFaces/StarryHorizon/StarryHorizon.ino index 8ff3562..887b81e 100644 --- a/examples/WatchFaces/StarryHorizon/StarryHorizon.ino +++ b/examples/WatchFaces/StarryHorizon/StarryHorizon.ino @@ -1,4 +1,4 @@ -// STARRY HORIZON for Watchy by SQFMI +// STARRY HORIZON for WatchyExpanded by SQFMI // Copyright 2021 Dan Delany dan.delany@gmail.com // 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; } -class StarryHorizon : public Watchy { +class StarryHorizon : public WatchyExpanded { public: - StarryHorizon(const watchySettings& s) : Watchy(s) { + StarryHorizon(const watchySettings& s) : WatchyExpanded(s) { // uncomment to re-generate stars // initStars(); } @@ -147,5 +147,5 @@ void setup() { } void loop() { - // this should never run, Watchy deep sleeps after init(); + // this should never run, WatchyExpanded deep sleeps after init(); } diff --git a/examples/WatchFaces/Tetris/Watchy_Tetris.h b/examples/WatchFaces/Tetris/Watchy_Tetris.h index ba05f83..0cb433d 100644 --- a/examples/WatchFaces/Tetris/Watchy_Tetris.h +++ b/examples/WatchFaces/Tetris/Watchy_Tetris.h @@ -4,11 +4,11 @@ #include #include "tetris.h" -class WatchyTetris : public Watchy{ - using Watchy::Watchy; +class WatchyTetris : public WatchyExpanded{ + using WatchyExpanded::WatchyExpanded; public: WatchyTetris(); void drawWatchFace(); }; -#endif \ No newline at end of file +#endif diff --git a/library.json b/library.json index be4de92..2874856 100644 --- a/library.json +++ b/library.json @@ -1,5 +1,5 @@ { - "name": "Watchy", + "name": "WatchyExpanded", "version": "1.4.0", "description": "Watchy - An Open Source E-Paper Watch by SQFMI", "authors": [ diff --git a/library.properties b/library.properties index 56da205..4f18529 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ -name=Watchy -version=1.4.0 -author=SQFMI -maintainer=SQFMI +name=WatchyExpanded +version=2022.4.10 +author=Michael-Paul Moore +maintainer=MichaelPaul Moore 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 url=https://watchy.sqfmi.com architectures=esp32 diff --git a/src/BLE.cpp b/src/BLE.cpp index ce6a032..52c5dec 100644 --- a/src/BLE.cpp +++ b/src/BLE.cpp @@ -93,7 +93,7 @@ BLE::~BLE(void) // // begin -bool BLE::begin(const char* localName = "Watchy BLE OTA") { +bool BLE::begin(const char* localName = "WatchyExpanded BLE OTA") { // Create the BLE Device 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}; pVersionCharacteristic->setValue((uint8_t*)hardwareVersion, 5); - pWatchFaceNameCharacteristic->setValue("Watchy 7 Segment"); + pWatchFaceNameCharacteristic->setValue("WatchyExpanded 7 Segment"); return true; } @@ -150,4 +150,4 @@ int BLE::updateStatus(){ int BLE::howManyBytes(){ return bytesReceived; -} \ No newline at end of file +} diff --git a/src/Watchy.cpp b/src/Watchy.cpp deleted file mode 100644 index 1dc0679..0000000 --- a/src/Watchy.cpp +++ /dev/null @@ -1,956 +0,0 @@ -#include "Watchy.h" - -WatchyRTC Watchy::RTC; -GxEPD2_BW 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", "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 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; -} diff --git a/src/Watchy.h b/src/Watchy.h deleted file mode 100644 index aeccc68..0000000 --- a/src/Watchy.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef WATCHY_H -#define WATCHY_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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 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 diff --git a/src/WatchyExpanded.cpp b/src/WatchyExpanded.cpp new file mode 100644 index 0000000..d8deef6 --- /dev/null +++ b/src/WatchyExpanded.cpp @@ -0,0 +1,959 @@ +#include "WatchyExpanded.h" + +WatchyRTC WatchyExpanded::RTC; +GxEPD2_BW 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", "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 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; +} diff --git a/src/WatchyExpanded.h b/src/WatchyExpanded.h new file mode 100644 index 0000000..3183bf3 --- /dev/null +++ b/src/WatchyExpanded.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 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;