Compare commits
	
		
			2 Commits
		
	
	
		
			8e0f2df9b1
			...
			de9fc112d0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| de9fc112d0 | |||
| 3dfd08e495 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -4,3 +4,4 @@ | ||||
| .clang_complete | ||||
| .gcc-flags.json | ||||
| firmware.bin | ||||
| /data/* | ||||
|   | ||||
| @@ -19,6 +19,8 @@ lib_deps = | ||||
|   MD_MAX72XX | ||||
|   IOTAppStory-ESP | ||||
|   NTPClient | ||||
|   ESPAsyncTCP | ||||
|   ESP Async WebServer | ||||
|  | ||||
| upload_port = /dev/cu.wchusbserial1420 | ||||
| upload_speed = 921600 | ||||
|   | ||||
| @@ -23,21 +23,21 @@ const MD_RobotEyes::animFrame_t MD_RobotEyes::seqWink[] /*PROGMEM*/ = | ||||
|   { { 2, 0 }, FRAME_TIME/2 }, | ||||
|   { { 3, 0 }, FRAME_TIME/2 }, | ||||
|   { { 4, 0 }, FRAME_TIME/2 }, | ||||
|   { { 5, 0 }, FRAME_TIME * 2 }, | ||||
|   { { 5, 0 }, FRAME_TIME }, | ||||
| }; | ||||
|  | ||||
| const MD_RobotEyes::animFrame_t MD_RobotEyes::seqRight[] /*PROGMEM*/ = | ||||
| { | ||||
|   { { 0, 0 }, FRAME_TIME }, | ||||
|   { { 6, 6 }, FRAME_TIME }, | ||||
|   { { 7, 7 }, FRAME_TIME * 5 }, | ||||
|   { { 7, 7 }, FRAME_TIME * 3 }, | ||||
| }; | ||||
|  | ||||
| const MD_RobotEyes::animFrame_t MD_RobotEyes::seqLeft[] /*PROGMEM*/ = | ||||
| { | ||||
|   { { 0, 0 }, FRAME_TIME }, | ||||
|   { { 8, 8 }, FRAME_TIME }, | ||||
|   { { 9, 9 }, FRAME_TIME * 5 }, | ||||
|   { { 9, 9 }, FRAME_TIME * 3 }, | ||||
| }; | ||||
|  | ||||
| const MD_RobotEyes::animFrame_t MD_RobotEyes::seqUp[] /*PROGMEM*/ = | ||||
| @@ -53,7 +53,7 @@ const MD_RobotEyes::animFrame_t MD_RobotEyes::seqDown[] /*PROGMEM*/ = | ||||
|   { { 00, 00 }, FRAME_TIME }, | ||||
|   { { 14, 14 }, FRAME_TIME }, | ||||
|   { { 15, 15 }, FRAME_TIME }, | ||||
|   { { 16, 16 }, FRAME_TIME * 5 }, | ||||
|   { { 16, 16 }, FRAME_TIME * 3 }, | ||||
| }; | ||||
|  | ||||
| const MD_RobotEyes::animFrame_t MD_RobotEyes::seqAngry[] /*PROGMEM*/ = | ||||
| @@ -62,7 +62,7 @@ const MD_RobotEyes::animFrame_t MD_RobotEyes::seqAngry[] /*PROGMEM*/ = | ||||
|   { { 22, 17 }, FRAME_TIME }, | ||||
|   { { 23, 18 }, FRAME_TIME }, | ||||
|   { { 24, 19 }, FRAME_TIME }, | ||||
|   { { 25, 20 }, 2000 }, | ||||
|   { { 25, 20 }, 1000 }, | ||||
| }; | ||||
|  | ||||
| const MD_RobotEyes::animFrame_t MD_RobotEyes::seqSad[] /*PROGMEM*/ = | ||||
| @@ -70,14 +70,14 @@ const MD_RobotEyes::animFrame_t MD_RobotEyes::seqSad[] /*PROGMEM*/ = | ||||
|   { { 00, 00 }, FRAME_TIME }, | ||||
|   { { 32, 27 }, FRAME_TIME }, | ||||
|   { { 33, 28 }, FRAME_TIME }, | ||||
|   { { 34, 29 }, 2000 }, | ||||
|   { { 34, 29 }, 1000 }, | ||||
| }; | ||||
|  | ||||
| const MD_RobotEyes::animFrame_t MD_RobotEyes::seqEvil[] /*PROGMEM*/ = | ||||
| { | ||||
|   { { 00, 00 }, FRAME_TIME }, | ||||
|   { { 39, 37 }, FRAME_TIME }, | ||||
|   { { 40, 38 }, 2000 }, | ||||
|   { { 40, 38 }, 1000 }, | ||||
| }; | ||||
|  | ||||
| const MD_RobotEyes::animFrame_t MD_RobotEyes::seqEvil2[] /*PROGMEM*/ = | ||||
| @@ -86,7 +86,7 @@ const MD_RobotEyes::animFrame_t MD_RobotEyes::seqEvil2[] /*PROGMEM*/ = | ||||
|   { { 54, 17 }, FRAME_TIME }, | ||||
|   { { 55, 18 }, FRAME_TIME }, | ||||
|   { { 56, 19 }, FRAME_TIME }, | ||||
|   { { 57, 20 }, 2000 }, | ||||
|   { { 57, 20 }, 1000 }, | ||||
| }; | ||||
|  | ||||
| const MD_RobotEyes::animFrame_t MD_RobotEyes::seqSquint[] /*PROGMEM*/ = | ||||
| @@ -95,7 +95,7 @@ const MD_RobotEyes::animFrame_t MD_RobotEyes::seqSquint[] /*PROGMEM*/ = | ||||
|   { { 54, 54 }, FRAME_TIME }, | ||||
|   { { 55, 55 }, FRAME_TIME }, | ||||
|   { { 56, 56 }, FRAME_TIME }, | ||||
|   { { 57, 57 }, 2000 }, | ||||
|   { { 57, 57 }, 1000 }, | ||||
| }; | ||||
|  | ||||
| const MD_RobotEyes::animFrame_t MD_RobotEyes::seqDead[] /*PROGMEM*/ = | ||||
|   | ||||
							
								
								
									
										85
									
								
								src/Relays.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,85 @@ | ||||
