This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Pend Clock Tuner V100SP | |
// (c) 2018 Kiraku Labo | |
// ESP8266 | |
// IO 0, 2, 15 for serial flash | |
// IO 14, 12 for I2C SDA & SCL | |
// IO 13 for LED, 4 for sensor, 5 for servo. | |
// LCD AQM00802A-FLW-GBW | |
#include <ESP8266WiFi.h> | |
#include <WiFiClient.h> | |
#include <ESP8266mDNS.h> | |
#include <WiFiUdp.h> | |
#include <ESP8266WebServer.h> | |
#include <ArduinoOTA.h> | |
#include <TimeLib.h> | |
#include <Wire.h> | |
#include <ST7032.h> //modified for ESP8266 | |
extern "C" { | |
#include "user_interface.h" | |
} | |
//Location dependent | |
#define TZH 9 //Japan +9H | |
//#define TZH 2 //EU Summer +2H | |
//Network dependent | |
#define STATIC_STA_IP true | |
#define WEB_PORT 80 | |
IPAddress staIP(192,168,0,112); | |
IPAddress staGateway(192,168,0,1); | |
IPAddress apIP(192,168,4,112); | |
IPAddress subnet(255,255,255,0); | |
IPAddress timeServerIP(147,156,7,18); //pool.ntp.org | |
//IPAddress timeServerIP(210,173,160,87);//ntp.jst.mfeed.ad.jp | |
const char* ssid = "XXXXXXXX"; //SSID of the site | |
const char* pass = "XXXXXXXX"; //Pass code of the site | |
const char* apSsid= "XXXXXXXX"; //SSID of this device | |
const char* apPass = "XXXXXXXX"; //Pass code of this device | |
//Clock mechanism dependent | |
//#define PEND_CYCLE 130 //2600cycles/49min=130c/147sec determined by gear teeth count. | |
//#define PEND_DUR 147 | |
#define PEND_CYCLE 7667 //PEND_CYCLE pendulum cycles per PEND_DUR seconds | |
#define PEND_DUR 7350 | |
#define TRIG_WINDOW_SPAN 2 //Window in seconds to measure pendulum period after 0th second of every minute. | |
#define CATCH_UP_THRESH 5 //If difference is greater than this value, coil is driven in full power. | |
#define PRECISE_THRESH 2 //In Catchup mode, when the error become within this second, catchup mode ends. | |
#define ERROR_LIMIT -3600 //If error (in sec) becomes greater than this value, coil power is cut | |
#define PEND_STOP_DET 1500 //msec | |
#define MIN_TRIG_HIGH_INTERVAL 500 //msec | |
#define MIN_TRIG_LOW_INTERVAL 20 //msec | |
#define ADJ_INITIAL_LEVEL -3 | |
//Tuner hardware dependent | |
#define COIL_ADDR 0x64 | |
#define LED_PIN 13 | |
#define SENS_PIN 4 | |
#define PEND_LIFT_FACTOR 3 //ADJ_MIN derating 1/3 | |
#define ADJ_MAX 63 | |
#define ADJ_MIN -63 | |
int16_t kP=400, kI=50; | |
//Sketch version | |
const String ver="v100S"; | |
const String plat="Pend"; | |
//Common | |
#define N_HISTORY 900 //=15H | |
#define MIN_HEAP 14000 | |
#define NUM_GRAPH_SPAN 480 //points = 8H | |
#define NUM_WEB_DUMP 900 | |
#define NTP_INTERVAL 300 //sec = 5min | |
#define NTP_PACKET_SIZE 48 //NTP time stamp is in the first 48 bytes | |
#define SEVENTY_YEARS 2208988800UL | |
WiFiUDP udp; | |
ST7032 lcd; | |
ESP8266WebServer server(WEB_PORT); | |
const String textHtml="text/html"; | |
boolean serialMon=false; | |
uint32_t ntpLastSynch=0; | |
byte packetBuffer[NTP_PACKET_SIZE]; | |
char strBuf[100]; | |
int16_t errorHistory[N_HISTORY]; | |
int16_t adjHistory[N_HISTORY]; | |
time_t tHistory[N_HISTORY]; | |
int32_t ckPendError=0, ckPendErrorMax=0, ckPendErrorMin=0, ckPendErrorLast=0; | |
time_t tNow, t0MinMax=0, tLast=0, tPend=0; | |
int16_t adjLevel=0, adjLevelMax=0, adjLevelMin=0; | |
float adjLevelAve=(float)ADJ_INITIAL_LEVEL; | |
int32_t numHistory=-1; | |
boolean ledColon=true, adjOn=true; | |
boolean catchupMode=false; | |
boolean wifiOk=false; | |
boolean timeSynched=false, setRef=false; | |
int16_t timeBuf[6]; | |
volatile boolean refSet=false, pendStopped=true; | |
volatile int32_t ckPend=0, tRef0, countPend=0; | |
volatile boolean pendTrig=false, trigWindow=false; | |
volatile int16_t pendDir=0; | |
volatile uint32_t msecSensLast=0, msecSens=0, msecSensDur=0; | |
const String msg1 = "<html><head><meta charset='UTF-8'><title>Kiraku Labo</title>\ | |
<script src='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.min.js'></script>\ | |
<style>html, body{width:95%;margin:0 auto;padding:30;background-color:#dddddd;text-align:center;font-size:30px;}</style>\ | |
</head><body><h2>Kiraku Labo " + plat + " " + ver + "</h2>\ | |
<form action='PC' style='display:inline'><input type='submit' value='Adj' style='font-size:60px'></form>\ | |
<form action='PD' style='display:inline'><input type='submit' value='Slow' style='font-size:60px'></form>\ | |
<form action='PZ' style='display:inline'><input type='submit' value='Zero' style='font-size:60px'></form>\ | |
<form action='PU' style='display:inline'><input type='submit' value='Fast' style='font-size:60px'></form><br>\ | |
<form action='KPD' style='display:inline'><input type='submit' value='KP dn' style='font-size:60px'></form>\ | |
<form action='KPU' style='display:inline'><input type='submit' value='KP up' style='font-size:60px'></form>\ | |
<form action='KID' style='display:inline'><input type='submit' value='KI dn' style='font-size:60px'></form>\ | |
<form action='KIU' style='display:inline'><input type='submit' value='KI up' style='font-size:60px'></form><br>\ | |
<form action='M0' style='display:inline'><input type='submit' value=' 0M ' style='font-size:60px'></form>\ | |
<form action='H0' style='display:inline'><input type='submit' value=' 0H ' style='font-size:60px'></form>\ | |
<form action='EZ' style='display:inline'><input type='submit' value='Zero Err' style='font-size:60px'></form>\ | |
<form action='MR' style='display:inline'><input type='submit' value='Rst M/M' style='font-size:60px'></form><br>\ | |
----------------------------------<br>\ | |
<form action='ST' style='display:inline'><input type='submit' value='Status' style='font-size:60px'></form>\ | |
<form action='HE' style='display:inline'><input type='submit' value='Err H.' style='font-size:60px'></form>\ | |
<form action='HP' style='display:inline'><input type='submit' value='Pwr H.' style='font-size:60px'></form>\ | |
<form action='GR' style='display:inline'><input type='submit' value='Graph' style='font-size:60px'></form>"; | |
const String msg2 = "</body></html>"; | |
void setup() { | |
pinMode(LED_PIN, OUTPUT); | |
Serial.begin(115200); | |
Wire.begin(14, 12); //14:SDA, 12:SCL | |
Wire.setClock(50000); | |
lcd.begin(8,2); //(c,r) | |
delay(1000); | |
lcd.setContrast(25); | |
lcd.display(); | |
lcdDispText(0,0, "PCT " + ver); | |
delay(3000); | |
lcdDispText(0,0, "COIL CHK"); | |
checkCoil(); | |
drvCoil(ADJ_INITIAL_LEVEL); | |
adjLevel=ADJ_INITIAL_LEVEL; | |
adjLevelMin=ADJ_INITIAL_LEVEL; | |
adjLevelMax=ADJ_INITIAL_LEVEL; | |
setupWifi(); | |
delay(1000); | |
setupServer(); | |
setupOta(); | |
attachInterrupt(SENS_PIN, sensInt, CHANGE); | |
} | |
void sensInt() { | |
int16_t sns=digitalRead(SENS_PIN); | |
if (pendDir==0 && sns==HIGH && millis()-msecSens>MIN_TRIG_HIGH_INTERVAL) { | |
pendDir=1; | |
digitalWrite(LED_PIN, HIGH); | |
pendTrig=true; | |
pendStopped=false; | |
msecSens=millis(); | |
if (refSet) { | |
countPend++; | |
ckPend=(int32_t)((int64_t)countPend * (int64_t)PEND_DUR / (int64_t)PEND_CYCLE); | |
if (trigWindow) { | |
msecSensDur=msecSens-msecSensLast; | |
msecSensLast=msecSens; | |
} | |
} | |
} | |
else if (pendDir==1 && sns==LOW && millis()-msecSens>MIN_TRIG_LOW_INTERVAL) { | |
pendDir=0; | |
digitalWrite(LED_PIN, LOW); | |
msecSens=millis(); | |
} | |
} | |
void loop() { | |
uint32_t msec=millis(); | |
tNow=now(); | |
if (millis()-msecSens>PEND_STOP_DET) pendStopped=true; | |
if (pendTrig) { | |
pendTrig=false; | |
if (refSet) updateError(); | |
if (trigWindow) { | |
if (setRef) { | |
tRef0=tNow; | |
t0MinMax=tNow; | |
msecSensLast=msec; | |
refSet=true; | |
setRef=false; | |
} | |
trigWindow=false; | |
if (refSet) tune(); | |
} | |
} | |
if (tNow!=tLast) { //every 1 sec | |
tLast=tNow; | |
if (second(tNow)==30 && wifiOk && tNow>=ntpLastSynch+NTP_INTERVAL) synchTime(); | |
else { | |
lcdDispTime(0,0, tNow); | |
if (pendStopped && refSet) updateError(); | |
if (second(tNow)==0) trigWindow=true; | |
else if (second(tNow)==TRIG_WINDOW_SPAN) { | |
if (trigWindow) { | |
trigWindow=false; | |
if (refSet) { | |
updateError(); | |
tune(); | |
} | |
} | |
} | |
} | |
} | |
server.handleClient(); | |
ArduinoOTA.handle(); | |
yield(); | |
} | |
void synchTime() { | |
ledColon=false; | |
lcdDispText(0,0, "Get NTP "); | |
time_t ntpTime=setNTP(); | |
if (ntpTime>0) { | |
tNow=ntpTime; | |
tLast=ntpTime; | |
ntpLastSynch=ntpTime; | |
ledColon=true; | |
lcdDispText(4,0, F(" OK ")); | |
if (timeSynched==false) setRef=true; | |
timeSynched=true; | |
} | |
} | |
void tune() { | |
int32_t countPendBuf=countPend; //countPend could change by interrupt | |
numHistory++; | |
int16_t idx=numHistory % N_HISTORY; | |
lcdDispNum5(0,1,ckPendError); | |
if (adjOn) adjPend(); | |
ckPendErrorLast=ckPendError; | |
lcdDispNum3n(5,1,adjLevel); | |
if (ckPendError<-32767) errorHistory[idx]=-32678; | |
else if (ckPendError<32767) errorHistory[idx]=(int16_t)ckPendError; | |
else errorHistory[idx]=32767; | |
if (numHistory<(N_HISTORY-1)) adjLevelAve = (adjLevelAve * (float)numHistory + (float)adjLevel ) / (float)(numHistory+1); //history is not full | |
else adjLevelAve += (float)(adjLevel-adjHistory[idx])/(float)N_HISTORY; //history is full | |
adjHistory[idx]=adjLevel; | |
tHistory[idx]=tNow; | |
sprintf(strBuf, "SYS:%02d/%02d %02d:%02d:%02d Cnt=%d Hst=%d Err=%d Adj=%d AdjAve=%d.%02d PND:msDur=%d",month(tNow), day(tNow), hour(tNow), minute(tNow), second(tNow),\ | |
countPendBuf, numHistory, ckPendError, adjLevel, (int)adjLevelAve, abs((int)(adjLevelAve*100)%100), msecSensDur); | |
dbgPrintln(strBuf); | |
} | |
void updateError() { | |
ckPendError=ckPend-(int32_t)(tNow-tRef0); | |
if (abs(ckPendError)>30*24*3600) resetRef(); //more than 30 day difference (in case of NTP connection after while) | |
if (ckPendError>ckPendErrorMax) ckPendErrorMax=ckPendError; | |
if (ckPendError<ckPendErrorMin) ckPendErrorMin=ckPendError; | |
lcdDispNum5(0,1,ckPendError); | |
} | |
void resetRef() { | |
countPend=0; | |
ckPendError=0; | |
ckPendErrorLast=0; | |
tRef0=now(); | |
t0MinMax=tRef0; | |
adjLevelMax=adjLevel; | |
adjLevelMin=adjLevel; | |
ckPendErrorMax=ckPendError; | |
ckPendErrorMin=ckPendError; | |
refSet=true; | |
} | |
void adjPend() { | |
if (abs(ckPendError)>CATCH_UP_THRESH) catchupMode=true; | |
if (catchupMode) { | |
if (ckPendError>PRECISE_THRESH) adjLevel=ADJ_MIN/PEND_LIFT_FACTOR; | |
else if (ckPendError<-PRECISE_THRESH) adjLevel=ADJ_MAX; | |
else { | |
catchupMode=false; | |
adjLevel=(int16_t)adjLevelAve; | |
} | |
} | |
else { | |
int16_t adjLevelDelta = (int16_t) ((ckPendError-ckPendErrorLast)*kP/100 + ckPendError*kI/100); | |
adjLevel = constrain(adjLevel-adjLevelDelta, ADJ_MIN/PEND_LIFT_FACTOR, ADJ_MAX); | |
} | |
if (ckPendError<ERROR_LIMIT) adjLevel=0; | |
drvCoil(adjLevel); | |
if (adjLevel>adjLevelMax) adjLevelMax=adjLevel; | |
if (adjLevel<adjLevelMin) adjLevelMin=adjLevel; | |
} | |
void drvCoil(int16_t pwr) { | |
byte polarity; | |
byte st; | |
int ipower; | |
if (pwr < 0) polarity = 1; | |
else polarity =2; | |
ipower=(byte) abs(pwr); | |
if (ipower == 0) polarity=0; //high z | |
ipower = constrain (ipower, 0, 0x3F); | |
st = read8830(COIL_ADDR); | |
if (st & 1) { | |
write8830(COIL_ADDR, 0, 0); | |
// Serial.print(adr);Serial.print(": ");Serial.println(st); | |
} | |
write8830(COIL_ADDR, ipower, polarity); | |
} | |
void write8830(byte adr, byte pwm, byte ctrl) { | |
Wire.beginTransmission(adr); | |
Wire.write(0); | |
byte drv=pwm*4+ctrl; | |
Wire.write(drv); | |
Wire.endTransmission(true); | |
} | |
int read8830(byte adr) { | |
Wire.beginTransmission(adr); | |
Wire.write(1); | |
Wire.endTransmission(false); | |
Wire.requestFrom((int)adr, (int)1, (int)1); | |
return Wire.read(); | |
} | |
void checkCoil() { | |
for (int i=0; i<2; i++) { | |
drvCoil(30); | |
delay(5); | |
int ad=analogRead(A0); | |
if (ad>100 && ad<400) digitalWrite(13, HIGH); | |
else digitalWrite(13, LOW); | |
delay(150); | |
digitalWrite(13, LOW); | |
delay(150); | |
drvCoil(-30); | |
delay(5); | |
ad=analogRead(A0); | |
if (ad>300 && ad<700) digitalWrite(13, HIGH); | |
else digitalWrite(13, LOW); | |
delay(150); | |
digitalWrite(13, LOW); | |
delay(150); | |
} | |
} | |
void dbgPrint(String s) { | |
if (serialMon) Serial.print(s); | |
} | |
void dbgPrintln(String s) { | |
dbgPrint(s+F("\r\n")); | |
} | |
time_t setNTP() { | |
time_t tNtp=getNTPTime(); | |
if (tNtp>0) { | |
setTime(tNtp); | |
sprintf(strBuf, "NTP:%02d/%02d %02d:%02d:%02d ", month(tNtp), day(tNtp), hour(tNtp), minute(tNtp), second(tNtp)); | |
dbgPrintln(strBuf); | |
return tNtp; | |
} | |
else { | |
tNow=now(); | |
sprintf(strBuf, "NTP FAILED:%02d:%02d:%02d ", hour(tNow), minute(tNow), second(tNow)); | |
dbgPrintln(strBuf); | |
return 0; | |
} | |
} | |
time_t getNTPTime() { | |
int16_t pktSize; | |
sendNTPpacket(timeServerIP); // send an NTP packet to a time server | |
uint32_t usec=micros(); | |
do { | |
delay(10); | |
pktSize = udp.parsePacket(); | |
} while (pktSize==0 && (micros()-usec)<2000000); | |
if (pktSize) { | |
udp.read(packetBuffer, NTP_PACKET_SIZE); | |
uint32_t secsSince1900 = packetBuffer[40]<<24 | packetBuffer[41]<<16 | packetBuffer[42]<<8 | packetBuffer[43]; | |
uint32_t epoch = secsSince1900 - SEVENTY_YEARS; //Unix time: | |
return epoch + (TZH * 3600); //TZH=time zone offset | |
} | |
else return 0; | |
} | |
void sendNTPpacket(IPAddress &address){ | |
memset(packetBuffer, 0, NTP_PACKET_SIZE);// set all bytes in the buffer to 0 | |
packetBuffer[0] = 0b11100011; // LI, Version, Mode | |
packetBuffer[1] = 0; // Stratum, or type of clock | |
packetBuffer[2] = 6; // Polling Interval | |
packetBuffer[3] = 0xEC; // Peer Clock Precision | |
packetBuffer[12] = 49; // 8 bytes of zero for Root Delay & Root Dispersion | |
packetBuffer[13] = 0x4E; | |
packetBuffer[14] = 49; | |
packetBuffer[15] = 52; | |
udp.beginPacket(address, 123); //NTP requests are to port 123 | |
udp.write(packetBuffer, NTP_PACKET_SIZE); | |
udp.endPacket(); | |
} | |
void lcdDispText(int16_t col, int16_t row, String s) { | |
lcd.setCursor(col,row); | |
lcd.print(s); | |
} | |
void lcdDispTime(int16_t col, int16_t row, time_t t) { | |
char tm[9]; | |
sprintf(tm, "%02d", hour(t)); | |
sprintf(&tm[2], ledColon?":":" "); | |
sprintf(&tm[3], "%02d", minute(t)); | |
sprintf(&tm[5], ledColon?":":" "); | |
sprintf(&tm[6], "%02d", second(t)); | |
lcd.setCursor(col,row); | |
lcd.print(tm); | |
} | |
void lcdDispNum5(int16_t col, int16_t row, int32_t n) { | |
char pNum[6]; | |
if (n>=10000000) sprintf(pNum, "%s", ">1E7 "); | |
else if (n>=1000000) sprintf(pNum, "%03dE4", n/10000); | |
else if (n>=100000) sprintf(pNum, "%03dE3", n/1000); | |
else if (n>=-9999) sprintf(pNum, "%05d", n); | |
else if (n>=-99999) sprintf(pNum, "%2dE3", n/1000); | |
else if (n>=-999999) sprintf(pNum, "%2dE4", n/10000); | |
else if (n>=-9999999) sprintf(pNum, "%2dE5", n/100000); | |
else sprintf(pNum, "%s", "<-E7 "); | |
lcd.setCursor(col,row); | |
lcd.print(pNum); | |
} | |
void lcdDispNum3n(int16_t col, int16_t row, int32_t n) { | |
char pNum[4]; | |
sprintf(pNum, "%3d", n); | |
lcd.setCursor(col,row); | |
lcd.print(pNum); | |
} | |
void handleRoot() { | |
server.send (200, textHtml, msg1+msg2); | |
} | |
void handleNotFound(){ | |
server.send(404, F("text/plain"), F("File Not Found\n\n")); | |
} | |
void dispStatus() { | |
time_t t=now(); | |
server.send (200, textHtml, msg1 + | |
"<h3>KP=" + String(kP) + " KI=" + String(kI) + | |
"<br>AdjOn=" + adjOn + " Catch up=" + catchupMode + | |
"<br>Time Now=" + String(month(t)) + "/" + String(day(t)) + " " + String(hour(t)) + ":" + preZero(String(minute(t))) + | |
":" + preZero(String(second(t))) + | |
"<br>Time NTP=" + String(month(ntpLastSynch)) + "/" + String(day(ntpLastSynch)) + " " + String(hour(ntpLastSynch)) + | |
":" + preZero(String(minute(ntpLastSynch))) + ":" + preZero(String(second(ntpLastSynch))) + | |
"<br>Time Ref=" + String(month(tRef0)) + "/" + String(day(tRef0)) + " " + String(hour(tRef0)) + ":" + preZero(String(minute(tRef0))) + | |
":" + preZero(String(second(tRef0))) + | |
"<br>Pend Ct=" + String(countPend) + " Hist=" + String(numHistory) + | |
"<br>M/M Reset " + String(month(t0MinMax)) + "/" + String(day(t0MinMax)) + " " + String(hour(t0MinMax)) + | |
":" + preZero(String(minute(t0MinMax))) + ":" + preZero(String(second(t0MinMax))) + | |
"<br>Error=" + String(ckPendError) + " [" + String(ckPendErrorMin) + ", " + String(ckPendErrorMax) + "]" + | |
"<br>Adj=" + String(adjLevel) + " [" + String(adjLevelMin) + ", " + String(adjLevelMax) + "]" + | |
"<br>Adj Average=" + String(adjLevelAve) + | |
"<br>Free Heap=" + String(system_get_free_heap_size()) + " bytes" + | |
"</h3>" + msg2); | |
} | |
void dispErrorHist() { | |
dispHist(1); | |
} | |
void dispAdjHist() { | |
dispHist(2); | |
} | |
void dispHist(byte n) { | |
if (numHistory<0) server.send (200, textHtml, msg1 + F("<h2>No History</h2>") + msg2); | |
else { | |
time_t t=now(); | |
String st = (n==1?"Error History ":"Adj History ") + String(month(t)) + "/" + String(day(t)) + " " + String(hour(t)) + ":" + preZero(String(minute(t))) + "<br>"; | |
int16_t count=0; | |
for (int32_t h=numHistory; h>numHistory-NUM_WEB_DUMP; h--) { | |
if (count%5==0) st += "-" + preZero(String(count)) + "m: "; | |
st += String(n==1?errorHistory[h%N_HISTORY]:adjHistory[h%N_HISTORY]) + ", "; | |
if (count%5==4) st += "<br>"; | |
if (h==0 || system_get_free_heap_size()<MIN_HEAP) break; | |
count++; | |
} | |
String sz=(n==2?("<br>Adj Average=" + String(adjLevelAve)):"") + | |
"<br>Content length = " + String(st.length()) + " bytes" + | |
"<br>Free Heap=" + String(system_get_free_heap_size()) + " bytes"; | |
server.send (200, textHtml, msg1 + F("<h3>") + st + sz + F("</h3>") + msg2); | |
} | |
} | |
void hand0Hour() { | |
time_t t=now(); | |
if (minute(t)>40) ckPendError=(60-minute(t))*60-second(t); //Clock hand ahead | |
else ckPendError=-(minute(t)*60+second(t)); //Clock hand behind | |
countPend=(t-tRef0+ckPendError)*PEND_CYCLE/PEND_DUR; | |
server.send (200, textHtml, msg1 + F("<h2>Error set to ") + String(ckPendError) + "<br>countPend set to " + String(countPend) + F("</h2>") + msg2); | |
} | |
void hand0Min() { | |
time_t t=now(); | |
if (second(t)>40) ckPendError=60-second(t); //Clock hand ahead | |
else ckPendError=-second(t); //Clock hand behind | |
countPend=(t-tRef0+ckPendError)*PEND_CYCLE/PEND_DUR; | |
server.send (200, textHtml, msg1 + F("<h2>Error set to ") + String(ckPendError) + "<br>countPend set to " + String(countPend) + F("</h2>") + msg2); | |
} | |
void zeroError() { | |
resetRef(); | |
server.send (200, textHtml, msg1 + F("<h2>Reset Ref, Error set to 0</h2>") + msg2); | |
} | |
void dispAdj() { | |
lcdDispNum3n(5,1,adjLevel); | |
server.send (200, textHtml, msg1 + F("<h2>Adj= ") + String(adjLevel) + | |
" [" + String(adjLevelMin) + ", " + String(adjLevelMax) + F("]</h2>") + msg2); | |
} | |
void dispGraph() { | |
int32_t hStart=numHistory-NUM_GRAPH_SPAN; | |
if (hStart<0) hStart=0; | |
time_t tSt=(tHistory[hStart%N_HISTORY]/60)*60; | |
String outData = "[0,0,0"; | |
String errData = "[5,-5,0"; | |
String minLabel="[0,0,0"; | |
for (int32_t i=hStart; i<=numHistory; i++) { | |
outData += "," + String(adjHistory[i%N_HISTORY]); | |
errData += "," + String(errorHistory[i%N_HISTORY]); | |
minLabel += "," + String((tHistory[i%N_HISTORY]-tSt)/60); | |
if (system_get_free_heap_size()<MIN_HEAP) break; | |
} | |
outData += "];"; | |
errData += "];"; | |
minLabel += "];"; | |
String st="<h3>Error Trend " + String(month(tSt)) + "/" + String(day(tSt)) + " " + String(hour(tSt)) + ":" + preZero(String(minute(tSt))) + | |
" - " + String(month(tNow)) + "/" + String(day(tNow)) + " " + String(hour(tNow)) + ":" + preZero(String(minute(tNow))) + "</h3>"; | |
st += "<canvas id='myChart' width='400' height='250'></canvas>"; | |
st += "<script>Chart.defaults.global.defaultFontSize=30;"; | |
st += "var ctx = document.getElementById('myChart').getContext('2d');"; | |
st += "var errData=" + errData; | |
st += "var outData=" + outData; | |
st += "var minLabel=" + minLabel; | |
st += "var xLabel=[]; var i;"; | |
st += "for (i=0; i<=" + String(numHistory-hStart+3) + "; i++) {xLabel.push(" + String(tSt-TZH*3600) + "000 + minLabel[i]*60000);outData[i]/=5;}"; | |
st += "var errLine={label:'Error(sec)', showLine:true, lineTension:0, fill:false, borderWidth:5, borderColor:'blue', pointRadius:0, data:errData};"; | |
st += "var outLine={label:'Output/5', showLine:true, lineTension:0, fill:false, borderWidth:1, borderColor:'red', pointRadius:0, data:outData};"; | |
st += "var opt={scales: {xAxes: [{type: 'time', time: {displayFormats: {millisecond:'kk:mm',second:'kk:mm',minute:'kk:mm',hour:'M/D ha',}}}]}};"; | |
st += "var config={type:'line', options: opt, data: {datasets: [errLine, outLine], labels: xLabel}};"; | |
st += "var myChart = new Chart(ctx,config);</script>"; | |
st += "<h3>Free Heap=" + String(system_get_free_heap_size()) + " bytes</h3>"; | |
server.send(200, "text/html", msg1 + st + msg2); | |
} | |
void adjControl() { | |
if (adjOn) { | |
adjOn=false; | |
lcdDispText(0,1, "ADJ= "); | |
} | |
else { | |
adjOn=true; | |
lcdDispText(0,1, " "); | |
} | |
server.send (200, textHtml, msg1 + F("<h2>Control= ") + String(adjOn) + F("</h2>") + msg2); | |
} | |
void adjZero() { | |
adjLevel=0; | |
drvCoil(0); | |
dispAdj(); | |
} | |
void adjUp(){ | |
if (adjLevel<=ADJ_MAX-5) adjLevel +=5; | |
else adjLevel=ADJ_MAX; | |
drvCoil(adjLevel); | |
dispAdj(); | |
} | |
void adjDwn(){ | |
if (adjLevel>=ADJ_MIN+5) adjLevel -=5; | |
else adjLevel=ADJ_MIN; | |
drvCoil(adjLevel); | |
dispAdj(); | |
} | |
String preZero(String s) { | |
if (s.length()==1) return "0"+s; | |
else return s; | |
} | |
void resetErrorHistory() { | |
for (int16_t i=0; i<N_HISTORY; i++) { | |
errorHistory[i]=0; | |
} | |
} | |
void paramIUp() { | |
kI += 10; | |
server.send (200, textHtml, msg1 + F("<h2>KI= ") + String(kI) + F("</h2>") + msg2); | |
} | |
void paramIDn() { | |
kI -= 10; | |
server.send (200, textHtml, msg1 + F("<h2>KI= ") + String(kI) + F("</h2>") + msg2); | |
} | |
void paramPUp() { | |
kP += 10; | |
server.send (200, textHtml, msg1 + F("<h2>KP= ") + String(kP) + F("</h2>") + msg2); | |
} | |
void paramPDn() { | |
kP -= 10; | |
server.send (200, textHtml, msg1 + F("<h2>KP= ") + String(kP) + F("</h2>") + msg2); | |
} | |
void resetMinMax() { | |
adjLevelMax=adjLevel; | |
adjLevelMin=adjLevel; | |
ckPendErrorMax=ckPendError; | |
ckPendErrorMin=ckPendError; | |
t0MinMax=now(); | |
dispStatus(); | |
} | |
void setupServer() { | |
server.on("/", handleRoot); | |
server.on("/PC", adjControl); | |
server.on("/PD", adjDwn); | |
server.on("/PZ", adjZero); | |
server.on("/PU", adjUp); | |
server.on("/KPD", paramPDn); | |
server.on("/KPU", paramPUp); | |
server.on("/KID", paramIDn); | |
server.on("/KIU", paramIUp); | |
server.on("/M0", hand0Min); | |
server.on("/H0", hand0Hour); | |
server.on("/EZ", zeroError); | |
server.on("/MR", resetMinMax); | |
server.on("/ST", dispStatus); | |
server.on("/HE", dispErrorHist); | |
server.on("/HP", dispAdjHist); | |
server.on("/GR", dispGraph); | |
server.onNotFound(handleNotFound); | |
server.begin(); | |
} | |
void setupWifi() { | |
uint32_t msec; | |
lcdDispText(0,0, F("WIFI....")); | |
WiFi.mode(WIFI_AP_STA); | |
#if (STATIC_STA_IP) | |
WiFi.config(staIP, staGateway, subnet); | |
#endif | |
WiFi.begin(ssid, pass); | |
msec=millis(); | |
int16_t st; | |
do { | |
st=WiFi.status(); | |
delay(20); | |
} while (st!= WL_CONNECTED && millis()-msec<10000); | |
if (st==WL_CONNECTED) { | |
lcdDispText(4,0, F(" OK ")); | |
delay(1000); | |
IPAddress ipadr=WiFi.localIP(); | |
lcdDispText(0,0, " . ."); | |
lcdDispNum3n(0,0, ipadr[0]); | |
lcdDispNum3n(4,0, ipadr[1]); | |
lcdDispText(0,1, " . "); | |
lcdDispNum3n(0,1, ipadr[2]); | |
lcdDispNum3n(4,1, ipadr[3]); | |
delay(3000); | |
wifiOk=true; | |
udp.begin(2390); | |
msec=millis(); | |
time_t ret; | |
do { | |
ret=setNTP(); | |
delay(10); | |
} while (ret==0 && millis()-msec<10000); | |
if (ret) { | |
timeSynched=true; | |
setRef=true; | |
ledColon=true; | |
lcdDispText(0,0, "NTP OK "); | |
} | |
else lcdDispText(0,0, F("NTP NG ")); | |
delay(2000); | |
} | |
else { | |
lcdDispText(4,0, F(" NG ")); | |
WiFi.mode(WIFI_AP); | |
delay(3000); | |
wifiOk=false; | |
setRef=true; | |
} | |
WiFi.softAPConfig(apIP, apIP, subnet); | |
WiFi.softAP(apSsid, apPass); | |
} | |
void setupOta() { | |
ArduinoOTA.onStart([]() { | |
dbgPrintln(F("Start")); | |
}); | |
ArduinoOTA.onEnd([]() { | |
dbgPrintln(F("\nEnd")); | |
}); | |
ArduinoOTA.onProgress([](uint16_t progress, uint16_t total) { | |
dbgPrint(F("Progress: "));dbgPrintln(String(progress / (total / 100))); | |
}); | |
ArduinoOTA.onError([](ota_error_t error) { | |
dbgPrint(F("Error["));dbgPrint(String(error));dbgPrint(F("]: ")); | |
if (error == OTA_AUTH_ERROR) dbgPrintln(F("Auth Failed")); | |
else if (error == OTA_BEGIN_ERROR) dbgPrintln(F("Begin Failed")); | |
else if (error == OTA_CONNECT_ERROR) dbgPrintln(F("Connect Failed")); | |
else if (error == OTA_RECEIVE_ERROR) dbgPrintln(F("Receive Failed")); | |
else if (error == OTA_END_ERROR) dbgPrintln(F("End Failed")); | |
}); | |
ArduinoOTA.begin(); | |
dbgPrint(F("IP address: ")); | |
dbgPrintln(WiFi.localIP().toString()); | |
} | |