Initial commit
This commit is contained in:
		
						commit
						62bcff8485
					
				
					 17 changed files with 1035 additions and 0 deletions
				
			
		
							
								
								
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
.pio
 | 
			
		||||
.vscode/**
 | 
			
		||||
							
								
								
									
										39
									
								
								include/README
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								include/README
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
 | 
			
		||||
This directory is intended for project header files.
 | 
			
		||||
 | 
			
		||||
A header file is a file containing C declarations and macro definitions
 | 
			
		||||
to be shared between several project source files. You request the use of a
 | 
			
		||||
header file in your project source file (C, C++, etc) located in `src` folder
 | 
			
		||||
by including it, with the C preprocessing directive `#include'.
 | 
			
		||||
 | 
			
		||||
```src/main.c
 | 
			
		||||
 | 
			
		||||
#include "header.h"
 | 
			
		||||
 | 
			
		||||
int main (void)
 | 
			
		||||
{
 | 
			
		||||
 ...
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Including a header file produces the same results as copying the header file
 | 
			
		||||
into each source file that needs it. Such copying would be time-consuming
 | 
			
		||||
and error-prone. With a header file, the related declarations appear
 | 
			
		||||
in only one place. If they need to be changed, they can be changed in one
 | 
			
		||||
place, and programs that include the header file will automatically use the
 | 
			
		||||
new version when next recompiled. The header file eliminates the labor of
 | 
			
		||||
finding and changing all the copies as well as the risk that a failure to
 | 
			
		||||
find one copy will result in inconsistencies within a program.
 | 
			
		||||
 | 
			
		||||
In C, the usual convention is to give header files names that end with `.h'.
 | 
			
		||||
It is most portable to use only letters, digits, dashes, and underscores in
 | 
			
		||||
header file names, and at most one dot.
 | 
			
		||||
 | 
			
		||||
Read more about using header files in official GCC documentation:
 | 
			
		||||
 | 
			
		||||
* Include Syntax
 | 
			
		||||
* Include Operation
 | 
			
		||||
* Once-Only Headers
 | 
			
		||||
* Computed Includes
 | 
			
		||||
 | 
			
		||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
 | 
			
		||||
							
								
								
									
										46
									
								
								lib/README
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								lib/README
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
 | 
			
		||||
This directory is intended for project specific (private) libraries.
 | 
			
		||||
PlatformIO will compile them to static libraries and link into executable file.
 | 
			
		||||
 | 
			
		||||
The source code of each library should be placed in a an own separate directory
 | 
			
		||||
("lib/your_library_name/[here are source files]").
 | 
			
		||||
 | 
			
		||||
For example, see a structure of the following two libraries `Foo` and `Bar`:
 | 
			
		||||
 | 
			
		||||
|--lib
 | 
			
		||||
|  |
 | 
			
		||||
|  |--Bar
 | 
			
		||||
|  |  |--docs
 | 
			
		||||
|  |  |--examples
 | 
			
		||||
|  |  |--src
 | 
			
		||||
|  |     |- Bar.c
 | 
			
		||||
|  |     |- Bar.h
 | 
			
		||||
|  |  |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
 | 
			
		||||
|  |
 | 
			
		||||
|  |--Foo
 | 
			
		||||
|  |  |- Foo.c
 | 
			
		||||
|  |  |- Foo.h
 | 
			
		||||
|  |
 | 
			
		||||
|  |- README --> THIS FILE
 | 
			
		||||
|
 | 
			
		||||
|- platformio.ini
 | 
			
		||||
|--src
 | 
			
		||||
   |- main.c
 | 
			
		||||
 | 
			
		||||
and a contents of `src/main.c`:
 | 
			
		||||
```
 | 
			
		||||
#include <Foo.h>
 | 
			
		||||
#include <Bar.h>
 | 
			
		||||
 | 
			
		||||
int main (void)
 | 
			
		||||
{
 | 
			
		||||
  ...
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
PlatformIO Library Dependency Finder will find automatically dependent
 | 
			
		||||
libraries scanning project source files.
 | 
			
		||||
 | 
			
		||||
More information about PlatformIO Library Dependency Finder
 | 
			
		||||
- https://docs.platformio.org/page/librarymanager/ldf.html
 | 
			
		||||
							
								
								
									
										23
									
								
								platformio.ini
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								platformio.ini
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
; PlatformIO Project Configuration File
 | 
			
		||||
;
 | 
			
		||||
;   Build options: build flags, source filter
 | 
			
		||||
;   Upload options: custom upload port, speed and extra flags
 | 
			
		||||
;   Library options: dependencies, extra library storages
 | 
			
		||||
;   Advanced options: extra scripting
 | 
			
		||||
;
 | 
			
		||||
; Please visit documentation for the other options and examples
 | 
			
		||||
; https://docs.platformio.org/page/projectconf.html
 | 
			
		||||
 | 
			
		||||
[env:watchy]
 | 
			
		||||
platform = espressif32
 | 
			
		||||
board = esp32dev
 | 
			
		||||
framework = arduino
 | 
			
		||||
lib_deps = 
 | 
			
		||||
	adafruit/Adafruit GFX Library@^1.11.5
 | 
			
		||||
    GxEPD2
 | 
			
		||||
    Time
 | 
			
		||||
	Rtc_Pcf8563
 | 
			
		||||
    NTPClient
 | 
			
		||||
 | 
			
		||||
lib_ldf_mode = deep+
 | 
			
		||||
board_build.partitions = min_spiffs.csv
 | 
			
		||||
							
								
								
									
										12
									
								
								src/Main.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/Main.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
#include "WatchFace.h"
 | 
			
		||||
 | 
			
		||||
WatchFace watchy;
 | 
			
		||||
 | 
			
		||||
void setup()
 | 
			
		||||
{
 | 
			
		||||
  watchy.Init();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void loop()
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										76
									
								
								src/SevenSegment.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/SevenSegment.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,76 @@
 | 
			
		|||
#include "SevenSegment.h"
 | 
			
		||||
 | 
			
		||||
SevenSegment::SevenSegment(uint16_t digitWidth, uint16_t digitHeight, uint16_t digitGap, uint16_t segmentThickness, uint16_t segmentGap)
 | 
			
		||||
  : m_digitWidth(digitWidth), m_digitHeight(digitHeight), m_digitGap(digitGap), m_segmentThickness(segmentThickness), m_segmentGap(segmentGap)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SevenSegment::DrawDigit(GxEPD2_BW<WatchyDisplayBase, WatchyDisplayBase::HEIGHT> & display, uint8_t digit, uint16_t x, uint16_t y, uint8_t color)
 | 
			
		||||
{
 | 
			
		||||
  uint16_t digitX = x;
 | 
			
		||||
  uint16_t digitY = y;
 | 
			
		||||
 | 
			
		||||
  // top top-right bottom-right bottom bottom-left top-left middle
 | 
			
		||||
 | 
			
		||||
  bool drawSegments0[7] = { true, true, true, true, true, true, false };
 | 
			
		||||
  bool drawSegments1[7] = { false, true, true, false, false, false, false };
 | 
			
		||||
  bool drawSegments2[7] = { true, true, false, true, true, false, true };
 | 
			
		||||
  bool drawSegments3[7] = { true, true, true, true, false, false, true };
 | 
			
		||||
  bool drawSegments4[7] = { false, true, true, false, false, true, true };
 | 
			
		||||
  bool drawSegments5[7] = { true, false, true, true, false, true, true };
 | 
			
		||||
  bool drawSegments6[7] = { false, false, true, true, true, true, true };
 | 
			
		||||
  bool drawSegments7[7] = { true, true, true, false, false, false, false };
 | 
			
		||||
  bool drawSegments8[7] = { true, true, true, true, true, true, true };
 | 
			
		||||
  bool drawSegments9[7] = { true, true, true, true, false, true, true };
 | 
			
		||||
  bool * drawSegments[10] = { drawSegments0, drawSegments1, drawSegments2, drawSegments3, drawSegments4, drawSegments5, drawSegments6, drawSegments7, drawSegments8, drawSegments9 };
 | 
			
		||||
  bool * segments = drawSegments[digit];
 | 
			
		||||
 | 
			
		||||
  // A
 | 
			
		||||
  if (segments[0]) {
 | 
			
		||||
    display.fillRect(digitX + m_segmentThickness, digitY, m_digitWidth - 2 * m_segmentThickness, m_segmentThickness, color);
 | 
			
		||||
  } else {
 | 
			
		||||
    display.fillRect(digitX + m_segmentThickness, digitY, m_digitWidth - 2 * m_segmentThickness, m_segmentThickness, color ^ 0xFF);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // B
 | 
			
		||||
  if (segments[1]) {
 | 
			
		||||
    display.fillRect(digitX + m_digitWidth - m_segmentThickness, digitY + m_segmentThickness, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color);
 | 
			
		||||
  } else {
 | 
			
		||||
    display.fillRect(digitX + m_digitWidth - m_segmentThickness, digitY + m_segmentThickness, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color ^ 0xFF);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // C
 | 
			
		||||
  if (segments[2]) {
 | 
			
		||||
    display.fillRect(digitX + m_digitWidth - m_segmentThickness, digitY + m_digitHeight / 2, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color);
 | 
			
		||||
  } else {
 | 
			
		||||
    display.fillRect(digitX + m_digitWidth - m_segmentThickness, digitY + m_digitHeight / 2, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color ^ 0xFF);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // D
 | 
			
		||||
  if (segments[3]) {
 | 
			
		||||
    display.fillRect(digitX + m_segmentThickness, digitY + m_digitHeight - m_segmentThickness * 2, m_digitWidth - 2 * m_segmentThickness, m_segmentThickness, color);
 | 
			
		||||
  } else {
 | 
			
		||||
    display.fillRect(digitX + m_segmentThickness, digitY + m_digitHeight - m_segmentThickness * 2, m_digitWidth - 2 * m_segmentThickness, m_segmentThickness, color ^ 0xFF);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // E
 | 
			
		||||
  if (segments[4]) {
 | 
			
		||||
    display.fillRect(digitX, digitY + m_digitHeight / 2, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color);
 | 
			
		||||
  } else {
 | 
			
		||||
    display.fillRect(digitX, digitY + m_digitHeight / 2, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color ^ 0xFF);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // F
 | 
			
		||||
  if (segments[5]) {
 | 
			
		||||
    display.fillRect(digitX, digitY + m_segmentThickness, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color);
 | 
			
		||||
  } else {
 | 
			
		||||
    display.fillRect(digitX, digitY + m_segmentThickness, m_segmentThickness, m_digitHeight / 2 - m_segmentThickness * 2, color ^ 0xFF);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // G
 | 
			
		||||
  if (segments[6]) {
 | 
			
		||||
    display.fillRect(digitX + m_segmentThickness, digitY + m_digitHeight / 2 - m_segmentThickness, m_digitWidth - 2 * m_segmentThickness, m_segmentThickness, color);
 | 
			
		||||
  } else {
 | 
			
		||||
    display.fillRect(digitX + m_segmentThickness, digitY + m_digitHeight / 2 - m_segmentThickness, m_digitWidth - 2 * m_segmentThickness, m_segmentThickness, color ^ 0xFF);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								src/SevenSegment.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/SevenSegment.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include "WatchyDisplay.h"
 | 
			
		||||
 | 
			
		||||
class SevenSegment
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
  SevenSegment(uint16_t digitWidth, uint16_t digitHeight, uint16_t digitGap, uint16_t segmentThickness, uint16_t segmentGap);
 | 
			
		||||
  void DrawDigit(GxEPD2_BW<WatchyDisplayBase, WatchyDisplayBase::HEIGHT> & display, uint8_t digit, uint16_t x, uint16_t y, uint8_t color);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  uint16_t m_digitWidth, m_digitHeight, m_digitGap, m_segmentThickness, m_segmentGap;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										50
									
								
								src/WatchFace.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/WatchFace.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,50 @@
 | 
			
		|||
#include "WatchFace.h"
 | 
			
		||||
#include "SevenSegment.h"
 | 
			
		||||
#include <cmath>
 | 
			
		||||
 | 
			
		||||
void WatchFace::DrawWatchFace()
 | 
			
		||||
{
 | 
			
		||||
	m_display.fillScreen(GxEPD_WHITE);
 | 
			
		||||
	DrawBatteryIcon();
 | 
			
		||||
 | 
			
		||||
	tmElements_t currentTime;
 | 
			
		||||
	m_RTC.read(currentTime, TZ_OFFSET);
 | 
			
		||||
	SevenSegment sevenSegment(30, 60, 6, 5, 5);
 | 
			
		||||
 | 
			
		||||
	if (currentTime.Hour < 10) {
 | 
			
		||||
		sevenSegment.DrawDigit(m_display, 0, 10, 75, GxEPD_BLACK);
 | 
			
		||||
	} else {
 | 
			
		||||
		sevenSegment.DrawDigit(m_display, currentTime.Hour / 10, 10, 75, GxEPD_BLACK);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sevenSegment.DrawDigit(m_display, currentTime.Hour % 10, 50, 75, GxEPD_BLACK);
 | 
			
		||||
 | 
			
		||||
	if (currentTime.Minute < 10) {
 | 
			
		||||
		sevenSegment.DrawDigit(m_display, 0, 100, 75, GxEPD_BLACK);
 | 
			
		||||
	} else {
 | 
			
		||||
		sevenSegment.DrawDigit(m_display, currentTime.Minute / 10, 100, 75, GxEPD_BLACK);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sevenSegment.DrawDigit(m_display, currentTime.Minute % 10, 140, 75, GxEPD_BLACK);
 | 
			
		||||
 | 
			
		||||
	m_display.fillRect(87, 90, 5, 5, GxEPD_BLACK);
 | 
			
		||||
	m_display.fillRect(87, 110, 5, 5, GxEPD_BLACK);
 | 
			
		||||
 | 
			
		||||
//	sevenSegment.DrawDigit(m_display, 9, 10, 75, GxEPD_BLACK);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchFace::DrawBatteryIcon()
 | 
			
		||||
{
 | 
			
		||||
	m_display.fillRect(200 - 48, 5, 43, 23, GxEPD_BLACK);
 | 
			
		||||
	m_display.fillRect(200 - 46, 7, 39, 19, GxEPD_WHITE);
 | 
			
		||||
	float VBAT = GetBatteryVoltage();
 | 
			
		||||
	float level = (VBAT - 3.6f) / 0.6f;
 | 
			
		||||
	if (level > 1.0f) {
 | 
			
		||||
		level = 1.0f;
 | 
			
		||||
	} else if (level < 0.0f) {
 | 
			
		||||
		level = 0.0f;
 | 
			
		||||
	}
 | 
			
		||||
	level = 0.5f - sin(asin(1.0f - 2.0f * level) / 3.0f);
 | 
			
		||||
 | 
			
		||||
	m_display.fillRect(200 - 44, 9, (int)std::round(35.0f * level), 15, GxEPD_BLACK);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/WatchFace.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/WatchFace.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "Watchy.h"
 | 
			
		||||
 | 
			
		||||
class WatchFace : public Watchy
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
  void DrawWatchFace();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  void DrawBatteryIcon();
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										143
									
								
								src/Watchy.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/Watchy.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,143 @@
 | 
			
		|||
#include "Watchy.h"
 | 
			
		||||
 | 
			
		||||
WatchyDisplayBase Watchy::m_displayBase;
 | 
			
		||||
GxEPD2_BW<WatchyDisplayBase, WatchyDisplayBase::HEIGHT> Watchy::m_display(Watchy::m_displayBase);
 | 
			
		||||
WatchyRTC Watchy::m_RTC;
 | 
			
		||||
 | 
			
		||||
RTC_DATA_ATTR bool g_displayFullInit = true;
 | 
			
		||||
 | 
			
		||||
Watchy::Watchy()
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Watchy::Init()
 | 
			
		||||
{
 | 
			
		||||
  esp_sleep_wakeup_cause_t wakeup_reason;
 | 
			
		||||
  wakeup_reason = esp_sleep_get_wakeup_cause();
 | 
			
		||||
 | 
			
		||||
  Wire.begin(SDA, SCL);
 | 
			
		||||
  m_RTC.init();
 | 
			
		||||
 | 
			
		||||
  m_display.epd2.selectSPI(SPI, SPISettings(20000000, MSBFIRST, SPI_MODE0));
 | 
			
		||||
  m_display.init(0, g_displayFullInit, 10, true);
 | 
			
		||||
  m_display.epd2.setBusyCallback(DisplayBusyCallback);
 | 
			
		||||
 | 
			
		||||
  switch(wakeup_reason) {
 | 
			
		||||
    case ESP_SLEEP_WAKEUP_EXT0:
 | 
			
		||||
      // RTC interrupt
 | 
			
		||||
      ShowWatchFace(true);
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_SLEEP_WAKEUP_EXT1:
 | 
			
		||||
      // Button press
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      ConnectWiFi();
 | 
			
		||||
      SyncNTPTime();
 | 
			
		||||
      DisconnectWiFi();
 | 
			
		||||
 | 
			
		||||
      m_display.clearScreen(0xFF);
 | 
			
		||||
      ShowWatchFace(false);
 | 
			
		||||
 | 
			
		||||
      // pinMode(VIB_MOTOR_PIN, OUTPUT);
 | 
			
		||||
      // bool motorOn = false;
 | 
			
		||||
      // for (int i = 0; i < 4; i++) {
 | 
			
		||||
      //   motorOn = !motorOn;
 | 
			
		||||
      //   digitalWrite(VIB_MOTOR_PIN, motorOn);
 | 
			
		||||
      //   delay(75);
 | 
			
		||||
      // }
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  DeepSleep();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Watchy::DeepSleep()
 | 
			
		||||
{
 | 
			
		||||
  m_display.hibernate();
 | 
			
		||||
  if (g_displayFullInit) { // For some reason, seems to be enabled on first boot
 | 
			
		||||
    esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  g_displayFullInit = false; // Notify not to init it again
 | 
			
		||||
  m_RTC.clearAlarm();        // resets the alarm flag in the RTC
 | 
			
		||||
 | 
			
		||||
  // Set GPIOs 0-39 to input to avoid power leaking out
 | 
			
		||||
  const uint64_t ignore = 0b11110001000000110000100111000010; // Ignore some GPIOs due to resets
 | 
			
		||||
  for (int i = 0; i < GPIO_NUM_MAX; i++) {
 | 
			
		||||
    if ((ignore >> i) & 0b1)
 | 
			
		||||
      continue;
 | 
			
		||||
    pinMode(i, INPUT);
 | 
			
		||||
  }
 | 
			
		||||
  esp_sleep_enable_ext0_wakeup((gpio_num_t)RTC_INT_PIN,
 | 
			
		||||
                               0); // enable deep sleep wake on RTC interrupt
 | 
			
		||||
  esp_sleep_enable_ext1_wakeup(
 | 
			
		||||
      BTN_PIN_MASK,
 | 
			
		||||
      ESP_EXT1_WAKEUP_ANY_HIGH); // enable deep sleep wake on button press
 | 
			
		||||
  esp_deep_sleep_start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Watchy::DisplayBusyCallback(const void *)
 | 
			
		||||
{
 | 
			
		||||
  gpio_wakeup_enable((gpio_num_t)DISPLAY_BUSY, GPIO_INTR_LOW_LEVEL);
 | 
			
		||||
  esp_sleep_enable_gpio_wakeup();
 | 
			
		||||
  esp_light_sleep_start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Watchy::VibeMotor(uint8_t intervalMs, uint8_t length)
 | 
			
		||||
{
 | 
			
		||||
  pinMode(VIB_MOTOR_PIN, OUTPUT);
 | 
			
		||||
  bool motorOn = false;
 | 
			
		||||
  for (int i = 0; i < length; i++) {
 | 
			
		||||
    motorOn = !motorOn;
 | 
			
		||||
    digitalWrite(VIB_MOTOR_PIN, motorOn);
 | 
			
		||||
    delay(intervalMs);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float Watchy::GetBatteryVoltage()
 | 
			
		||||
{
 | 
			
		||||
  return analogReadMilliVolts(BATT_ADC_PIN) / 1000.0f * 2.0f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Watchy::ConnectWiFi()
 | 
			
		||||
{
 | 
			
		||||
  if(WiFi.begin(WIFI_SSID, WIFI_PASS) == WL_CONNECT_FAILED) {  
 | 
			
		||||
    Serial.begin(9600);
 | 
			
		||||
    Serial.println("Failed to connect to WiFi");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if(WiFi.waitForConnectResult() != WL_CONNECTED) {
 | 
			
		||||
    Serial.begin(9600);
 | 
			
		||||
    Serial.println("Failed to connect to WiFi");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Watchy::SyncNTPTime()
 | 
			
		||||
{
 | 
			
		||||
  WiFiUDP ntpUDP;
 | 
			
		||||
  // GMT offset should be, RTC class will adjust to local time
 | 
			
		||||
  NTPClient timeClient(ntpUDP, "pool.ntp.org", 0);
 | 
			
		||||
  timeClient.begin();
 | 
			
		||||
  bool success = timeClient.forceUpdate();
 | 
			
		||||
  if (!success) {
 | 
			
		||||
    Serial.begin(9600);
 | 
			
		||||
    Serial.println("Failed to get NTP time");
 | 
			
		||||
  }
 | 
			
		||||
  tmElements_t tm;
 | 
			
		||||
  breakTime((time_t)timeClient.getEpochTime(), tm);
 | 
			
		||||
  m_RTC.set(tm);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Watchy::DisconnectWiFi()
 | 
			
		||||
{
 | 
			
		||||
  WiFi.mode(WIFI_OFF);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Watchy::ShowWatchFace(bool partialRefresh)
 | 
			
		||||
{
 | 
			
		||||
  m_display.setFullWindow();
 | 
			
		||||
  DrawWatchFace();
 | 
			
		||||
  m_display.display(partialRefresh);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								src/Watchy.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/Watchy.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,31 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "WatchyDisplay.h"
 | 
			
		||||
#include "WatchyRTC.h"
 | 
			
		||||
#include <WiFi.h>
 | 
			
		||||
#include <WiFiUdp.h>
 | 
			
		||||
#include <NTPClient.h>
 | 
			
		||||
 | 
			
		||||
class Watchy
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
  Watchy();
 | 
			
		||||
  void Init();
 | 
			
		||||
  void DeepSleep();
 | 
			
		||||
  void VibeMotor(uint8_t intervalMs = 100, uint8_t length = 20);
 | 
			
		||||
  float GetBatteryVoltage();
 | 
			
		||||
  void ConnectWiFi();
 | 
			
		||||
  void SyncNTPTime();
 | 
			
		||||
  void DisconnectWiFi();
 | 
			
		||||
  void ShowWatchFace(bool partialRefresh = false);
 | 
			
		||||
 | 
			
		||||
  virtual void DrawWatchFace() = 0;
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
  static void DisplayBusyCallback(const void *);
 | 
			
		||||
 | 
			
		||||
  static WatchyDisplayBase m_displayBase;
 | 
			
		||||
  static GxEPD2_BW<WatchyDisplayBase, WatchyDisplayBase::HEIGHT> m_display;
 | 
			
		||||
  static WatchyRTC m_RTC;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										357
									
								
								src/WatchyDisplay.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										357
									
								
								src/WatchyDisplay.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,357 @@
 | 
			
		|||
#include "WatchyDisplay.h"
 | 
			
		||||
 | 
			
		||||
WatchyDisplayBase::WatchyDisplayBase()
 | 
			
		||||
  : GxEPD2_EPD(DISPLAY_CS, DISPLAY_DC, DISPLAY_RES, DISPLAY_BUSY, HIGH, 10000000, DISPLAY_WIDTH, DISPLAY_HEIGHT, panel, false, true, true)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// init controller memory and screen (default white)
 | 
			
		||||
void WatchyDisplayBase::clearScreen(uint8_t value)
 | 
			
		||||
{
 | 
			
		||||
  writeScreenBuffer(value);
 | 
			
		||||
  refresh(true);
 | 
			
		||||
 | 
			
		||||
  if (!_using_partial_mode) {
 | 
			
		||||
    InitPart();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _startTransfer();
 | 
			
		||||
  TransferCommand(0x24);
 | 
			
		||||
  for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++) {
 | 
			
		||||
    _transfer(value);
 | 
			
		||||
  }
 | 
			
		||||
  _endTransfer();
 | 
			
		||||
 | 
			
		||||
  // Is this necessary? Come back to this - FIXME
 | 
			
		||||
  _startTransfer();
 | 
			
		||||
  TransferCommand(0x26);
 | 
			
		||||
  for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++) {
 | 
			
		||||
    _transfer(value);
 | 
			
		||||
  }
 | 
			
		||||
  _endTransfer();
 | 
			
		||||
  _initial_write = false; // initial full screen buffer clean done
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// init controller memory (default white)
 | 
			
		||||
void WatchyDisplayBase::writeScreenBuffer(uint8_t value)
 | 
			
		||||
{
 | 
			
		||||
  if (!_using_partial_mode) {
 | 
			
		||||
    InitPart();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (_initial_write) {
 | 
			
		||||
    _startTransfer();
 | 
			
		||||
    TransferCommand(0x26);
 | 
			
		||||
 | 
			
		||||
    for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++) {
 | 
			
		||||
      _transfer(value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _endTransfer();
 | 
			
		||||
 | 
			
		||||
    _initial_write = false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyDisplayBase::writeImage(const uint8_t * bitmap, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
 | 
			
		||||
{
 | 
			
		||||
  if (_initial_write) {
 | 
			
		||||
    if (!_using_partial_mode) {
 | 
			
		||||
      InitPart();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    writeScreenBuffer(0x24); // initial full screen buffer clean
 | 
			
		||||
    _initial_write = false;
 | 
			
		||||
  }
 | 
			
		||||
#if defined(ESP8266) || defined(ESP32)
 | 
			
		||||
  yield(); // avoid wdt
 | 
			
		||||
#endif
 | 
			
		||||
  int16_t wb = (w + 7) / 8; // width bytes, bitmaps are padded
 | 
			
		||||
  x -= x % 8; // byte boundary
 | 
			
		||||
  w = wb * 8; // byte boundary
 | 
			
		||||
  int16_t x1 = x < 0 ? 0 : x; // limit
 | 
			
		||||
  int16_t y1 = y < 0 ? 0 : y; // limit
 | 
			
		||||
  int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x; // limit
 | 
			
		||||
  int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y; // limit
 | 
			
		||||
  int16_t dx = x1 - x;
 | 
			
		||||
  int16_t dy = y1 - y;
 | 
			
		||||
  w1 -= dx;
 | 
			
		||||
  h1 -= dy;
 | 
			
		||||
  if ((w1 <= 0) || (h1 <= 0)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!_using_partial_mode) {
 | 
			
		||||
    InitPart();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  SetPartialRamArea(x1, y1, w1, h1);
 | 
			
		||||
  _startTransfer();
 | 
			
		||||
  TransferCommand(0x24);
 | 
			
		||||
 | 
			
		||||
  for (int16_t i = 0; i < h1; i++) {
 | 
			
		||||
    for (int16_t j = 0; j < w1 / 8; j++) {
 | 
			
		||||
      uint8_t data;
 | 
			
		||||
      // use wb, h of bitmap for index!
 | 
			
		||||
      int16_t idx = mirror_y ? j + dx / 8 + ((h - 1 - (i + dy))) * wb : j + dx / 8 + (i + dy) * wb;
 | 
			
		||||
      if (pgm) {
 | 
			
		||||
#if defined(__AVR) || defined(ESP8266) || defined(ESP32)
 | 
			
		||||
        data = pgm_read_byte(&bitmap[idx]);
 | 
			
		||||
#else
 | 
			
		||||
        data = bitmap[idx];
 | 
			
		||||
#endif
 | 
			
		||||
      } else {
 | 
			
		||||
        data = bitmap[idx];
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (invert) {
 | 
			
		||||
        data = ~data;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      _transfer(data);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  _endTransfer();
 | 
			
		||||
#if defined(ESP8266) || defined(ESP32)
 | 
			
		||||
  yield(); // avoid wdt
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// screen refresh from controller memory, partial screen
 | 
			
		||||
void WatchyDisplayBase::writeImagePart(const uint8_t * bitmap, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
 | 
			
		||||
{
 | 
			
		||||
  if (_initial_write) {
 | 
			
		||||
    writeScreenBuffer(); // initial full screen buffer clean
 | 
			
		||||
  }
 | 
			
		||||
#if defined(ESP8266) || defined(ESP32)
 | 
			
		||||
  yield(); // avoid wdt
 | 
			
		||||
#endif
 | 
			
		||||
  if ((w_bitmap < 0) || (h_bitmap < 0) || (w < 0) || (h < 0)) return;
 | 
			
		||||
  if ((x_part < 0) || (x_part >= w_bitmap)) return;
 | 
			
		||||
  if ((y_part < 0) || (y_part >= h_bitmap)) return;
 | 
			
		||||
  int16_t wb_bitmap = (w_bitmap + 7) / 8; // width bytes, bitmaps are padded
 | 
			
		||||
  x_part -= x_part % 8; // byte boundary
 | 
			
		||||
  w = w_bitmap - x_part < w ? w_bitmap - x_part : w; // limit
 | 
			
		||||
  h = h_bitmap - y_part < h ? h_bitmap - y_part : h; // limit
 | 
			
		||||
  x -= x % 8; // byte boundary
 | 
			
		||||
  w = 8 * ((w + 7) / 8); // byte boundary, bitmaps are padded
 | 
			
		||||
  int16_t x1 = x < 0 ? 0 : x; // limit
 | 
			
		||||
  int16_t y1 = y < 0 ? 0 : y; // limit
 | 
			
		||||
  int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x; // limit
 | 
			
		||||
  int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y; // limit
 | 
			
		||||
  int16_t dx = x1 - x;
 | 
			
		||||
  int16_t dy = y1 - y;
 | 
			
		||||
  w1 -= dx;
 | 
			
		||||
  h1 -= dy;
 | 
			
		||||
  if ((w1 <= 0) || (h1 <= 0)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!_using_partial_mode) {
 | 
			
		||||
    InitPart();
 | 
			
		||||
  }
 | 
			
		||||
  SetPartialRamArea(x1, y1, w1, h1);
 | 
			
		||||
  _startTransfer();
 | 
			
		||||
  TransferCommand(0x24);
 | 
			
		||||
  for (int16_t i = 0; i < h1; i++)
 | 
			
		||||
  {
 | 
			
		||||
    for (int16_t j = 0; j < w1 / 8; j++)
 | 
			
		||||
    {
 | 
			
		||||
      uint8_t data;
 | 
			
		||||
      // use wb_bitmap, h_bitmap of bitmap for index!
 | 
			
		||||
      int16_t idx = mirror_y ? x_part / 8 + j + dx / 8 + ((h_bitmap - 1 - (y_part + i + dy))) * wb_bitmap : x_part / 8 + j + dx / 8 + (y_part + i + dy) * wb_bitmap;
 | 
			
		||||
      if (pgm)
 | 
			
		||||
      {
 | 
			
		||||
#if defined(__AVR) || defined(ESP8266) || defined(ESP32)
 | 
			
		||||
        data = pgm_read_byte(&bitmap[idx]);
 | 
			
		||||
#else
 | 
			
		||||
        data = bitmap[idx];
 | 
			
		||||
#endif
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        data = bitmap[idx];
 | 
			
		||||
      }
 | 
			
		||||
      if (invert) data = ~data;
 | 
			
		||||
      _transfer(data);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  _endTransfer();
 | 
			
		||||
#if defined(ESP8266) || defined(ESP32)
 | 
			
		||||
  yield(); // avoid wdt
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// screen refresh from controller memory to full screen
 | 
			
		||||
void WatchyDisplayBase::refresh(bool partial_update_mode)
 | 
			
		||||
{
 | 
			
		||||
  if (partial_update_mode) {
 | 
			
		||||
    refresh(0, 0, WIDTH, HEIGHT);
 | 
			
		||||
  } else {
 | 
			
		||||
    if (_using_partial_mode) {
 | 
			
		||||
      InitFull();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    UpdateFull();
 | 
			
		||||
    _initial_refresh = false; // initial full update done
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyDisplayBase::refresh(int16_t x, int16_t y, int16_t w, int16_t h)
 | 
			
		||||
{
 | 
			
		||||
  if (_initial_refresh) {
 | 
			
		||||
    return refresh(false); // initial update needs be full update
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // intersection with screen
 | 
			
		||||
  int16_t w1 = x < 0 ? w + x : w; // reduce
 | 
			
		||||
  int16_t h1 = y < 0 ? h + y : h; // reduce
 | 
			
		||||
  int16_t x1 = x < 0 ? 0 : x; // limit
 | 
			
		||||
  int16_t y1 = y < 0 ? 0 : y; // limit
 | 
			
		||||
  w1 = x1 + w1 < int16_t(WIDTH) ? w1 : int16_t(WIDTH) - x1; // limit
 | 
			
		||||
  h1 = y1 + h1 < int16_t(HEIGHT) ? h1 : int16_t(HEIGHT) - y1; // limit
 | 
			
		||||
  if ((w1 <= 0) || (h1 <= 0)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // make x1, w1 multiple of 8
 | 
			
		||||
  w1 += x1 % 8;
 | 
			
		||||
  if (w1 % 8 > 0) w1 += 8 - w1 % 8;
 | 
			
		||||
  x1 -= x1 % 8;
 | 
			
		||||
  if (!_using_partial_mode) {
 | 
			
		||||
    InitPart();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  SetPartialRamArea(x1, y1, w1, h1);
 | 
			
		||||
  UpdatePart();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void WatchyDisplayBase::powerOff()
 | 
			
		||||
{
 | 
			
		||||
  if (_power_is_on) {
 | 
			
		||||
    _startTransfer();
 | 
			
		||||
    TransferCommand(0x22);
 | 
			
		||||
    _transfer(0x83);
 | 
			
		||||
    TransferCommand(0x20);
 | 
			
		||||
    _endTransfer();
 | 
			
		||||
    _waitWhileBusy("_PowerOff", power_off_time);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _power_is_on = false;
 | 
			
		||||
  _using_partial_mode = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyDisplayBase::hibernate()
 | 
			
		||||
{
 | 
			
		||||
  if (_rst >= 0) {
 | 
			
		||||
    _writeCommand(0x10); // deep sleep mode
 | 
			
		||||
    _writeData(0x1);     // enter deep sleep
 | 
			
		||||
    _hibernating = true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyDisplayBase::powerOn()
 | 
			
		||||
{
 | 
			
		||||
  if (!_power_is_on) {
 | 
			
		||||
    _startTransfer();
 | 
			
		||||
    TransferCommand(0x22);
 | 
			
		||||
    _transfer(0xf8);
 | 
			
		||||
    TransferCommand(0x20);
 | 
			
		||||
    _endTransfer();
 | 
			
		||||
    _waitWhileBusy("_PowerOn", power_on_time);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _power_is_on = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyDisplayBase::InitFull()
 | 
			
		||||
{
 | 
			
		||||
  InitDisplay();
 | 
			
		||||
  powerOn();
 | 
			
		||||
  _using_partial_mode = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyDisplayBase::InitPart()
 | 
			
		||||
{
 | 
			
		||||
  InitDisplay();
 | 
			
		||||
  powerOn();
 | 
			
		||||
  _using_partial_mode = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyDisplayBase::InitDisplay()
 | 
			
		||||
{
 | 
			
		||||
  if (_hibernating) {
 | 
			
		||||
    _reset();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _writeCommand(0x12); // soft reset
 | 
			
		||||
  _waitWhileBusy("_SoftReset", 10); // 10ms max according to specs
 | 
			
		||||
 | 
			
		||||
  _startTransfer();
 | 
			
		||||
  TransferCommand(0x01); // Driver output control
 | 
			
		||||
  _transfer(0xC7);
 | 
			
		||||
  _transfer(0x00);
 | 
			
		||||
  _transfer(0x00);
 | 
			
		||||
  TransferCommand(0x3C); // BorderWavefrom
 | 
			
		||||
  _transfer(darkBorder ? 0x02 : 0x05);
 | 
			
		||||
  TransferCommand(0x18); // Read built-in temperature sensor
 | 
			
		||||
  _transfer(0x80);
 | 
			
		||||
  _endTransfer();
 | 
			
		||||
 | 
			
		||||
  SetPartialRamArea(0, 0, WIDTH, HEIGHT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyDisplayBase::UpdateFull()
 | 
			
		||||
{
 | 
			
		||||
  _startTransfer();
 | 
			
		||||
  TransferCommand(0x22);
 | 
			
		||||
  _transfer(0xf4);
 | 
			
		||||
  TransferCommand(0x20);
 | 
			
		||||
  _endTransfer();
 | 
			
		||||
  _waitWhileBusy("_Update_Full", full_refresh_time);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyDisplayBase::UpdatePart()
 | 
			
		||||
{
 | 
			
		||||
  _startTransfer();
 | 
			
		||||
  TransferCommand(0x22);
 | 
			
		||||
  //_transfer(0xcc); // skip temperature load (-5ms)
 | 
			
		||||
  _transfer(0xfc);
 | 
			
		||||
  TransferCommand(0x20);
 | 
			
		||||
  _endTransfer();
 | 
			
		||||
  _waitWhileBusy("_Update_Part", partial_refresh_time);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyDisplayBase::TransferCommand(uint8_t value)
 | 
			
		||||
{
 | 
			
		||||
  if (_dc >= 0) {
 | 
			
		||||
    digitalWrite(_dc, LOW);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  SPI.transfer(value);
 | 
			
		||||
 | 
			
		||||
  if (_dc >= 0) {
 | 
			
		||||
    digitalWrite(_dc, HIGH);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyDisplayBase::SetPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
 | 
			
		||||
{
 | 
			
		||||
  _startTransfer();
 | 
			
		||||
  TransferCommand(0x11); // set ram entry mode
 | 
			
		||||
  _transfer(0x03);    // x increase, y increase : normal mode
 | 
			
		||||
  TransferCommand(0x44);
 | 
			
		||||
  _transfer(x / 8);
 | 
			
		||||
  _transfer((x + w - 1) / 8);
 | 
			
		||||
  TransferCommand(0x45);
 | 
			
		||||
  _transfer(y % 256);
 | 
			
		||||
  _transfer(y / 256);
 | 
			
		||||
  _transfer((y + h - 1) % 256);
 | 
			
		||||
  _transfer((y + h - 1) / 256);
 | 
			
		||||
  TransferCommand(0x4e);
 | 
			
		||||
  _transfer(x / 8);
 | 
			
		||||
  TransferCommand(0x4f);
 | 
			
		||||
  _transfer(y % 256);
 | 
			
		||||
  _transfer(y / 256);
 | 
			
		||||
  _endTransfer();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								src/WatchyDisplay.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/WatchyDisplay.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,50 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include <GxEPD2_EPD.h>
 | 
			
		||||
#include <GxEPD2_BW.h>
 | 
			
		||||
 | 
			
		||||
class WatchyDisplayBase : public GxEPD2_EPD
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
  static const uint16_t WIDTH = DISPLAY_WIDTH;
 | 
			
		||||
  static const uint16_t HEIGHT = DISPLAY_HEIGHT;
 | 
			
		||||
  static const uint16_t WIDTH_VISIBLE = WIDTH;
 | 
			
		||||
 | 
			
		||||
  WatchyDisplayBase();
 | 
			
		||||
 | 
			
		||||
  // init controller memory and screen (default white)
 | 
			
		||||
  void clearScreen(uint8_t value = 0xFF) override;
 | 
			
		||||
 | 
			
		||||
  // init controller memory (default white)
 | 
			
		||||
  void writeScreenBuffer(uint8_t value  = 0xFF) override;
 | 
			
		||||
  void writeImage(const uint8_t * bitmap, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) override;
 | 
			
		||||
 | 
			
		||||
  // screen refresh from controller memory, partial screen
 | 
			
		||||
  void writeImagePart(const uint8_t * bitmap, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) override;
 | 
			
		||||
 | 
			
		||||
  // screen refresh from controller memory to full screen
 | 
			
		||||
  void refresh(bool partial_update_mode = false) override;
 | 
			
		||||
  void refresh(int16_t x, int16_t y, int16_t w, int16_t h) override;
 | 
			
		||||
  void powerOff() override;
 | 
			
		||||
  void hibernate() override;
 | 
			
		||||
 | 
			
		||||
  void powerOn();
 | 
			
		||||
 | 
			
		||||
  static const GxEPD2::Panel panel = GxEPD2::GDEH0154D67;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  static const uint16_t power_on_time = 100; // ms, e.g. 95583us
 | 
			
		||||
  static const uint16_t power_off_time = 150; // ms, e.g. 140621us
 | 
			
		||||
  static const uint16_t full_refresh_time = 2600; // ms, e.g. 2509602us
 | 
			
		||||
  static const uint16_t partial_refresh_time = 500; // ms, e.g. 457282us
 | 
			
		||||
  static const bool darkBorder = false;
 | 
			
		||||
 | 
			
		||||
  void InitFull();
 | 
			
		||||
  void InitPart();
 | 
			
		||||
  void InitDisplay();
 | 
			
		||||
  void UpdateFull();
 | 
			
		||||
  void UpdatePart();
 | 
			
		||||
  void TransferCommand(uint8_t value);
 | 
			
		||||
  void SetPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										112
									
								
								src/WatchyRTC.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/WatchyRTC.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,112 @@
 | 
			
		|||
#include "WatchyRTC.h"
 | 
			
		||||
 | 
			
		||||
WatchyRTC::WatchyRTC()
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyRTC::init() {
 | 
			
		||||
  byte error;
 | 
			
		||||
  Wire.beginTransmission(RTC_PCF_ADDR);
 | 
			
		||||
  error = Wire.endTransmission();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyRTC::config(String datetime) { // String datetime format is YYYY:MM:DD:HH:MM:SS
 | 
			
		||||
  _PCFConfig(datetime);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyRTC::clearAlarm() {
 | 
			
		||||
  int nextAlarmMinute = 0;
 | 
			
		||||
  rtc_pcf.clearAlarm(); // resets the alarm flag in the RTC
 | 
			
		||||
  nextAlarmMinute = rtc_pcf.getMinute();
 | 
			
		||||
  nextAlarmMinute = (nextAlarmMinute == 59) ? 0 : (nextAlarmMinute + 1); // set alarm to trigger 1 minute from now
 | 
			
		||||
  rtc_pcf.setAlarm(nextAlarmMinute, 99, 99, 99);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyRTC::read(tmElements_t & tm, int offsetInSeconds) {
 | 
			
		||||
  rtc_pcf.getDate();
 | 
			
		||||
  tm.Year  = y2kYearToTm(rtc_pcf.getYear());
 | 
			
		||||
  tm.Month = rtc_pcf.getMonth();
 | 
			
		||||
  tm.Day   = rtc_pcf.getDay();
 | 
			
		||||
  tm.Wday = rtc_pcf.getWeekday() + 1; // TimeLib & DS3231 has Wday range of 1-7, but PCF8563 stores day of week in 0-6 range
 | 
			
		||||
  tm.Hour   = rtc_pcf.getHour();
 | 
			
		||||
  tm.Minute = rtc_pcf.getMinute();
 | 
			
		||||
  tm.Second = rtc_pcf.getSecond();
 | 
			
		||||
 | 
			
		||||
  int day = tm.Day;
 | 
			
		||||
  int hour = tm.Hour;
 | 
			
		||||
  int minute = tm.Minute;
 | 
			
		||||
  int second = tm.Second;
 | 
			
		||||
 | 
			
		||||
  // adjust for offset - making some assumptions here: month and year will never change.
 | 
			
		||||
  second += offsetInSeconds;
 | 
			
		||||
  if (second >= 60 || second < 0) {
 | 
			
		||||
    minute += second / 60;
 | 
			
		||||
    second = second % 60;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (minute >= 60 || minute < 0) {
 | 
			
		||||
    hour += minute / 60;
 | 
			
		||||
    minute = minute % 60;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (hour >= 24 || hour < 0) {
 | 
			
		||||
    day += hour / 24;
 | 
			
		||||
    hour = hour % 24;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  tm.Day = day;
 | 
			
		||||
  tm.Hour = hour;
 | 
			
		||||
  tm.Minute = minute;
 | 
			
		||||
  tm.Second = second;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyRTC::set(tmElements_t tm) {
 | 
			
		||||
  time_t t = makeTime(tm); // make and break to calculate tm.Wday
 | 
			
		||||
  breakTime(t, tm);
 | 
			
		||||
  // day, weekday, month, century(1=1900, 0=2000), year(0-99)
 | 
			
		||||
  rtc_pcf.setDate(tm.Day, tm.Wday - 1, tm.Month, 0, tmYearToY2k(tm.Year));
 | 
			
		||||
  rtc_pcf.setTime(tm.Hour, tm.Minute, tm.Second);
 | 
			
		||||
  clearAlarm();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchyRTC::_PCFConfig(String datetime) { // String datetime is YYYY:MM:DD:HH:MM:SS
 | 
			
		||||
  if (datetime != "") {
 | 
			
		||||
    tmElements_t tm;
 | 
			
		||||
    tm.Year = CalendarYrToTm(_getValue(datetime, ':', 0).toInt()); // YYYY -
 | 
			
		||||
                                                                   // 1970
 | 
			
		||||
    tm.Month  = _getValue(datetime, ':', 1).toInt();
 | 
			
		||||
    tm.Day    = _getValue(datetime, ':', 2).toInt();
 | 
			
		||||
    tm.Hour   = _getValue(datetime, ':', 3).toInt();
 | 
			
		||||
    tm.Minute = _getValue(datetime, ':', 4).toInt();
 | 
			
		||||
    tm.Second = _getValue(datetime, ':', 5).toInt();
 | 
			
		||||
    time_t t  = makeTime(tm); // make and break to calculate tm.Wday
 | 
			
		||||
    breakTime(t, tm);
 | 
			
		||||
    // day, weekday, month, century(1=1900, 0=2000), year(0-99)
 | 
			
		||||
    rtc_pcf.setDate(
 | 
			
		||||
        tm.Day, tm.Wday - 1, tm.Month, 0,
 | 
			
		||||
        tmYearToY2k(tm.Year)); // TimeLib & DS3231 has Wday range of 1-7, but
 | 
			
		||||
                               // PCF8563 stores day of week in 0-6 range
 | 
			
		||||
    // hr, min, sec
 | 
			
		||||
    rtc_pcf.setTime(tm.Hour, tm.Minute, tm.Second);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // on POR event, PCF8563 sets month to 0, which will give an error since
 | 
			
		||||
  // months are 1-12
 | 
			
		||||
  clearAlarm();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
String WatchyRTC::_getValue(String data, char separator, int index) {
 | 
			
		||||
  int found      = 0;
 | 
			
		||||
  int strIndex[] = {0, -1};
 | 
			
		||||
  int maxIndex   = data.length() - 1;
 | 
			
		||||
 | 
			
		||||
  for (int i = 0; i <= maxIndex && found <= index; i++) {
 | 
			
		||||
    if (data.charAt(i) == separator || i == maxIndex) {
 | 
			
		||||
      found++;
 | 
			
		||||
      strIndex[0] = strIndex[1] + 1;
 | 
			
		||||
      strIndex[1] = (i == maxIndex) ? i + 1 : i;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/WatchyRTC.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/WatchyRTC.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
#ifndef WATCHY_RTC_H
 | 
			
		||||
#define WATCHY_RTC_H
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include <TimeLib.h>
 | 
			
		||||
#include <Rtc_Pcf8563.h>
 | 
			
		||||
 | 
			
		||||
#define RTC_PCF_ADDR    0x51
 | 
			
		||||
#define YEAR_OFFSET_PCF 2000
 | 
			
		||||
 | 
			
		||||
class WatchyRTC {
 | 
			
		||||
public:
 | 
			
		||||
  Rtc_Pcf8563 rtc_pcf;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
  WatchyRTC();
 | 
			
		||||
  void init();
 | 
			
		||||
  void config(String datetime); // String datetime format is YYYY:MM:DD:HH:MM:SS
 | 
			
		||||
  void clearAlarm();
 | 
			
		||||
  void read(tmElements_t & tm, int offsetInSeconds = 0);
 | 
			
		||||
  void set(tmElements_t tm);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  void _PCFConfig(String datetime);
 | 
			
		||||
  int _getDayOfWeek(int d, int m, int y);
 | 
			
		||||
  String _getValue(String data, char separator, int index);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										27
									
								
								src/config.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/config.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
#define MENU_BTN_PIN 26
 | 
			
		||||
#define BACK_BTN_PIN 25
 | 
			
		||||
#define DOWN_BTN_PIN 4
 | 
			
		||||
#define UP_BTN_PIN 35
 | 
			
		||||
#define BATT_ADC_PIN 34
 | 
			
		||||
#define DISPLAY_CS 5
 | 
			
		||||
#define DISPLAY_RES 9
 | 
			
		||||
#define DISPLAY_DC 10
 | 
			
		||||
#define DISPLAY_BUSY 19
 | 
			
		||||
#define ACC_INT_1_PIN 14
 | 
			
		||||
#define ACC_INT_2_PIN 12
 | 
			
		||||
#define VIB_MOTOR_PIN 13
 | 
			
		||||
#define RTC_INT_PIN 27
 | 
			
		||||
 | 
			
		||||
#define MENU_BTN_MASK GPIO_SEL_26
 | 
			
		||||
#define BACK_BTN_MASK GPIO_SEL_25
 | 
			
		||||
#define DOWN_BTN_MASK GPIO_SEL_4
 | 
			
		||||
#define UP_BTN_MASK  GPIO_SEL_35
 | 
			
		||||
#define ACC_INT_MASK  GPIO_SEL_14
 | 
			
		||||
#define BTN_PIN_MASK  MENU_BTN_MASK|BACK_BTN_MASK|UP_BTN_MASK|DOWN_BTN_MASK
 | 
			
		||||
 | 
			
		||||
#define DISPLAY_WIDTH 200
 | 
			
		||||
#define DISPLAY_HEIGHT 200
 | 
			
		||||
 | 
			
		||||
#define WIFI_SSID "<my ssid>>"
 | 
			
		||||
#define WIFI_PASS "<my pass>"
 | 
			
		||||
#define TZ_OFFSET 3600 * 3
 | 
			
		||||
							
								
								
									
										11
									
								
								test/README
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								test/README
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
 | 
			
		||||
This directory is intended for PlatformIO Test Runner and project tests.
 | 
			
		||||
 | 
			
		||||
Unit Testing is a software testing method by which individual units of
 | 
			
		||||
source code, sets of one or more MCU program modules together with associated
 | 
			
		||||
control data, usage procedures, and operating procedures, are tested to
 | 
			
		||||
determine whether they are fit for use. Unit testing finds problems early
 | 
			
		||||
in the development cycle.
 | 
			
		||||
 | 
			
		||||
More information about PlatformIO Unit Testing:
 | 
			
		||||
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue