2018年6月13日水曜日

振り子時計の精度改善

// 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());
}