| #include "Relays.h" | ||||
|  | ||||
| static unsigned long last_relay_off_ts=0, last_relay_hold_ts=0; | ||||
| enum RelayState { RELAY_STATE_OFF=0, RELAY_STATE_ON_EVEN_MINUTE, RELAY_STATE_ON_ODD_MINUTE }; | ||||
| static RelayState relaysState = RELAY_STATE_OFF; | ||||
| static RelayState lastRelayOnState = RELAY_STATE_ON_EVEN_MINUTE; | ||||
| static bool relayCanSwitch=true; | ||||
|  | ||||
| void Relays::begin(int _relay1Pin, int _relay2Pin) { | ||||
|   relay1Pin = _relay1Pin; | ||||
|   relay2Pin = _relay2Pin; | ||||
|   pinMode(relay1Pin, OUTPUT); | ||||
|   pinMode(relay2Pin, OUTPUT); | ||||
|   digitalWrite(relay1Pin, LOW); | ||||
|   digitalWrite(relay2Pin, LOW); | ||||
| } | ||||
|  | ||||
| void Relays::toggle() { | ||||
|   if (relayCanSwitch) { | ||||
|     if (lastRelayOnState == RELAY_STATE_ON_EVEN_MINUTE) { | ||||
|       digitalWrite(relay1Pin, HIGH); | ||||
|       digitalWrite(relay2Pin, LOW); | ||||
|       relaysState = RELAY_STATE_ON_ODD_MINUTE; | ||||
|       // P.print("R-OEv"); | ||||
|     } else { | ||||
|       digitalWrite(relay1Pin, LOW); | ||||
|       digitalWrite(relay2Pin, HIGH); | ||||
|       relaysState = RELAY_STATE_ON_EVEN_MINUTE; | ||||
|       // P.print("R-OOd"); | ||||
|     } | ||||
|     lastRelayOnState = relaysState; | ||||
|   } // else P.print("R-OErr"); | ||||
|   relayCanSwitch = false; | ||||
|   last_relay_hold_ts = millis(); | ||||
|   // increment displayed time | ||||
|   displayedTime_minutes++; | ||||
|   if (displayedTime_minutes >= 60) { | ||||
|     displayedTime_hours++; | ||||
|     displayedTime_minutes -= 60; | ||||
|     if (displayedTime_hours >= 24) { | ||||
|       displayedTime_hours = 0; | ||||
|     } | ||||
|   } | ||||
|   if (fastforwardMode) { | ||||
|     if (displayedTime_hours == targetHours && displayedTime_minutes == targetMinutes) { | ||||
|       // wanted time reached! | ||||
|       fastforwardMode = false; | ||||
|     } | ||||
|   } | ||||
|   Serial.println(F("Toggle Relays")); | ||||
| } | ||||
|  | ||||
| void Relays::off(void) { | ||||
|   digitalWrite(relay1Pin, LOW); | ||||
|   digitalWrite(relay2Pin, LOW); | ||||
|   last_relay_off_ts = millis(); | ||||
|   relaysState = RELAY_STATE_OFF; | ||||
|   // P.print("R-Off"); | ||||
| } | ||||
|  | ||||
| void Relays::loop(void) { | ||||
|   if (relaysState == RELAY_STATE_OFF) { | ||||
|     if (millis() - last_relay_off_ts > minOffTime_ms) { | ||||
|       relayCanSwitch = true; | ||||
|       if (fastforwardMode) toggle(); | ||||
|     } | ||||
|   } else { | ||||
|     if (millis() - last_relay_hold_ts > holdTime_ms) { | ||||
|       off(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void Relays::fwdToTime(unsigned int hours, unsigned int minutes) { | ||||
|   // @TODO | ||||
|   // as we have a 12h clock, we adjust displayedTime to be before the wanted time | ||||
|   if (hours < displayedTime_hours || | ||||
|       (hours == displayedTime_hours && minutes < displayedTime_minutes)) { | ||||
|           displayedTime_hours -= 12; | ||||
|           if (displayedTime_hours < 0) displayedTime_hours += 24; | ||||
|   } | ||||
|   targetHours = hours; | ||||
|   targetMinutes = minutes; | ||||
|   fastforwardMode = true; | ||||
| } | ||||
							
								
								
									
										40
									
								
								src/Relays.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,40 @@ | ||||
| // | ||||
| //    FILE: Relays.h | ||||
| // VERSION: 1.0 | ||||
| // PURPOSE: Handle two relays used to toggle voltage polarity to control a clock | ||||
| // | ||||
| // | ||||
|  | ||||
| #ifndef _relaysLoaded | ||||
| #define _relaysLoaded true | ||||
|  | ||||
| #include <Arduino.h> | ||||
|  | ||||
| class Relays { | ||||
| public: | ||||
|   Relays():holdTime_ms(200), minOffTime_ms(100), fastforwardMode(false) {}; | ||||
|   void begin(int _relay1Pin, int _relay2Pin); | ||||
|   void toggle(); | ||||
|   void off(); | ||||
|   void loop(); | ||||
|   void setHoldTime_ms(unsigned int newValue) { holdTime_ms = newValue; } | ||||
|   void setMinOffTime_ms(unsigned int newValue) { minOffTime_ms = newValue; } | ||||
|   unsigned int getHoldTime_ms() { return holdTime_ms; } | ||||
|   unsigned int getMinOffTime_ms() { return minOffTime_ms; } | ||||
|   void setDisplayedTime(unsigned int h, unsigned int m) { displayedTime_hours = h; displayedTime_minutes = m; } | ||||
|   unsigned int getDisplayedTime_hours() { return displayedTime_hours; } | ||||
|   unsigned int getDisplayedTime_minutes() { return displayedTime_minutes; } | ||||
|   void fwdToTime(unsigned int hours, unsigned int minutes); | ||||
| protected: | ||||
|   int relay1Pin, relay2Pin; | ||||
|   unsigned int holdTime_ms; | ||||
|   unsigned int minOffTime_ms; | ||||
|   unsigned int displayedTime_hours; | ||||
|   unsigned int displayedTime_minutes; | ||||
|   unsigned int targetHours; | ||||
|   unsigned int targetMinutes; | ||||
|   boolean fastforwardMode; | ||||
|  | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										
											BIN
										
									
								
								src/data/btn-do.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 9.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/data/btn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/data/favicon.ico
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 4.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/data/fremoei.gif
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 3.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/data/icon-0-75x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/data/icon-1-5x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/data/icon-1x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/data/icon-2x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/data/icon-3x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 11 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/data/icon-4x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/data/img_fremo_sw.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/data/img_sh0.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 191 B | 
							
								
								
									
										94
									
								
								src/data/index.htm
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,94 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|  | ||||
| <head> | ||||
|   <meta http-equiv="Content-type" content="text/html; charset=utf-8"> | ||||
|   <meta name="viewport" content="width=device-width,initial-scale=1"> | ||||
|   <title>Fastclock</title> | ||||
|   <link href="main.css" rel="stylesheet" type="text/css" /> | ||||
|   <link rel="manifest" href="manifest.json"> | ||||
|   <script type="text/javascript"> | ||||
|     var button = document.getElementById("button"); | ||||
|     var led = document.getElementById("led"); | ||||
|  | ||||
|     function ajaxGet(url, responseCallback) { | ||||
|       var xh = new XMLHttpRequest(); | ||||
|       xh.onreadystatechange = function () { | ||||
|         if (xh.readyState == 4) { | ||||
|           if (xh.status == 200) { | ||||
|             var result = JSON.parse(xh.responseText); | ||||
|             responseCallback(result); | ||||
|           } | ||||
|         } | ||||
|       }; | ||||
|       xh.open("GET", url, true); | ||||
|       xh.send(null); | ||||
|     }; | ||||
|  | ||||
|     function handleFwdClockResponse(result) { | ||||
|       if (result.result == "OK") { | ||||
|         led.src = "led-on.png"; | ||||
|         var clockName = document.getElementById("clockNameId"); | ||||
|         clockName.html = result.clockName; | ||||
|         var displayedTime = document.getElementById("displayedTimeId"); | ||||
|         displayedTime.html = result.hours + ":" + result.minutes + " " + result.seconds; | ||||
|       } else { | ||||
|         led.src = "led-off.png"; | ||||
|       } | ||||
|     }; | ||||
|  | ||||
|     function fwdClock() { | ||||
|       ajaxGet("/fwd", handleFwdClockResponse); | ||||
|     }; | ||||
|  | ||||
|     function handleClockStatusResponse(result) { | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     function getClockStatus() { | ||||
|       ajaxGet("/clock", handleClockStatusResponse); | ||||
|       setTimeout(function() { | ||||
|         getClockStatus(); | ||||
|       }, 400); | ||||
|     }; | ||||
|  | ||||
|     function setDisplayedTime() { | ||||
|       var hoursInput = document.getElementById("displayedHoursInputId"); | ||||
|       var minutesInput = document.getElementById("displayedMinutesInputId"); | ||||
|       alert("Values=" + hoursInput.value + ":" + minutesInput.value); | ||||
|     } | ||||
|  | ||||
|     function onBodyLoad() { | ||||
|       var button = document.getElementById("button"); | ||||
|       button.onmousedown = function(event) { | ||||
|         event.stopPropagation(); | ||||
|         event.preventDefault(); | ||||
|         button.src = "btn-do.png"; | ||||
|         fwdClock(); | ||||
|         navigator.vibrate(20); | ||||
|       }; | ||||
|       button.onmouseup = function() { | ||||
|         setTimeout(function() { | ||||
|           button.src = "btn.png"; | ||||
|         }, 400); | ||||
|       }; | ||||
|       setTimeout(function() { | ||||
|         getClockStatus(); | ||||
|       }, 400); | ||||
|     }; | ||||
|   </script> | ||||
| </head> | ||||
|  | ||||
| <body id="index" onload="onBodyLoad()"> | ||||
|   <div class="hdr"><img src="fremoei.gif"></div> | ||||
|   <div><label>Clock name: </label><span id="clockNameId"></span></div> | ||||
|   <div><label>Time: </label><span id="displayedTimeId"></span></div> | ||||
|   <div> | ||||
|     <input type="number" min="0" max="23" id="displayedHoursInputId"> | ||||
|     <input type="number" min="0" max="59" id="displayedMinutesInputId"> | ||||
|     <input type="button" onclick="setDisplayedTime"> | ||||
|   </div> | ||||
|   <div class="btn_cnt"><img id="led" src="led-off.png"><a href="#"><img id="button" src="btn.png"></a></div> | ||||
| </body> | ||||
|  | ||||
| </html> | ||||
							
								
								
									
										
											BIN
										
									
								
								src/data/led-off.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/data/led-on.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.5 KiB | 
							
								
								
									
										47
									
								
								src/data/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,47 @@ | ||||
| html { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   -webkit-box-sizing: border-box; | ||||
|   -moz-box-sizing: border-box; | ||||
|   box-sizing: border-box; | ||||
| } | ||||
|  | ||||
| *, *:before, *:after { | ||||
|   -webkit-box-sizing: inherit; | ||||
|   -moz-box-sizing: inherit; | ||||
|   box-sizing: inherit; | ||||
| } | ||||
|  | ||||
| :focus { | ||||
|   outline: none; | ||||
|   outline-style: none; | ||||
|   box-shadow: none; | ||||
|   border-color: transparent; | ||||
| } | ||||
|  | ||||
| body { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   background: #EEE; | ||||
|   font-family: Arial, Helvetica, sans-serif; | ||||
|   font-size: 12px; | ||||
|   text-align: center; | ||||
|   color: #555; | ||||
| } | ||||
|  | ||||
| .hdr { | ||||
|   width: 100%; | ||||
|   padding: 15px 0 15px 0; | ||||
| } | ||||
|  | ||||
| .btn_cnt { | ||||
|   width: 111px; | ||||
|   margin: 0 auto 0 auto; | ||||
|   padding-top: 5%; | ||||
| } | ||||
|  | ||||
| .btn_cnt a { | ||||
|   -webkit-tap-highlight-color: rgba(255, 255, 255, 0); | ||||
| } | ||||
							
								
								
									
										39
									
								
								src/data/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,39 @@ | ||||
| { | ||||
|   "name": "Fastclock Client", | ||||
|   "icons": [{ | ||||
|     "src": "icon-0-75x.png", | ||||
|     "sizes": "36x36", | ||||
|     "type": "image/png", | ||||
|     "density": 0.75 | ||||
|   }, { | ||||
|     "src": "icon-1x.png", | ||||
|     "sizes": "48x48", | ||||
|     "type": "image/png", | ||||
|     "density": 1.0 | ||||
|   }, { | ||||
|     "src": "icon-1-5x.png", | ||||
|     "sizes": "72x72", | ||||
|     "type": "image/png", | ||||
|     "density": 1.5 | ||||
|   }, { | ||||
|     "src": "icon-2x.png", | ||||
|     "sizes": "96x96", | ||||
|     "type": "image/png", | ||||
|     "density": 2.0 | ||||
|   }, { | ||||
|     "src": "icon-3x.png", | ||||
|     "sizes": "144x144", | ||||
|     "type": "image/png", | ||||
|     "density": 3.0 | ||||
|   }, { | ||||
|     "src": "icon-4x.png", | ||||
|     "sizes": "192x192", | ||||
|     "type": "image/png", | ||||
|     "density": 4.0 | ||||
|   }], | ||||
|   "start_url": "index.htm", | ||||
|   "display": "standalone", | ||||
|   "orientation": "portrait", | ||||
|   "theme_color": "dimgray", | ||||
|   "background_color": "black" | ||||
| } | ||||
							
								
								
									
										282
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						| @@ -2,6 +2,8 @@ | ||||
|  */ | ||||
|  | ||||
| #define COMPDATE __DATE__ __TIME__ | ||||
| #define APP_VERSION "0.2.18" | ||||
|  | ||||
| // Button pin on the esp for selecting modes. 0 for Generic devices! | ||||
| #define MODEBUTTON D3 | ||||
| #define RELAY1_PIN D1 | ||||
| @@ -12,8 +14,8 @@ | ||||
| #define VERTICAL_BAR_STARTS_TOP false | ||||
| #define DEBUG_RELAYS false | ||||
| #define DEBUG_DISPLAY false | ||||
| #define STARTUP1_ANIMATION_DURATION_ms 15000 | ||||
| #define STARTUP2_ANIMATION_DURATION_ms 45000 | ||||
| #define STARTUP1_ANIMATION_DURATION_ms 2000 | ||||
| #define STARTUP2_ANIMATION_DURATION_ms 33000 | ||||
|  | ||||
| #include <Arduino.h> | ||||
| #include <IOTAppStory.h> | ||||
| @@ -24,6 +26,11 @@ | ||||
| #include <ESP8266WiFi.h> | ||||
| #include <WiFiUdp.h> | ||||
| #include "MD_RobotEyes.h" | ||||
| #include <FS.h> | ||||
| #include <ESPAsyncTCP.h>                               // https://github.com/me-no-dev/AsyncTCP | ||||
| #include <ESPAsyncWebServer.h> | ||||
| #include <ESP8266HTTPClient.h> | ||||
| #include "Relays.h" | ||||
|  | ||||
| IOTAppStory IAS(COMPDATE, MODEBUTTON); | ||||
| String deviceName = "wemosMatrixDisplay"; | ||||
| @@ -52,9 +59,11 @@ Your hardware matches the setting for FC-16 modules. Please set FC16_HW. | ||||
| // Arbitrary output pins | ||||
| MD_Parola P = MD_Parola(HARDWARE_TYPE, DISPLAY_DATA_PIN, DISPLAY_CLK_PIN, DISPLAY_CS_PIN, MAX_DEVICES); | ||||
| MD_RobotEyes E; | ||||
| Relays R; | ||||
|  | ||||
| WiFiUDP ntpUDP; | ||||
| NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000); | ||||
| AsyncWebServer server(80); | ||||
|  | ||||
| // Field default values | ||||
| char *clockName = "FREMO"; | ||||
| @@ -64,9 +73,7 @@ char *relay1Pin_String = "D1"; | ||||
| char *relay2Pin_String = "D2"; | ||||
| int relay1Pin = D1, relay2Pin = D2; | ||||
| char *relayHoldTime_ms_String = "200"; | ||||
| unsigned int relayHoldTime_ms = 200; | ||||
| char *relayMinOffTime_ms_String = "100"; | ||||
| unsigned int relayMinOffTime_ms = 100; | ||||
| unsigned int displayRefresh_ms = 200; | ||||
|  | ||||
| // Clock Display Config Parameter | ||||
| @@ -74,7 +81,12 @@ static char * displayClockNameEvery_ms_String = "16000"; | ||||
| static char * displayClockNameDuration_ms_String = "1200"; | ||||
| static uint32_t displayClockNameEvery_ms = 16000; | ||||
| static uint32_t displayClockNameDuration_ms = 1200; | ||||
| static uint32_t doNotShowClockNameBeforeAndAfterMinuteChange_s = 2; | ||||
| static uint16_t doNotShowClockNameBeforeAndAfterMinuteChange_s = 2; | ||||
|  | ||||
| static uint16_t hours, minutes, seconds; | ||||
| static char minuteProgressIndicator; | ||||
| static char timeBuffer[10]; | ||||
|  | ||||
|  | ||||
| void setupIAS(void) { | ||||
|   #if defined  ESP8266 | ||||
| @@ -87,7 +99,7 @@ void setupIAS(void) { | ||||
|   // preset deviceName this is also your MDNS responder: http://deviceName.local | ||||
|   IAS.preSetDeviceName(deviceName); | ||||
|   IAS.preSetAppName(F("Wemos2RelaysMatrixDisplays")); | ||||
|   IAS.preSetAppVersion(F("0.2.1")); | ||||
|   IAS.preSetAppVersion(F(APP_VERSION)); | ||||
|   IAS.preSetAutoUpdate(true); | ||||
|  | ||||
|   // define fields | ||||
| @@ -150,12 +162,12 @@ void setupIAS(void) { | ||||
|   // Call home interval in seconds, use 60s only for development. | ||||
|   // Please change it to at least 2 hours in production | ||||
|   IAS.setCallHomeInterval(120); | ||||
|   //IAS.callHome(false /*SPIFFS-check*/); | ||||
|   IAS.callHome(true /*SPIFFS-check*/); | ||||
|   clockSpeed_modelMsPerRealSec = atoi(clockSpeed_modelMsPerRealSec_String); | ||||
|   relay1Pin = IAS.dPinConv(relay1Pin_String); | ||||
|   relay2Pin = IAS.dPinConv(relay2Pin_String); | ||||
|   relayHoldTime_ms = atoi(relayHoldTime_ms_String); | ||||
|   relayMinOffTime_ms = atoi(relayMinOffTime_ms_String); | ||||
|   R.setHoldTime_ms(atoi(relayHoldTime_ms_String)); | ||||
|   R.setMinOffTime_ms(atoi(relayMinOffTime_ms_String)); | ||||
|   displayClockNameEvery_ms = atoi(displayClockNameEvery_ms_String); | ||||
|   displayClockNameDuration_ms = atoi(displayClockNameDuration_ms_String); | ||||
|  | ||||
| @@ -163,20 +175,13 @@ void setupIAS(void) { | ||||
|   Serial.print(F("Relay1 Pin: ")); Serial.println(relay1Pin); | ||||
|   Serial.print(F("Relay2 Pin: ")); Serial.println(relay2Pin); | ||||
|   Serial.print(F("Clock speed: ")); Serial.print(clockSpeed_modelMsPerRealSec); Serial.println(F(" model ms per real sec")); | ||||
|   Serial.print(F("Relay hold time (ms): ")); Serial.println(relayHoldTime_ms); | ||||
|   Serial.print(F("Relay min off time (ms): ")); Serial.println(relayMinOffTime_ms); | ||||
|   Serial.print(F("Relay hold time (ms): ")); Serial.println(relayHoldTime_ms_String); | ||||
|   Serial.print(F("Relay min off time (ms): ")); Serial.println(relayMinOffTime_ms_String); | ||||
|   Serial.print(F("Clock speed (model ms per real time s): ")); Serial.println(clockSpeed_modelMsPerRealSec); | ||||
|   Serial.print(F("Show clock name every (ms): ")); Serial.println(displayClockNameEvery_ms); | ||||
|   Serial.print(F("Show clock name for (ms): ")); Serial.println(displayClockNameDuration_ms); | ||||
| } | ||||
|  | ||||
| void setupRelays(int relay1Pin, int relay2Pin) { | ||||
|   pinMode(relay1Pin, OUTPUT); | ||||
|   pinMode(relay2Pin, OUTPUT); | ||||
|   digitalWrite(relay1Pin, LOW); | ||||
|   digitalWrite(relay2Pin, LOW); | ||||
| } | ||||
|  | ||||
| static MD_MAX72XX *graphicDisplay = NULL; | ||||
|  | ||||
| void setupDisplay() { | ||||
| @@ -224,15 +229,152 @@ void setupDisplay() { | ||||
|   P.print(intro); | ||||
| } | ||||
|  | ||||
| //called when the url is not defined here return 404 | ||||
| void onRequest(AsyncWebServerRequest *request){ | ||||
|   //Handle Unknown Request | ||||
|   request->send(404); | ||||
| } | ||||
|  | ||||
| void setupFS() { | ||||
|   if(!SPIFFS.begin()){ | ||||
|     Serial.println(F(" SPIFFS Mount Failed")); | ||||
|     return; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void setupWebServer() { | ||||
|   server.on("/fwd", HTTP_GET, [](AsyncWebServerRequest *request){ | ||||
|     Serial.println(F("\n WebApp button pressed -- move clock forward")); | ||||
|  | ||||
|     R.toggle(); | ||||
|  | ||||
|     // create json return | ||||
|     String json = "{"; | ||||
|     json += "\"result\":\"OK\","; | ||||
|     json += "\"clockName\":\"" + String(clockName) + "\","; | ||||
|     json += "\"hours\":\"" + String(hours) + "\","; | ||||
|     json += "\"minutes\":\"" + String(minutes) + "\","; | ||||
|     json += "\"seconds\":\"" + String(seconds) + "\""; | ||||
|     json += "}"; | ||||
|  | ||||
|     // return json to WebApp | ||||
|     request->send(200, F("text/json"), json); | ||||
|     json = String(); | ||||
|   }); | ||||
|  | ||||
|   server.on("/clock", HTTP_GET, [](AsyncWebServerRequest *request){ | ||||
|     // create json return | ||||
|     String json = "{"; | ||||
|     json += "\"clockName\":\""+String(clockName)+"\","; | ||||
|     json += "\"clockSpeed\":\""+String(clockSpeed_modelMsPerRealSec)+"\","; | ||||
|     json += "\"relayHoldTime_ms\":\""+String(R.getHoldTime_ms())+"\","; | ||||
|     json += "\"relayMinOffTime_ms\":\""+String(R.getMinOffTime_ms())+"\","; | ||||
|     json += "\"displayRefresh_ms\":\""+String(displayRefresh_ms)+"\","; | ||||
|     json += "\"displayClockNameEvery_ms\":\""+String(displayClockNameEvery_ms)+"\","; | ||||
|     json += "\"displayClockNameDuration_ms\":\""+String(displayClockNameDuration_ms)+"\","; | ||||
|     json += "\"doNotShowClockNameBeforeAndAfterMinuteChange_s\":\""+String(doNotShowClockNameBeforeAndAfterMinuteChange_s)+"\","; | ||||
|     json += "\"real_hours\":\""+String(hours)+"\","; | ||||
|     json += "\"real_minutes\":\""+String(minutes)+"\","; | ||||
|     json += "\"real_seconds\":\""+String(seconds)+"\","; | ||||
|     json += "\"model_hours\":\""+String(hours)+"\","; | ||||
|     json += "\"model_minutes\":\""+String(minutes)+"\","; | ||||
|     json += "\"model_seconds\":\""+String(seconds)+"\""; | ||||
|     json += "}"; | ||||
|  | ||||
|     // return json to WebApp | ||||
|     request->send(200, F("text/json"), json); | ||||
|     json = String(); | ||||
|   }); | ||||
|  | ||||
|   server.on("/setDT", HTTP_GET, [](AsyncWebServerRequest *request){ | ||||
|     String h, m, message; | ||||
|     Serial.println(F("\n Setting displayed time of clock")); | ||||
|  | ||||
|     message = ""; | ||||
|     if (request->hasParam("h")) { | ||||
|         h = request->getParam("h")->value(); | ||||
|     } else { | ||||
|         message += "Parameter h for hours missing. "; | ||||
|     } | ||||
|     if (request->hasParam("m")) { | ||||
|         m = request->getParam("m")->value(); | ||||
|     } else { | ||||
|         message += "Parameter m for minutes missing. "; | ||||
|     } | ||||
|  | ||||
|     R.setDisplayedTime(h.toInt(), m.toInt()); | ||||
|     h = String(); | ||||
|     m = String(); | ||||
|  | ||||
|     // create json return | ||||
|     String json = "{"; | ||||
|     if (message.length() > 0) { | ||||
|       json += "\"result\":\"Error\","; | ||||
|       json += "\"message\": \"" + message + "\""; | ||||
|     } else { | ||||
|       json += "\"result\":\"OK\""; | ||||
|     } | ||||
|     json += "}"; | ||||
|  | ||||
|     // return json to WebApp | ||||
|     request->send(200, F("text/json"), json); | ||||
|     json = String(); | ||||
|     R.fwdToTime(hours, minutes); | ||||
|   }); | ||||
|  | ||||
|   server.on("/files", HTTP_GET, [](AsyncWebServerRequest *request){ | ||||
|     Serial.println(F("\n Directory of FS requested")); | ||||
|     FSInfo fs_info; | ||||
|     String message = ""; | ||||
|  | ||||
|     if (!SPIFFS.info(fs_info)) { | ||||
|       message += "Cannot get info about file system! "; | ||||
|     } | ||||
|  | ||||
|     // create json return | ||||
|     String json = "{"; | ||||
|     if (message.length() > 0) { | ||||
|       json += "\"result\":\"Error\","; | ||||
|       json += "\"message\":\"" + message + "\""; | ||||
|     } else { | ||||
|       json += "\"result\":\"OK\","; | ||||
|       json += "\"files\":\"["; | ||||
|       Dir dir = SPIFFS.openDir("/"); | ||||
|       boolean isFirstEntry = true; | ||||
|       while (dir.next()) { | ||||
|         if (isFirstEntry) { isFirstEntry = false; } else { json += ","; } | ||||
|         json += "{\"filename\":\"" + dir.fileName() + "\",\"size\":" + dir.fileSize() + "}"; | ||||
|       } | ||||
|       json += "]"; | ||||
|     } | ||||
|     json += "}"; | ||||
|  | ||||
|     // return json to WebApp | ||||
|     request->send(200, F("text/json"), json); | ||||
|     json = String(); | ||||
|   }); | ||||
|  | ||||
|  | ||||
|   server.serveStatic("/", SPIFFS, "/"); | ||||
|   server.onNotFound(onRequest); | ||||
|  | ||||
|   // start the HTTP server | ||||
|   server.begin(); | ||||
|   Serial.print(F("HTTP server started at: ")); | ||||
|   Serial.println(WiFi.localIP()); | ||||
|   Serial.println(""); | ||||
| } | ||||
|  | ||||
|  | ||||
| void setup(void) | ||||
| { | ||||
|   Serial.println(F("setup():")); | ||||
|   setupDisplay(); | ||||
|   setupFS(); | ||||
|   setupIAS(); | ||||
|   setupWebServer(); | ||||
|   delay(200); | ||||
|   setupRelays(relay1Pin, relay2Pin); | ||||
|  | ||||
|   R.begin(relay1Pin, relay2Pin); | ||||
|   timeClient.begin(); | ||||
|   Serial.println(F("setup() finished")); | ||||
| } | ||||
| @@ -242,51 +384,6 @@ static bool timeClientInitialized = false; | ||||
| static unsigned long lastTimeOutput_ms = 0; | ||||
| #define TIME_BETWEEN_REALTIME_UPDATE_ms 60000 | ||||
|  | ||||
| static unsigned long last_relay_off_ts=0, last_relay_hold_ts=0; | ||||
| enum RelayState { RELAY_STATE_OFF=0, RELAY_STATE_ON_EVEN_MINUTE, RELAY_STATE_ON_ODD_MINUTE }; | ||||
| static RelayState relaysState = RELAY_STATE_OFF; | ||||
| static RelayState lastRelayOnState = RELAY_STATE_ON_EVEN_MINUTE; | ||||
| static bool relayCanSwitch=true; | ||||
|  | ||||
| void toggleRelays() { | ||||
|   if (relayCanSwitch) { | ||||
|     if (lastRelayOnState == RELAY_STATE_ON_EVEN_MINUTE) { | ||||
|       digitalWrite(relay1Pin, HIGH); | ||||
|       digitalWrite(relay2Pin, LOW); | ||||
|       relaysState = RELAY_STATE_ON_ODD_MINUTE; | ||||
|       // P.print("R-OEv"); | ||||
|     } else { | ||||
|       digitalWrite(relay1Pin, LOW); | ||||
|       digitalWrite(relay2Pin, HIGH); | ||||
|       relaysState = RELAY_STATE_ON_EVEN_MINUTE; | ||||
|       // P.print("R-OOd"); | ||||
|     } | ||||
|     lastRelayOnState = relaysState; | ||||
|   } // else P.print("R-OErr"); | ||||
|   relayCanSwitch = false; | ||||
|   last_relay_hold_ts = millis(); | ||||
|   Serial.println(F("Toggle Relays")); | ||||
| } | ||||
|  | ||||
| void relaysOff(void) { | ||||
|   digitalWrite(relay1Pin, LOW); | ||||
|   digitalWrite(relay2Pin, LOW); | ||||
|   last_relay_off_ts = millis(); | ||||
|   relaysState = RELAY_STATE_OFF; | ||||
|   // P.print("R-Off"); | ||||
| } | ||||
|  | ||||
| void loopRelays(void) { | ||||
|   if (relaysState == RELAY_STATE_OFF) { | ||||
|     if (millis() - last_relay_off_ts > relayMinOffTime_ms) { | ||||
|       relayCanSwitch = true; | ||||
|     } | ||||
|   } else { | ||||
|     if (millis() - last_relay_hold_ts > relayHoldTime_ms) { | ||||
|       relaysOff(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| typedef struct | ||||
| { | ||||
| @@ -330,6 +427,7 @@ void loopStartupAnimation() { | ||||
|       index++; | ||||
|       if (index >= ARRAY_SIZE(eSeq)) | ||||
|         index = 0; | ||||
|       P.displayClear(); | ||||
|       #if DISPLAY_ANIM_NAME | ||||
|         E.setText(eSeq[index].name); | ||||
|       #endif | ||||
| @@ -363,14 +461,26 @@ void loopStartupAnimation() { | ||||
|   } | ||||
| } | ||||
|  | ||||
| void reInitializeDisplay() { | ||||
|   static unsigned long last_reinit_ts = 0; | ||||
|   #define REINIT_AFTER_ms 5000 | ||||
|   #define AVOID_REINIT_BEFORE_AND_AFTER_FULLMINUTE_FOR_s 3 | ||||
|  | ||||
|   if (last_reinit_ts == 0) last_reinit_ts = millis(); | ||||
|  | ||||
|   if (millis() - last_reinit_ts > REINIT_AFTER_ms | ||||
|       && seconds < 60 - AVOID_REINIT_BEFORE_AND_AFTER_FULLMINUTE_FOR_s | ||||
|       && seconds > AVOID_REINIT_BEFORE_AND_AFTER_FULLMINUTE_FOR_s) { | ||||
|     P.begin(); | ||||
|     last_reinit_ts = millis(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void loop(void) | ||||
| { | ||||
|   int currentDisplayState; | ||||
|   int hours, minutes, seconds; | ||||
|   char minuteProgressIndicator; | ||||
|   static int lastMinutes = 0; | ||||
|   static int lastSeconds = 0; | ||||
|   static char timeBuffer[10]; | ||||
|   #define MsgSize 10 | ||||
|   static char debugMsg[MsgSize+1]; | ||||
|   static int recentDisplayState = -1; | ||||
| @@ -415,32 +525,6 @@ void loop(void) | ||||
|   if (minuteProgressIndicator > 9) minuteProgressIndicator = 9; | ||||
|   snprintf(timeBuffer, 10, "%c %2d:%02d", minuteProgressIndicator, hours, minutes); | ||||
|  | ||||
|   //P.displayAnimate(); | ||||
|   //P.displayClear(); | ||||
|   /* DEBUG */ | ||||
|   #if DEBUG_RELAYS | ||||
|   if (seconds != lastSeconds) { | ||||
|     switch (seconds % 4) { | ||||
|       case 0: | ||||
|         digitalWrite(relay1Pin, HIGH); | ||||
|         break; | ||||
|       case 1: | ||||
|         digitalWrite(relay1Pin, LOW); | ||||
|         break; | ||||
|       case 2: | ||||
|         digitalWrite(relay2Pin, HIGH); | ||||
|         break; | ||||
|       case 3: | ||||
|         digitalWrite(relay2Pin, LOW); | ||||
|         break; | ||||
|     } | ||||
|     Serial.print("Rel dbg: "); Serial.println(seconds, HEX); | ||||
|     delay(5); | ||||
|   } | ||||
|   #endif | ||||
|   /* END DEBUG */ | ||||
|  | ||||
|  | ||||
|   // standard procedure to display | ||||
|   static uint32_t last_clock_refresh = 0; | ||||
|   static uint32_t lastTimeClockNameShown = 0; | ||||
| @@ -455,7 +539,8 @@ void loop(void) | ||||
|     if ((millis() - lastTimeClockNameShown > displayClockNameEvery_ms) | ||||
|         && (seconds < 60-doNotShowClockNameBeforeAndAfterMinuteChange_s) | ||||
|         && (seconds > doNotShowClockNameBeforeAndAfterMinuteChange_s)) { | ||||
|       P.begin(); // re-initialize, that fixes display problems due to electrical relais feedbacks | ||||
|       //P.begin(); // re-initialize, that fixes display problems due to electrical relais feedbacks | ||||
|       reInitializeDisplay(); | ||||
|       P.setIntensity(2); | ||||
|       P.print(clockName); | ||||
|       lastTimeClockNameShown = millis(); | ||||
| @@ -463,7 +548,8 @@ void loop(void) | ||||
|     } else { | ||||
|       // showing clock | ||||
|       if (millis() - last_clock_refresh > displayRefresh_ms) { | ||||
|         //P.displayClear(); | ||||
|         // P.begin(); // re-initialize, that fixes display problems due to electrical relais feedbacks | ||||
|         reInitializeDisplay(); | ||||
|         P.setIntensity(1); | ||||
|         P.print(timeBuffer); | ||||
|         last_clock_refresh = millis(); | ||||
| @@ -473,10 +559,10 @@ void loop(void) | ||||
|  | ||||
|   // toggle relays | ||||
|   if (lastMinutes != minutes) { | ||||
|     toggleRelays(); | ||||
|     R.toggle(); | ||||
|     lastMinutes = minutes; | ||||
|   } | ||||
|   lastSeconds = seconds; | ||||
|  | ||||
|   loopRelays(); | ||||
|   R.loop(); | ||||
| } | ||||
|   | ||||