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
// | |
// Automatic Clock Tuner | |
// by Kiraku Labo | |
// | |
#include <SPIFFS.h> | |
#include <WiFiUdp.h> | |
#include <WiFi.h> | |
#include <HTTPClient.h> | |
#include <SSD1306Wire.h> | |
#include <ArduinoOTA.h> | |
#include <ESPAsyncWebServer.h> | |
#include <AsyncTCP.h> | |
const String verNum="214"; | |
const String fileVer="D"; //if file ver is different, load default setting | |
const String project="cTune"; | |
const char* apPass = "87654321"; | |
#define TZ_STR0 "JST-9" | |
#define TZ_STR1 "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00" | |
#define TZ_STR2 "CST6CDT5,M3.2.0/02:00:00,M11.1.0/02:00:00" | |
#define TZ_STR3 "NZST-12NZDT-13,M9.5.0/02:00:00,M4.1.0/2:00:00" | |
#if !defined(DEVELOP) | |
#define WEMOS //CPU board | |
//#define HELTEC //CPU board | |
#define DISP_INVERTED | |
#define A3144 //Mag sensor (Hall) | |
//#define SK8552 //Mag sensor (Hall) | |
//#define HMC5883 //Mag sensor (Digital compass) | |
//#define LSM303 //Mag sensor (Digital compass) | |
#define DEFAULT_PEND_CYCLE 1 | |
#define DEFAULT_PEND_DUR 1 | |
#define DEFAULT_TUNE_CYCLE 60 | |
#define DEFAULT_PEND_LF 1 | |
#define DEFAULT_KP 150 | |
#define DEFAULT_KI 20 | |
#define DEFAULT_NTP_BY_NAME true | |
#define DEFAULT_STA_IP_FIX "0" //1:fix 0:DNS | |
#define DEFAULT_DATE_FORMAT 2 //European | |
const char* DEFAULT_STA_GW="192.168.0.1"; | |
const char* DEFAULT_SUBNET="255.255.255.0"; | |
const char* DEFAULT_STA_DNS="192.168.0.1"; | |
const char* DEFAULT_NTP_IP=""; | |
const char* DEFAULT_NTP_NAME="pool.ntp.org"; | |
const char* DEFAULT_TZ_STR=TZ_STR1; //; | |
const char* DEFAULT_WIFI_SSID=""; | |
const char* DEFAULT_WIFI_PASS=""; | |
const char* DEFAULT_STA_IP="192.168.0.123"; | |
const IPAddress apIP(192,168,123,123); | |
const char* apSsid= "cTune"; | |
const String verLetter="V"; | |
#endif | |
#define DEFAULT_NTP_MIN_INTERVAL 3 //minute | |
#define DEFAULT_NTP_MAX_INTERVAL 60 //minute | |
#define MAX_NTP_TIMEOUT 1000 //msec | |
#define INITIAL_NTP_VALID 100 //msec | |
#define N_NTP_IP_HISTORY 20 | |
#define BUF_SIZE 200 | |
#define N_NTP_HISTORY 300 | |
#define PRE_CYCLE_MS 8000 //8 sec worth of cycles before setRef | |
#define N_HISTORY 1500 | |
#define MIN_HEAP 15000 | |
#define NUM_GRAPH_SPAN 1440 //points, must be less than N_HISTORY | |
#define NTP_PACKET_SIZE 48 | |
#define SEVENTY_YEARS 2208988800UL | |
#define SCREEN_W 128 | |
#define SCREEN_H 64 | |
#define CENTER_W (SCREEN_W/2) | |
#define CHAR_W 8 | |
#define CHAR_H 16 | |
#define GTOP 33 | |
#define GBOTTOM 63 | |
#define GYCENTER 48 // y axis 0 | |
#define GXORIGIN 2 | |
#define GRAPH_W (SCREEN_W-GXORIGIN-3) | |
#define OLEDFONT ArialMT_Plain_16 | |
#define CONFIG_FILE "/config.file" | |
#define G2_INTERVAL 15 //points | |
#define G2_THRESH 1500 //points | |
#define ADJ_MAX 1023 //must be 63 or more, see ##1 | |
#define ADJ_MIN -1023 | |
#define ONOFF_MODE_THRESH 500 //csec, If difference is greater than this value, coil is driven in full power. | |
#define PRECISE_MODE_THRESH 100 //In Catchup mode, when the error become within this csec, catchup mode ends. | |
#define ERROR_LIMIT -43200 //=12H If error (in sec) <this value (big delay), coil power is disabled | |
#define NTP_OFFSET_THRESH_H 20000 //usec | |
#define NTP_OFFSET_THRESH_L 10000 //usec | |
#if defined(HELTEC) | |
#define SENS_PIN 12 | |
#define LED_PIN 25 //internal LED | |
#define DRV1_PIN 14 | |
#define DRV2_PIN 27 | |
#define SDA_PIN 4 | |
#define SCL_PIN 15 | |
#elif defined(WEMOS) | |
#if defined(LSM303) | |
#define SENS_PIN 16 | |
#else | |
#define SENS_PIN 15 | |
#endif | |
#define LED_PIN 13 | |
#define DRV1_PIN 12 | |
#define DRV2_PIN 14 | |
#define SDA_PIN 5 | |
#define SCL_PIN 4 | |
#endif | |
#if defined(A3144) | |
#define MAG_ON LOW | |
#define MAG_OFF HIGH | |
#else | |
#define MAG_ON HIGH | |
#define MAG_OFF LOW | |
#endif | |
#if defined(HMC5883) | |
#define MAG_ADDR 0x1E | |
#define MAG_THRESH_H 3000 | |
#define MAG_THRESH_L 2000 | |
#elif defined(LSM303) | |
#define MAG_ADDR 0x1E | |
#define MAG_THRESH_HB 16 | |
#define MAG_THRESH_LB 0 | |
#endif | |
int32_t ntpOffsetHistory[N_NTP_HISTORY]; //usec | |
int32_t ntpDelayHistory[N_NTP_HISTORY]; //usec | |
int32_t ntpData3History[N_HISTORY]; | |
int32_t ntpData4History[N_HISTORY]; | |
time_t ntpTimeHistory[N_NTP_HISTORY]; //sec | |
byte ntpPassHistory[N_NTP_HISTORY]; //pass=1 ,fail=0 | |
int32_t ntpNumHistory=-1; | |
struct tm tmAtRef0Local, tmAtSetTimeLocal, tmAtResetMMLocal, tmSynchLocal; | |
struct timeval tvAtNtpSuccess, tvAtSensTrig, tvAtRef0, tvAtResetMM, tvAtNtpFail, tvGmtNow, tvAtNtpServerSet; | |
struct timeval tvNtpRef, tvNtpOrig, tvNtpRecv, tvNtpTrans, tvStart, tvEnd; | |
int32_t ntpDelay, ntpOffset, ntpOffsetPerMin=0; | |
float ntpDelayAve; | |
unsigned portBASE_TYPE stzckSize; | |
boolean serialMon=false; | |
const String textHtml="text/html"; | |
int32_t errorHistory[2][N_HISTORY]; //csec | |
int16_t adjHistory[2][N_HISTORY]; | |
time_t tHistory[2][N_HISTORY]; //sec | |
time_t gmtLast=0, gmtAtSec=0, gmtNow; | |
int16_t adjLevel=0, adjLevelMax=0, adjLevelMin=0; | |
float cadjLevelAve=0.0; | |
int32_t numHistory[2]={-1, -1}; | |
boolean adjOn=true; | |
boolean onoffMode=false; | |
boolean timeWasSynched=false, setRefReady=false; | |
int16_t timeBuf[6]; | |
int16_t adjGraphFactor=5*(ADJ_MAX+1)/64; | |
uint16_t ntpIPHistoryIdx=0; | |
volatile boolean refSet=false; | |
volatile int32_t countPend=0; | |
volatile boolean sensTrig=false, trigWindow=false; | |
volatile boolean sensRelease=false; | |
volatile int16_t pendDir=0; | |
volatile uint32_t milliSensLast=0, milliSensTrig=0, milliSensRelease=0, milliSensDur=0; | |
volatile int64_t csecPend=0; | |
volatile boolean sensWithinWindow=false; | |
char strBuf[BUF_SIZE]; | |
byte packetBuffer[NTP_PACKET_SIZE]; | |
boolean shortPush=false, longPush=false, longlongPush=false; | |
uint32_t buttonMilli=0; | |
time_t tLocal; | |
time_t tzStart=0, tzEnd=0; | |
int32_t tzOffset=0; | |
boolean tzDstObs=false; | |
int tzDst=0; | |
int32_t csecPendError=0, csecPendErrorMax=0, csecPendErrorMin=0, csecPendErrorLast=0; | |
int32_t cadjLevel=0; | |
uint32_t ntpTurnaround; | |
byte gSpanUnitIdx=1; //0:h 1:d 2:w | |
byte gYEscaleIdx=0; | |
byte gYCscaleIdx=0; | |
uint32_t graphSpan=24*3600; //in seconds | |
String gYEscaleMM, gYCscaleMM; | |
const uint32_t gSpanUnit[] = {3600, 3600*24, 24*7*3600}; | |
const String gSpanUnitStr[] = {"h", "d", "w"}; | |
const String gYEscaleStr[] = {"Auto", "1s", "5s ", "20s", "100s"}; | |
const String gYCscaleStr[] = {"Auto", "20", "100", "500", "2000"}; | |
const int16_t gYEscaleNum[] = {0,1,5,20,100}; | |
const int16_t gYCscaleNum[] = {0,20,100,500,2000}; | |
const int16_t ngY1scaleNum[] = {10,20,50,100}; | |
byte ngY1scaleIdx=0; | |
int16_t gYEscale=0, gYCscale=0; | |
byte ntpPacketId=0; | |
byte ntpInvalidCount=0; | |
boolean ntpByName=DEFAULT_NTP_BY_NAME; | |
uint16_t countConsecutiveDec=0; | |
WiFiUDP udp; | |
HTTPClient http; | |
AsyncWebServer server(80); | |
SSD1306Wire oled(0x3c, SDA_PIN, SCL_PIN); | |
const IPAddress apSNM(255,255,255,0); | |
IPAddress staIP; | |
IPAddress staGateway; | |
IPAddress staSNM; | |
IPAddress staDNS; | |
IPAddress ntpIP; | |
IPAddress ntpIPbyIP; | |
IPAddress ntpIPHistory[N_NTP_IP_HISTORY]; | |
time_t ntpIPTimeHistory[N_NTP_IP_HISTORY]; | |
const String msgHead ="<html><head><meta charset='UTF-8'><title>Kiraku Labo</title>\ | |
<style>body{width:95%;margin:0;padding:30;background-color:#dddddd;text-align:center;font-size:36pt}\ | |
h1{font-size:40pt} h2{font-size:36pt} p{font-size:36pt} button{font-size:50pt}\ | |
input[type=submit]{font-size:50pt}\ | |
input[type=radio]{width:50px;height:50px;vertical-align:middle}\ | |
input[type=text]{width:500px;font-size:36pt;margin-right:50px}\ | |
input[type=number]{width:130px;font-size:36pt;margin-right:0px}\ | |
span{display:inline-block;width:350px;text-align:right}\ | |
form{display:inline}</style>\ | |
</head><body><h1>Kiraku Labo " + project + " " + verLetter+verNum +"</h1>"; | |
const String msgHome = msgHead + | |
"<form action='CF'><input type='submit' value='Config'></form> \ | |
<form action='SA'><input type='submit' value='Sens Adj'></form> \ | |
<form action='MR'><input type='submit' value='Rst M/M'></form><br>\ | |
<form action='H0'><input type='submit' value='Hand 0s'></form>\ | |
<form action='EZ'><input type='submit' value='Err 0'></form>\ | |
<form id='form2' action='SE'><button type='submit' form='form2'>Err +/-</button>\ | |
<input id='EV' name='EV' type='number' step='0.1' value='0' style='font-size:50pt'>s</form><br>\ | |
<form action='TC'><input type='submit' value='Tgl Ctrl'></form>\ | |
<form id='form3' action='SO'><button type='submit' form='form3'>Set Current</button>\ | |
<input id='OV' name='OV' type='number' value='0' max='1023' min='-1023' style='font-size:50pt;width:200px'></form><br>\ | |
<form action='ST'><input type='submit' value='Home'></form> \ | |
<form action='GR'><input type='submit' value='Accuracy'></form> \ | |
<form action='GN'><input type='submit' value=' NTP '></form>"; | |
const String msgEnd = "</body></html>"; | |
const String msgYesNo= "<form action='YS'><input type='submit' value='Yes'></form>\ | |
<form action='NO'><input type='submit' value=' No '>"; | |
const String msgChartHead = "<canvas id='myChart' width='400' height='250'></canvas>\ | |
<script src='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.min.js'></script>\ | |
<script>Chart.defaults.global.defaultFontSize=30;"; | |
int32_t pendCycle, pendDur; | |
String msgConf; | |
String wifiSSID, wifiPass, ntpServerName, tzStr; | |
String strVer, strPC, strPD, strPT, strKP, strKI, strLF, strWS, strWP, strIF, strIL, strIG, strID, strIS, strNF, strNN, strNI, strNV, strNX, strTZ, strDF; | |
boolean staIpFixed=false, apOnly=false, wifiDefined=false; | |
byte dateFormat=0; | |
const String dateFmtYMD[4]={"%Y/%m/%d", "%Y/%m/%d", "%d/%m/%Y", "%m/%d/%Y"}; | |
const String dateFmtMD[4]={"%m/%d", "%m/%d", "%d/%m", "%m/%d"}; | |
const String dateStr[4]={"", "yyyy/mm/dd", "dd/mm/yyyy", "mm/dd/yyyy"}; | |
volatile uint32_t microSensTrig=0; | |
volatile boolean sensAdjMode=false; | |
byte lcdGmode=0; //0:alternate, 1:error, 2:Current | |
int32_t ntpDelayLimitUs=INITIAL_NTP_VALID*1000; | |
int16_t magX, magY, magZ, magLast; | |
int32_t minSensOffDuration, minSensOnDuration, trigWindowSpan, pendWindow; | |
int16_t kP, kI, liftFactor; | |
byte preCount; | |
int32_t tuneInterval; //in sec | |
int32_t tuneIntervalMs; //in msec | |
int16_t pendTuneCycle; //cycles per tune | |
uint32_t pendPeriodMs; | |
int32_t secTmAdj, usecTmAdj; | |
int16_t ntpInterval, ntpMinInterval, ntpMaxInterval; | |
boolean ntpFirstOffset=true; | |
int32_t ntpInterporateAccum=0, ntpInterporateCount=0; | |
int64_t trigDelay=0; | |
uint32_t msecInterporate=0, msecNTP=0; | |
uint32_t msecTune=0; | |
uint32_t ntpSynchCount=0, ntpIntervalInc=0, ntpIntervalDec=0; | |
uint16_t ntpY1Scale=20, ntpY2Scale=200; | |
const int32_t gscaleE[]={10,20,50,100,200,500,1000,2000,5000,10000,20000,50000,100000,200000,500000}; //x0.01sec | |
const int16_t gscaleA[]={5,10,20,50,100,200,500,1000}; | |
const int16_t numgE=sizeof(gscaleE)/sizeof(gscaleE[0]); | |
const int16_t numgA=sizeof(gscaleA)/sizeof(gscaleA[0]); | |
const uint32_t i2cTimeout=10; //msec | |
byte i2cStatus; | |
//boolean compassTimeout; | |
void (*funcPtrYes)(AsyncWebServerRequest *request)=NULL; | |
void (*funcPtrNo)(AsyncWebServerRequest *request)=NULL; | |
void (*funcPtrWeb)(AsyncWebServerRequest *request)=NULL; | |
AsyncWebServerRequest *funcReqWeb; | |
void sensInt() { | |
uint32_t milliAtInt=millis(); | |
int16_t sns=digitalRead(SENS_PIN); | |
if (pendDir==0 && sns==MAG_ON && (milliAtInt-milliSensRelease)>minSensOffDuration) { | |
pendDir=1; | |
if (!sensAdjMode) digitalWrite(LED_PIN, HIGH); | |
sensTrig=true; | |
microSensTrig=micros(); | |
milliSensTrig=milliAtInt; | |
if (refSet) { | |
countPend++; | |
csecPend=(int64_t)countPend * (int64_t)pendDur * 100 / (int64_t)pendCycle; | |
} | |
} | |
else if (pendDir==1 && sns==MAG_OFF && (milliAtInt-milliSensTrig)>minSensOnDuration) { | |
pendDir=0; | |
if (!sensAdjMode) digitalWrite(LED_PIN, LOW); | |
sensRelease=true; | |
milliSensRelease=milliAtInt; | |
} | |
} | |
void setup() { | |
SPIFFS.begin(true); | |
Serial.begin(115200); | |
pinMode(LED_PIN, OUTPUT); | |
#ifdef HELTEC | |
pinMode(16, OUTPUT); | |
digitalWrite(16, LOW); // set GPIO16 low to reset OLED | |
delay(50); | |
digitalWrite(16, HIGH); | |
#endif | |
setupOled(); | |
Wire.setClock(50000); //50khz | |
#ifdef DISP_INVERTED | |
oled.flipScreenVertically(); | |
#endif | |
oled.setTextAlignment(TEXT_ALIGN_LEFT); | |
lcdDispText(0, 0, project+" "+verLetter+verNum); | |
#if defined(HMC5883) | |
setupHMC5883(); | |
#elif defined(LSM303) | |
setupLSM303(); | |
#endif | |
boolean loadConfErr=false; | |
if (SPIFFS.exists(CONFIG_FILE)) { | |
if (!loadConfigFile()) { | |
loadDefaultConfig(); | |
loadConfErr=true; | |
} | |
} | |
else { | |
loadDefaultConfig(); | |
loadConfErr=true; | |
} | |
setConfigPara(); | |
setConfigMsg(); | |
if (loadConfErr) saveConfigFile(); | |
ntpInterval=ntpMinInterval; | |
setupMechTiming(); | |
preCount=PRE_CYCLE_MS/pendPeriodMs; | |
if (preCount<3) preCount=3; | |
setupAP(); | |
byte tryCount=0; | |
do { | |
#ifdef DEVELOP | |
selectWifi(); | |
#endif | |
setupWifi(); | |
tryCount++; | |
} while(wifiConnected()==false && tryCount<3); | |
if (!wifiConnected()) { | |
apOnly=true; | |
lcdDispText(0,0, F("AP Only ")); | |
lcdDispText(0,1, apSsid); | |
lcdDispText(0,2, apPass); | |
lcdDispText(0,3, WiFi.softAPIP().toString()); | |
} | |
setupServer(); | |
while(!wifiConnected()); //if no wifi, infinite loop here | |
setupOta(); | |
setupNtp(); | |
while (!synchTime(1)) { //use transmit timestamp | |
delay(10000); | |
} | |
tvAtNtpServerSet=tvAtNtpSuccess; | |
ntpIPHistory[0]=ntpIP; | |
ntpIPTimeHistory[0]=tvAtNtpServerSet.tv_sec; | |
ntpDelayAve=(float)ntpDelay; | |
updateNtpValidDelay(); | |
while (!synchTime(0)) { //use offset | |
updateNtpValidDelay(); | |
delay(10000); | |
} | |
updateNtpValidDelay(); | |
lcdDispText(0,0, "NTP delay=" + String(ntpDelay/1000)); | |
delay(3000); | |
setDst(); | |
#if !defined(DRV8830) | |
ledcSetup(1, 100, 10); //LEDC_CHANNEL, LEDC_FREQUENCY, LEDC_RESOLUTION_BITS | |
ledcSetup(2, 100, 10); | |
ledcAttachPin(DRV1_PIN, 1); //PIN, LEDC_CHANNEL | |
ledcAttachPin(DRV2_PIN, 2); | |
ledcWrite(1,0); | |
ledcWrite(2,0); | |
#endif | |
pinMode(SENS_PIN, INPUT_PULLUP); | |
#if !defined(HMC5883) | |
attachInterrupt(digitalPinToInterrupt(SENS_PIN), sensInt, CHANGE); | |
#endif | |
#if defined(DEVELOP) | |
devSetup(); | |
#endif | |
} | |
void loop() { | |
#if defined(HMC5883) | |
getHMC5883(); | |
if (sensAdjMode) { | |
char lcdbuf[13]; | |
snprintf(lcdbuf, 19, "X= %s%05d", magX>=0?" ":"-", abs(magX)); | |
lcdDispText2(0,1, lcdbuf, 80); | |
snprintf(lcdbuf, 19, "Y= %s%05d", magY>=0?" ":"-", abs(magY)); | |
lcdDispText2(0,2, lcdbuf, 80); | |
snprintf(lcdbuf, 19, "Z= %s%05d", magZ>=0?" ":"-", abs(magZ)); | |
lcdDispText2(0,3, lcdbuf, 80); | |
} | |
int16_t magValue=abs(magX)+abs(magY)+abs(magZ); | |
if (magValue>MAG_THRESH_H) magTrigH(); | |
else if (magValue<MAG_THRESH_L) magTrigL(); | |
#elif defined(LSM303) | |
if (sensAdjMode) { | |
char lcdbuf[13]; | |
magX=readLSM303(0x28); | |
snprintf(lcdbuf, 19, "X= %s%05d", magX>=0?" ":"-", abs(magX)); | |
lcdDispText2(0,1, lcdbuf, 80); | |
magY=readLSM303(0x2A); | |
snprintf(lcdbuf, 19, "Y= %s%05d", magY>=0?" ":"-", abs(magY)); | |
lcdDispText2(0,2, lcdbuf, 80); | |
magZ=readLSM303(0x2C); | |
snprintf(lcdbuf, 19, "Z= %s%05d", magZ>=0?" ":"-", abs(magZ)); | |
lcdDispText2(0,3, lcdbuf, 80); | |
if (digitalRead(SENS_PIN)==MAG_ON) pendIndicator(WHITE); | |
else pendIndicator(BLACK); | |
} | |
#endif | |
#if !defined(HMC5883) | |
if (sensAdjMode) { | |
if (digitalRead(SENS_PIN)==MAG_ON) digitalWrite(LED_PIN, HIGH); | |
else digitalWrite(LED_PIN, LOW); | |
} | |
#endif | |
if (sensTrig) { | |
sensTrig=false; | |
#if !defined(HMC5883) | |
trigDelay=(int64_t)(micros()-microSensTrig); | |
if (trigDelay<0) trigDelay=0; | |
#endif | |
gettimeofday(&tvAtSensTrig, NULL); | |
convUsec2Tv(convTv2Usec(&tvAtSensTrig) - trigDelay, &tvAtSensTrig); | |
pendIndicator(WHITE); | |
if (refSet) { | |
if (countPend%pendTuneCycle==0) { | |
updateError(); | |
lcdDispError(); | |
tune(); | |
lcdDispGraph(); | |
} | |
else { | |
updateError(); | |
if (!sensAdjMode) { | |
lcdDispError(); | |
lcdDispGraph(); | |
} | |
} | |
} | |
if (preCount>0) { | |
preCount--; | |
if (preCount==0) { | |
setRef0(); | |
tune(); | |
} | |
} | |
if (refSet) lcdDispNum4n(0,0,countPend%pendTuneCycle); | |
} | |
else if (sensRelease) { | |
sensRelease=false; | |
pendIndicator(BLACK); | |
} | |
gettimeofday(&tvGmtNow, NULL); | |
gmtNow=tvGmtNow.tv_sec; | |
if (gmtNow>gmtLast) { //every 1 sec | |
gmtAtSec=gmtNow; | |
gmtLast=gmtNow; | |
int sec=gmtNow%60; | |
if (preCount>0) lcdDispTime(0,0,1); //date & time | |
else lcdDispTime(5,0,2); //time only | |
if (millis()-milliSensTrig>pendWindow && refSet) { | |
tvAtSensTrig=tvGmtNow; | |
updateError(); | |
if (!sensAdjMode) lcdDispError(); | |
} | |
if (sec==30) { | |
if (wifiConnected()) { | |
if (gmtAtSec>=tvAtNtpSuccess.tv_sec + ntpInterval*60) { | |
if (millis()-msecNTP>30000) { | |
msecNTP=millis(); | |
if (synchTime(0)) updateNtpValidDelay(); | |
else if (!ntpFirstOffset) interporateTime(); | |
} | |
} | |
else if (!ntpFirstOffset) interporateTime(); | |
} | |
if (millis()-milliSensTrig>pendWindow && refSet) tune(); | |
} | |
if (refSet) lcdDispGraph(); | |
#if defined(DEVELOP) | |
devOneSec(); | |
#endif | |
} | |
if (funcPtrWeb) { | |
funcPtrWeb(funcReqWeb); | |
funcPtrWeb=NULL; | |
} | |
senseButton(); | |
ArduinoOTA.handle(); | |
yield(); | |
} | |
void setRef0() { | |
countPend=0; | |
refSet=true; | |
tvAtRef0=tvAtSensTrig; | |
localtime_r(&tvAtRef0.tv_sec, &tmAtRef0Local); | |
tvAtResetMM=tvAtRef0; | |
tmAtResetMMLocal=tmAtRef0Local; | |
numHistory[0]=0; | |
numHistory[1]=0; | |
tHistory[0][0]=tvAtRef0.tv_sec; | |
tHistory[1][0]=tvAtRef0.tv_sec; | |
milliSensLast=milliSensTrig; | |
csecPendError=0; | |
csecPendErrorLast=0; | |
adjLevelMax=0; | |
adjLevelMin=0; | |
csecPendErrorMax=0; | |
csecPendErrorMin=0; | |
sendStat(); | |
} | |
void sendStat() { | |
milliSensDur=milliSensTrig-milliSensLast; | |
milliSensLast=milliSensTrig; | |
struct tm tl; | |
localtime_r(&tvAtSensTrig.tv_sec, &tl); | |
snprintf(strBuf, BUF_SIZE, "PEND:%02d/%02d %02d:%02d:%02d.%03d Intv=%04d Cnt=%d Hst=%d TrigD=%d Err=%.2f Out=%d Ave=%.2f", | |
tl.tm_mon+1, tl.tm_mday, tl.tm_hour, tl.tm_min, tl.tm_sec, tvAtSensTrig.tv_usec/1000, | |
milliSensDur, countPend, numHistory[0], (int32_t)trigDelay, (float)csecPendError/100.0, adjLevel, cadjLevelAve/100.0); | |
dbgPrintln(strBuf); | |
} | |
void pendIndicator(OLEDDISPLAY_COLOR color) { | |
oled.setColor(color); | |
oled.fillCircle(124, 25, 3); | |
oled.display(); | |
} | |
void tune() { | |
numHistory[0]++; | |
int16_t idx=numHistory[0] % N_HISTORY; | |
if (adjOn) { | |
adjPend(); | |
lcdDispNum4n(9,1,adjLevel); | |
} | |
csecPendErrorLast=csecPendError; | |
errorHistory[0][idx]=csecPendError; | |
if (!onoffMode) cadjLevelAve = cadjLevelAve * 0.97 + (float)cadjLevel * 0.03; | |
adjHistory[0][idx]=adjLevel; | |
tHistory[0][idx]=gmtAtSec; | |
if (numHistory[0]%G2_INTERVAL==0) { | |
numHistory[1]++; | |
int16_t idx2=numHistory[1] % N_HISTORY; | |
errorHistory[1][idx2]=csecPendError; | |
adjHistory[1][idx2]=adjLevel; | |
tHistory[1][idx2]=gmtAtSec; | |
} | |
sendStat(); | |
} | |
void updateError() { | |
// if (abs(csecPendError/100)>30*24*3600) resetRef(); //more than 30 day difference | |
csecPendError=(int32_t)(csecPend - (100*(int64_t)(tvAtSensTrig.tv_sec-tvAtRef0.tv_sec) +(int64_t)(tvAtSensTrig.tv_usec-tvAtRef0.tv_usec)/10000)); | |
if (csecPendError>csecPendErrorMax) { | |
csecPendErrorMax=csecPendError; | |
// sendStat(); | |
} | |
if (csecPendError<csecPendErrorMin) { | |
csecPendErrorMin=csecPendError; | |
// sendStat(); | |
} | |
} | |
void lcdDispError() { | |
lcdDispNumF(0,1, csecPendError); | |
} | |
void adjPend() { | |
msecTune=millis(); | |
if (abs(csecPendError)>ONOFF_MODE_THRESH) onoffMode=true; | |
if (onoffMode) { | |
if (csecPendError>PRECISE_MODE_THRESH) cadjLevel=ADJ_MIN*100/liftFactor; | |
else if (csecPendError<-PRECISE_MODE_THRESH) cadjLevel=ADJ_MAX*100; | |
else { | |
onoffMode=false; | |
cadjLevel=(int32_t)cadjLevelAve; | |
} | |
} | |
else { | |
int32_t cadjLevelDelta = (int32_t) ((csecPendError-csecPendErrorLast)*6*kP/tuneInterval + csecPendError*kI/10); //use per-min rate factor of 60/tuneInterval | |
cadjLevelDelta *= (ADJ_MAX+1)/64; //##1 | |
cadjLevel = constrain(cadjLevel-cadjLevelDelta, ADJ_MIN*100/liftFactor, ADJ_MAX*100); | |
} | |
if (csecPendError<ERROR_LIMIT*100) cadjLevel=0; | |
adjLevel=cadjLevel/100; | |
drvCoil(adjLevel); | |
if (adjLevel>adjLevelMax) adjLevelMax=adjLevel; | |
if (adjLevel<adjLevelMin) adjLevelMin=adjLevel; | |
} | |
#if !defined(DRV8830) | |
void drvCoil(int16_t pwr) { | |
if (pwr==0) digitalWrite(LED_PIN, LOW); | |
else digitalWrite(LED_PIN, HIGH); | |
if (pwr>=0) { | |
ledcWrite(1, 0); | |
ledcWrite(2, pwr); | |
} | |
else { | |
ledcWrite(2, 0); | |
ledcWrite(1, -pwr); | |
} | |
} | |
#endif | |
void senseButton() { | |
int button=digitalRead(0); | |
if (button==LOW) { | |
if (shortPush==false) { | |
shortPush=true; | |
longPush=false; | |
longlongPush=false; | |
buttonMilli=millis(); | |
} | |
else { | |
if (millis()-buttonMilli>1000) { | |
if (!longPush) { //long push = enable/disable Current | |
longPush=true; | |
if (adjOn) { | |
adjOn=false; | |
adjLevel=0; | |
cadjLevel=0; | |
drvCoil(0); | |
lcdDispText(9,1, "---"); | |
} | |
else { | |
adjOn=true; | |
lcdDispText(9,1, " "); | |
} | |
} | |
else if (millis()-buttonMilli>2000) { | |
if (!longlongPush) { //long long push | |
longlongPush=true; | |
} | |
} | |
} | |
} | |
} | |
else { | |
if (shortPush==true) { | |
shortPush=false; | |
if (!longPush && !longlongPush) { //short push = lcd graph mode | |
lcdGmode = (lcdGmode+1)%3; | |
} | |
} | |
} | |
} | |
int64_t convTv2Usec(struct timeval* tv) { | |
return (int64_t)(tv->tv_sec)*1000000 + (int64_t)(tv->tv_usec); | |
} | |
void convUsec2Tv(int64_t t, struct timeval* tv) { | |
tv->tv_sec=(time_t)(t/1000000); | |
tv->tv_usec=(uint32_t)(t%1000000); | |
} | |
boolean synchTime(byte synchMode) { //syncMode 0= offset synch, 1=Transmit timestamp sync | |
// lcdDispText(0,0, "NTP mode"+String(synchMode)+"..."); | |
struct timeval tv1, tv2; | |
int ret=getNTPTime(synchMode); | |
if (ret==0) { //delay validity is OK | |
if (synchMode==1) { //transfer | |
settimeofday(&tvNtpTrans, NULL); | |
tvAtNtpSuccess=tvNtpTrans; | |
} | |
else if (synchMode==0) { //offset mode | |
ntpSynchCount++; | |
if (ntpFirstOffset) { | |
ntpInterval=ntpMinInterval; | |
ntpOffset /= ntpInterval; | |
ntpOffsetPerMin=ntpOffset; | |
ntpDelayAve=(float)ntpDelay; | |
ntpFirstOffset=false; | |
} | |
else { | |
int16_t increment; | |
ntpOffsetPerMin=(ntpInterporateAccum+ntpOffset)/(ntpInterporateCount+1); | |
if (abs(ntpOffset)<NTP_OFFSET_THRESH_L*3 && abs(ntpOffsetPerMin)<NTP_OFFSET_THRESH_L) { | |
increment=ntpInterval*3/10; | |
if (increment==0) increment=1; | |
ntpIntervalInc++; | |
countConsecutiveDec=0; | |
} | |
else if (abs(ntpOffset)>=NTP_OFFSET_THRESH_H*3 || abs(ntpOffsetPerMin)>=NTP_OFFSET_THRESH_H) { | |
increment=-ntpInterval*3/13; | |
if (increment==0) increment=-1; | |
ntpIntervalDec++; | |
countConsecutiveDec++; | |
if (countConsecutiveDec>=3 && ntpByName) ntpChange(); | |
} | |
else { | |
increment=0; | |
countConsecutiveDec=0; | |
} | |
ntpInterval += increment; | |
if (ntpInterval>ntpMaxInterval) ntpInterval=ntpMaxInterval; | |
if (ntpInterval<ntpMinInterval) ntpInterval=ntpMinInterval; | |
if (ntpInterval<=0) ntpInterval=1; | |
dbgPrintln("NTP interval+=" + String(increment) + " =" + String(ntpInterval) + " InterporateCt=" + String(ntpInterporateCount) + " Offset=" + String(ntpOffset/1000) + "ms"); //$$$$$ | |
ntpInterporateCount=0; | |
ntpInterporateAccum=0; | |
} | |
gettimeofday(&tv1, NULL); | |
convUsec2Tv(convTv2Usec(&tv1) + +ntpOffset + ntpOffsetPerMin, &tv2); //##2 | |
settimeofday(&tv2, NULL); | |
tvAtNtpSuccess=tv2; | |
} | |
if (timeWasSynched==false) { | |
timeWasSynched=true; | |
} | |
} | |
else { | |
gettimeofday(&tvAtNtpFail, NULL); | |
if (ntpInvalidCount>=3) { | |
if (ntpDelayLimitUs*2<MAX_NTP_TIMEOUT*1000) ntpDelayLimitUs *= 2; | |
else ntpDelayLimitUs=MAX_NTP_TIMEOUT*1000; | |
if (ntpByName) { | |
if (ntpInvalidCount>=6) { | |
ntpInvalidCount=0; | |
ntpIntervalInc=0; | |
ntpIntervalDec=0; | |
ntpDelayLimitUs=INITIAL_NTP_VALID*1000; | |
ntpChange(); | |
} | |
} | |
} | |
} | |
if (synchMode==0) updateNtpHistory(ret); | |
localtime_r(&tv1.tv_sec, &tmSynchLocal); | |
snprintf(strBuf, BUF_SIZE, "NTP:%d.%d.%d.%d PID=%03d Code=%d Settime=%02d/%02d %02d:%02d:%02d.%06d offset=%d offset/min=%d delay=%d delayAve=%.2f %s", | |
ntpIP[0], ntpIP[1], ntpIP[2], ntpIP[3], ntpPacketId, ret, tmSynchLocal.tm_mon+1, tmSynchLocal.tm_mday, tmSynchLocal.tm_hour, tmSynchLocal.tm_min, | |
tmSynchLocal.tm_sec, tv1.tv_usec, ntpOffset, ntpOffsetPerMin, ntpDelay, ntpDelayAve, ret==0?"Synch Success":"Validity Fail"); | |
dbgPrintln(strBuf); | |
if (ret==0) return true; | |
else return false; | |
} | |
void ntpChange() { | |
IPAddress temp=ntpIP; | |
if (ntpByName) WiFi.hostByName(ntpServerName.c_str(), ntpIP); | |
else ntpIP=ntpIPbyIP; | |
if (temp[0]==ntpIP[0] && temp[1]==ntpIP[1] && temp[2]==ntpIP[2] && temp[3]==ntpIP[3]) return; | |
gettimeofday(&tvAtNtpServerSet, NULL); | |
ntpFirstOffset=true; | |
ntpInterval=ntpMinInterval; | |
countConsecutiveDec=0; | |
ntpIPHistoryIdx++; | |
ntpIPHistory[ntpIPHistoryIdx%N_NTP_IP_HISTORY]=ntpIP; | |
ntpIPTimeHistory[ntpIPHistoryIdx%N_NTP_IP_HISTORY]=tvAtNtpServerSet.tv_sec; | |
} | |
void updateNtpHistory(byte ret) { | |
ntpNumHistory++; | |
int32_t idx=ntpNumHistory%N_NTP_HISTORY; | |
ntpOffsetHistory[idx]=ntpOffsetPerMin; | |
ntpDelayHistory[idx]=ntpDelay; | |
ntpPassHistory[idx]=(ret==0?1:0); | |
ntpData3History[idx]=ntpOffset; | |
ntpData4History[idx]=ntpDelayLimitUs; | |
if (ret==0) ntpTimeHistory[idx]=tvAtNtpSuccess.tv_sec; | |
else ntpTimeHistory[idx]=tvAtNtpFail.tv_sec; | |
} | |
void updateNtpValidDelay() { | |
ntpDelayAve = ntpDelayAve * 0.9 + 0.1*(float)ntpDelay; | |
ntpDelayLimitUs=(int32_t)(ntpDelayAve*2.0); | |
if (ntpDelayLimitUs>MAX_NTP_TIMEOUT*1000) ntpDelayLimitUs=MAX_NTP_TIMEOUT*1000; | |
} | |
void interporateTime() { | |
if (ntpMinInterval>ntpMaxInterval) return; //no interporation | |
if (millis()-msecInterporate<30000) return; | |
msecInterporate=millis(); | |
struct timeval tv1, tv2; | |
delay(10); //prevent sec going back | |
gettimeofday(&tv1, NULL); | |
convUsec2Tv(convTv2Usec(&tv1) + ntpOffsetPerMin, &tv2); | |
settimeofday(&tv2, NULL); | |
ntpInterporateCount++; | |
ntpInterporateAccum+=ntpOffsetPerMin; | |
dbgPrintln("Time interporation:" + String(ntpInterporateCount) + " present usec=" + String(tv1.tv_usec) + " adj=" + String(ntpOffsetPerMin) + "usec"); | |
} | |
int getNTPTime(byte synchMode) { | |
int16_t pktSize; | |
if (udp.parsePacket()>0) udp.read(packetBuffer, NTP_PACKET_SIZE); //clear buffer | |
ntpPacketId++; | |
if (ntpPacketId==0) ntpPacketId=1; //1 to 255 are used | |
uint32_t msec=millis(); | |
gettimeofday(&tvStart, NULL); | |
sendNTPpacket(); // send an NTP packet to a time server | |
do { | |
pktSize = udp.parsePacket(); | |
ntpTurnaround=millis()-msec; | |
} while (pktSize<NTP_PACKET_SIZE && ntpTurnaround<MAX_NTP_TIMEOUT); | |
if (pktSize>0) udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer | |
gettimeofday(&tvEnd, NULL); | |
getNtpTv(32, &tvNtpRecv); | |
getNtpTv(40, &tvNtpTrans); | |
int64_t t1=convTv2Usec(&tvStart); | |
int64_t t2=convTv2Usec(&tvNtpRecv); | |
int64_t t3=convTv2Usec(&tvNtpTrans); | |
int64_t t4=convTv2Usec(&tvEnd); | |
ntpOffset = (int32_t)( t2 - t1 + t3 - t4 )/2; | |
ntpDelay = (int32_t)((t4 - t1) - (t3 - t2)); | |
if (ntpTurnaround>=MAX_NTP_TIMEOUT) { | |
ntpInvalidCount++; | |
return 2; | |
} | |
else if ((synchMode==0?ntpDelay/1000:ntpTurnaround)>=ntpDelayLimitUs/1000) { //##3 | |
ntpInvalidCount++; | |
return 1; | |
} | |
else if (packetBuffer[31]!=ntpPacketId) return 3; //packet not match | |
else if (tvNtpTrans.tv_sec>2082726000UL) return 4; //if tv_sec>2036/1/1 do not record (NTP overflows in this year) | |
else { | |
ntpInvalidCount=0; | |
return 0; | |
} | |
} | |
void getNtpTv(int adr, struct timeval* tvp) { | |
uint32_t secSince1900 = packetBuffer[adr+0]<<24 | packetBuffer[adr+1]<<16 | packetBuffer[adr+2]<<8 | packetBuffer[adr+3]; | |
tvp->tv_sec =secSince1900 - SEVENTY_YEARS; | |
tvp->tv_usec = (uint32_t)packetBuffer[adr+4]*1000000/256 + (uint32_t)packetBuffer[adr+5]*1000000/65536 + | |
(uint32_t)packetBuffer[adr+6]*1000000/65536/256; | |
} | |
void sendNTPpacket(){ | |
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; | |
packetBuffer[47] = ntpPacketId; //Transmit timestamp | |
udp.beginPacket(ntpIP, 123); | |
udp.write(packetBuffer, NTP_PACKET_SIZE); | |
udp.endPacket(); | |
} | |
void setDst() { | |
setenv("TZ", tzStr.c_str(), 1); | |
tzset(); | |
String s=String(tzname[0]); | |
if (tzStr.indexOf(',')==-1) { | |
tzDstObs=false; | |
lcdDispText(0,3, "DST not observed"); | |
} | |
else { | |
tzDstObs=true; | |
s+= "/"+String(tzname[1]); | |
} | |
lcdDispText(0,2, "TZ="+s); | |
} | |
void setupAP() { | |
WiFi.softAP(apSsid, apPass); | |
delay(200); | |
WiFi.softAPConfig(apIP, apIP, apSNM); | |
} | |
void setupWifi() { | |
uint32_t milli; | |
lcdDispText(0,1,""); | |
clearGraph(); | |
lcdDispText(0,2, wifiSSID); | |
WiFi.disconnect(true); | |
WiFi.config(0u, 0u, 0u); //clear static IP | |
WiFi.mode(WIFI_AP_STA); | |
if (staIpFixed) WiFi.config(staIP, staGateway, staSNM, staDNS); | |
if (wifiSSID.length()>0) { | |
WiFi.begin(wifiSSID.c_str(), wifiPass.c_str()); | |
milli=millis(); | |
int16_t st; | |
do { | |
st=WiFi.status(); | |
delay(20); | |
} while (st!= WL_CONNECTED && millis()-milli<15000); | |
if (st==WL_CONNECTED) { | |
lcdDispText(12,2, " OK"); | |
lcdDispText(0,3, WiFi.localIP().toString()); | |
delay(5000); | |
} | |
else { | |
lcdDispText(12,2, " NG"); | |
delay(1000); | |
} | |
} | |
} | |
void setupNtp() { | |
if (ntpByName) { | |
lcdDispText(0,2, ntpServerName); | |
WiFi.hostByName(ntpServerName.c_str(), ntpIP); | |
} | |
else { | |
lcdDispText(0,2, "NTP by IP"); | |
ntpIP=ntpIPbyIP; | |
} | |
ntpFirstOffset=true; | |
lcdDispText(0,3, ntpIP.toString()); | |
delay(4000); | |
} | |
boolean wifiConnected() { | |
if (WiFi.status()==WL_CONNECTED) return true; | |
else return false; | |
} | |
#if !defined(DEVELOP) | |
void dbgPrint(String s) { | |
if (serialMon) Serial.print(s); | |
} | |
#endif | |
void dbgPrintln(String s) { | |
dbgPrint(s+F("\r\n")); | |
} | |
void handleNotFound(AsyncWebServerRequest *request){ | |
request->send(404, "text/plain", F("File Not Found\n\n")); | |
} | |
void dispStatus(AsyncWebServerRequest *request) { | |
String fmt=dateFmtMD[dateFormat] + " %H:%M:%S."; | |
struct tm tim; | |
struct timeval tv; | |
char buf[40]={0}; | |
String s= msgHome + | |
"<p>Pendulum Count=" + String(countPend) + " History=" + String(numHistory[0]) + | |
"<br>Error=" + csecToSecF(csecPendError) + " [" + csecToSecF(csecPendErrorMin) + ", " + csecToSecF(csecPendErrorMax) + "] sec" + | |
"<br>Current=" + String(adjLevel) + " [" + String(adjLevelMin) + ", " + String(adjLevelMax) + "] Ave=" + String(cadjLevelAve/100.0) + | |
"<br>Local IP=" + WiFi.localIP().toString() + "<br>NTP IP=" + ntpIP.toString() + (ntpByName?" (Pool)":"") + | |
"<br>Control Enabled=" + adjOn + " OnOff Mode=" + onoffMode + | |
"<br>Sensor: Min Off=" + String(minSensOffDuration) + " Min On=" + String(minSensOnDuration) + " msec" + | |
"<br>Penddulum window=" + String(pendWindow) + " msec"; | |
strftime(buf, 20, fmt.c_str(), &tmAtRef0Local); | |
s+= "<br>Reference 0=" + String(buf) + preZero3(tvAtRef0.tv_usec/1000); | |
strftime(buf, 20, fmt.c_str(), &tmAtResetMMLocal); | |
s+= "<br>M/M. Reset=" + String(buf) + preZero3(tvAtResetMM.tv_usec/1000); | |
gettimeofday(&tv, NULL); | |
localtime_r(&tv.tv_sec, &tim); | |
strftime(buf, 20, fmt.c_str(), &tim); | |
s+= "<br>Now=" + String(buf) + preZero3(tv.tv_usec/1000); | |
#if defined(DEVELOP) | |
s+= "<br>Free Heap=" + String((int)esp_get_free_heap_size()) + " bytes" + | |
"<br>Task1 Free Stack=" + uxTaskGetStackHighWaterMark(tskHandle1) + " bytes" + | |
"<br>Loop Free Stack=" + uxTaskGetStackHighWaterMark(xTaskGetCurrentTaskHandle()) + " bytes" + | |
#endif | |
"</p>" + msgEnd; | |
request->send (200, textHtml, s); | |
} | |
String csecToSecF(int32_t csec) { | |
String s; | |
if (csec<0) s="-"; | |
else s=""; | |
return s + String(abs(csec)/100) + "." + preZero2((int)(abs(csec)%100)); | |
} | |
String preZero2(int n) { | |
String s=String(n); | |
if (s.length()==1) return "0"+s; | |
else return s; | |
} | |
String preZero3(int n) { | |
String s=String(n); | |
int len=s.length(); | |
if (len==1) return "00"+s; | |
else if (len==2) return "0"+s; | |
else return s; | |
} | |
void hand0Sec(AsyncWebServerRequest *request) { | |
String st; | |
if (!refSet) { | |
st=F("<h2>Time Ref not set</h2>"); | |
request->send (200, textHtml, msgHome + st + msgEnd); | |
} | |
else { | |
funcPtrYes=hand0SecExe; | |
funcPtrNo=dispStatus; | |
st=F("<h2>Set 0 second?</h2>"); | |
request->send (200, textHtml, msgHead + st + msgYesNo + msgEnd); | |
} | |
} | |
void hand0SecExe(AsyncWebServerRequest *request) { | |
struct tm tl; | |
struct timeval tv; | |
gettimeofday(&tv, NULL); | |
localtime_r(&tv.tv_sec, &tl); | |
time_t secDiff; | |
if (tl.tm_sec>40) secDiff=60-tl.tm_sec; //Clock hand ahead | |
else secDiff=-tl.tm_sec; //Clock hand behind | |
if (tvAtRef0.tv_usec<tv.tv_usec) tvAtRef0.tv_sec -= 1; | |
tvAtRef0.tv_sec += secDiff; | |
tvAtRef0.tv_usec -= tv.tv_usec; | |
updateError(); | |
csecPendErrorLast=csecPendError; | |
String st="<h2>Ref0 adjusted</h2>"; | |
request->send (200, textHtml, msgHome + st + msgEnd); | |
} | |
void resetMinMax(AsyncWebServerRequest *request) { | |
funcPtrYes=resetMinMaxExe; | |
funcPtrNo=dispStatus; | |
String st=F("<h2>Reset Min/Max for Error and Adj level?</h2>"); | |
request->send (200, textHtml, msgHead + st + msgYesNo + msgEnd); | |
} | |
void resetMinMaxExe(AsyncWebServerRequest *request) { | |
adjLevelMax=adjLevel; | |
adjLevelMin=adjLevel; | |
csecPendErrorMax=csecPendError; | |
csecPendErrorMin=csecPendError; | |
gettimeofday(&tvAtResetMM, NULL); | |
localtime_r(&tvAtResetMM.tv_sec, &tmAtResetMMLocal); | |
String st= "<h2> Error M/M=" + csecToSecF(csecPendError) + " Adj M/M=" + String(adjLevel) + "</h2>"; | |
request->send(200, textHtml, msgHome + st + msgEnd); | |
} | |
void zeroError(AsyncWebServerRequest *request) { | |
funcPtrYes=zeroErrorExe; | |
funcPtrNo=dispStatus; | |
String st=F("<h2>Error set to 0?</h2>"); | |
request->send (200, textHtml, msgHead + st + msgYesNo + msgEnd); | |
} | |
void zeroErrorExe(AsyncWebServerRequest *request) { | |
time_t secErr = csecPendError/100; | |
int32_t usecErr = (csecPendError%100)*10000; | |
int32_t temp=tvAtRef0.tv_usec - usecErr; | |
int32_t carry=0; | |
if (temp>=1000000) { | |
temp -= 1000000; | |
carry=1; | |
} | |
else if (temp<0) { | |
temp += 1000000; | |
carry=-1; | |
} | |
tvAtRef0.tv_usec=temp; | |
tvAtRef0.tv_sec+=carry-secErr; | |
updateError(); | |
csecPendErrorLast=csecPendError; | |
String st=F("<h2>Ref0 adjusted and Error set to 0</h2>"); | |
request->send (200, textHtml, msgHome + st + msgEnd); | |
} | |
void graphNtp(AsyncWebServerRequest *request) { | |
String fmt=dateFmtMD[dateFormat] + " %H:%M:%S"; | |
struct tm tim; | |
struct timeval tv; | |
char buf[40]={0}; | |
int32_t hEnd=ntpNumHistory; | |
int32_t hStart=hEnd-N_NTP_HISTORY+1; | |
if (hStart<0) hStart=0; | |
String offsetData = "["; | |
String line3Data = "["; | |
String line4Data = "["; | |
String delayData = "["; | |
String passFailData= "["; | |
String secLabel="["; | |
for (int32_t i=hStart; i<=hEnd; i++) { | |
int32_t j=i%N_NTP_HISTORY; | |
offsetData += String(ntpOffsetHistory[j]) + ","; | |
line3Data += String(ntpData3History[j]) + ","; | |
line4Data += String(ntpData4History[j]) + ","; | |
delayData += String(ntpDelayHistory[j]) + ","; | |
passFailData += String(ntpPassHistory[j]) + ","; | |
secLabel += String(ntpTimeHistory[j]-ntpTimeHistory[hStart%N_NTP_HISTORY]) + ","; | |
if ((int)esp_get_free_heap_size()<MIN_HEAP) break; | |
} | |
offsetData += "];"; | |
line3Data += "];"; | |
line4Data += "];"; | |
delayData += "];"; | |
passFailData += "];"; | |
secLabel += "];"; | |
String stButton = "<form action='GYN' name='formGYN' onchange='changeVal()'><input type='submit' value='Reload'>\ | |
Y1:<select name='Y1' style='font-size:50pt;width:230px;margin:0px'>"; | |
String sel; | |
for (byte i=0; i<4; i++) { | |
sel=(ngY1scaleIdx==i)?"selected":""; | |
stButton += "<option " + sel + " value='" + String(i) + "'>" + String(ngY1scaleNum[i])+"ms" + "</option>"; | |
} | |
stButton += "</select></form> "; | |
stButton += "<form action='GR'><input type='submit' value='Accuracy'></form> \ | |
<form action='MM'><input type='submit' value='Home'></form>"; | |
localtime_r(&ntpTimeHistory[hStart%N_NTP_HISTORY], &tim); | |
strftime(buf, 40, fmt.c_str(), &tim); | |
String stgT = "<br>" + verLetter+verNum + " NTP status from "+ buf; | |
String stg = msgChartHead + "var ctx = document.getElementById('myChart').getContext('2d');"; | |
stg += "var offsetData=" + offsetData; | |
stg += "var line3Data=" + line3Data; | |
stg += "var line4Data=" + line4Data; | |
stg += "var delayData=" + delayData; | |
stg += "var passFailData=" + passFailData; | |
stg += "var secLabel=" + secLabel; | |
stg += "var xLabel=[];var ptType=[]; var ptDelay=[]; var ptLine3=[]; var ptLine4=[]; var i; var y1max=" + String(ngY1scaleNum[ngY1scaleIdx]) + "; var y2max=" + String(ngY1scaleNum[ngY1scaleIdx]*10) +";"; | |
stg += "for(i=0; i<=" + String(hEnd-hStart) + "; i++) {xLabel.push(" + String(ntpTimeHistory[hStart%N_NTP_HISTORY]) + "*1000 + secLabel[i]*1000);"; | |
stg += "delayData[i] /=1000; offsetData[i] /=1000; line3Data[i] /=1000; line4Data[i] /=1000;"; | |
stg += "ptType.push('circle');ptDelay.push('circle');ptLine3.push('circle');ptLine4.push('circle');"; | |
stg += "if(offsetData[i]>y1max){offsetData[i]=y1max;ptType[i]='triangle';} if(offsetData[i]<-y1max){offsetData[i]=-y1max;ptType[i]='triangle';}"; | |
stg += "if(delayData[i]>y2max){delayData[i]=y2max;ptDelay[i]='triangle';}"; | |
stg += "if(line3Data[i]>y2max){line3Data[i]=y2max;ptLine3[i]='triangle';} if(line3Data[i]<-y2max){line3Data[i]=-y2max;ptLine3[i]='triangle';}"; | |
stg += "if(line4Data[i]>y2max){line4Data[i]=y2max;ptLine4[i]='triangle';}"; | |
stg += "if(passFailData[i]==0){ptDelay[i]='crossRot';}}"; | |
stg += "var offsetLine={label:'<-Offset/min', showLine:true, pointRadius:1, lineTension:0, fill:false, borderWidth:3, borderColor:'blue', pointStyle:ptType, data:offsetData, yAxisID:'y1'};"; | |
stg += "var delayLine={label:'Delay->', showLine:false, pointRadius:4, lineTension:0, fill:false, borderWidth:3, borderColor:'red', pointStyle:ptDelay, data:delayData, yAxisID:'y2'};"; | |
stg += "var line3={label:'Offset->', showLine:false, pointRadius:4, lineTension:0, fill:false, borderWidth:3, borderColor:'green', pointStyle:ptLine3, data:line3Data, yAxisID:'y2'};"; | |
stg += "var line4={label:'Limit->', showLine:true, pointRadius:1, lineTension:0, fill:false, borderWidth:3, borderColor:'red', pointStyle:ptLine4, data:line4Data, yAxisID:'y2'};"; | |
stg += "var opt={scales: {xAxes: [{type:'time', time:{displayFormats:{millisecond:'kk:mm', second:'kk:mm', minute:'kk:mm', hour:'M/D ha'}}}],"; | |
stg += "yAxes: [{id:'y1',type:'linear',position:'left',ticks:{min:-y1max, max:y1max}}, {id:'y2', type:'linear', position:'right', ticks:{min:-y2max, max:y2max}}]}};"; | |
stg += "var config={type:'line', options: opt, data:{datasets: [offsetLine, line3, delayLine, line4], labels:xLabel}};"; | |
stg += "var ntpChart = new Chart(ctx,config);"; | |
stg += "function changeVal() {"; | |
stg += "document.formGYN.submit();"; | |
stg += "}"; | |
stg += "</script>"; | |
String st = "NTP IP=" + ntpIP.toString() + (ntpByName?" (Pool)":""); | |
st += "<br>NTP Sync=" + String(ntpSynchCount) + " Inc=" + String(ntpIntervalInc) + " Dec=" + String(ntpIntervalDec); | |
st += "<br>NTP Interval=" + String(ntpInterval) + "min"; | |
snprintf(buf, 40, "<br>NTP Delay Limit=%6.2fms", ((float)ntpDelayLimitUs)/1000.0); | |
st += String(buf); | |
snprintf(buf, 40, "<br>Offset=%6.2f, Offset/min=%6.2fms", ((float)ntpOffset)/1000.0, ((float)ntpOffsetPerMin)/1000.0); | |
st += String(buf); | |
snprintf(buf, 40, "<br>Delay=%6.2f, Average=%6.2fms", ((float)ntpDelay)/1000.0, ntpDelayAve/1000.0); | |
st += String(buf); | |
localtime_r(&tvAtNtpFail.tv_sec, &tim); | |
strftime(buf, 20, fmt.c_str(), &tim); | |
st += "<br>NTP Fail=" + String(buf); | |
localtime_r(&tvAtNtpSuccess.tv_sec, &tim); | |
strftime(buf, 40, fmt.c_str(), &tim); | |
st += "<br>NTP Success=" + String(buf); | |
gettimeofday(&tv, NULL); | |
localtime_r(&tv.tv_sec, &tim); | |
strftime(buf, 40, fmt.c_str(), &tim); | |
st += "<br>Now=" + String(buf) + "<br>-- NTP Server History --"; | |
for (uint16_t i=0; i<N_NTP_IP_HISTORY; i++) { | |
uint16_t idx=(ntpIPHistoryIdx-i)%N_NTP_IP_HISTORY; | |
localtime_r(&ntpIPTimeHistory[idx], &tim); | |
strftime(buf, 40, fmt.c_str(), &tim); | |
st += "<br>" + String(buf) + " " + ntpIPHistory[idx].toString(); | |
if (idx==0) break; | |
} | |
#if defined(DEVELOP) | |
st += "<br>Free Heap=" + String((int)esp_get_free_heap_size()) + " bytes" + "<br>Free Stack=" + String(uxTaskGetStackHighWaterMark(xTaskGetCurrentTaskHandle())) + " bytes"; | |
#endif | |
request->send (200, textHtml, msgHead + stButton + stgT + stg + st + msgEnd); | |
} | |
void setNtpGraphScale(AsyncWebServerRequest *request) { | |
if (request->hasParam("Y1")) { | |
String rq=request->getParam("Y1")->value(); | |
ngY1scaleIdx=(byte)atoi(rq.c_str()); | |
} | |
graphNtp(request); | |
} | |
void graphError(AsyncWebServerRequest *request) { | |
char buf[20]={0}; | |
struct tm tsl, tel; | |
time_t tEnd=time(NULL); | |
time_t tStart=tEnd-graphSpan; | |
byte dataSet; | |
String sel; | |
gYCscaleMM="suggestedMin:-30, suggestedMax:30"; | |
String msgG = "<form action='GU' name='formGU' onchange='changeVal()'>"; | |
msgG += "X:<select name='XS' style='font-size:50pt;width:110px;margin:0px'>"; | |
for (byte i=1; i<=24; i++) { | |
sel=(graphSpan/gSpanUnit[gSpanUnitIdx]==i)?"selected":""; | |
msgG += "<option " + sel + " value='" + String(i) + "'>" + String(i) + "</option>"; | |
} | |
msgG += "</select><select name='XU' style='font-size:50pt;width:100px;margin:0px'>"; | |
for (byte i=0; i<3; i++) { | |
sel=(gSpanUnitIdx==i)?"selected":""; | |
msgG += "<option " + sel + " value='" + String(i) + "'>" + gSpanUnitStr[i] + "</option>"; | |
} | |
msgG += "</select> A:<select name='YE' style='font-size:50pt;width:200px;margin:0px'>"; | |
for (byte i=0; i<5; i++) { | |
sel=(gYEscaleIdx==i)?"selected":""; | |
msgG += "<option " + sel + " value='" + String(i) + "'>" + gYEscaleStr[i] + "</option>"; | |
} | |
msgG += "</select> C:<select name='YC' style='font-size:50pt;width:200px'>"; | |
for (byte i=0; i<5; i++) { | |
sel=(gYCscaleIdx==i)?"selected":""; | |
msgG += "<option " + sel + " value='" + String(i) + "'>" + gYCscaleStr[i] + "</option>"; | |
} | |
msgG += "</select><br><input type='submit' value='Reload'></form> \ | |
<form action='GN'><input type='submit' value=' NTP '></form> \ | |
<form action='MM'><input type='submit' value='Home'></form>"; | |
if (gYEscaleIdx==0) gYEscaleMM="suggestedMin:-5, suggestedMax:5"; | |
else gYEscaleMM="min:-" + String(gYEscale) + ", max:" + String(gYEscale); | |
if (gYCscaleIdx==0) gYCscaleMM="suggestedMin:-500, suggestedMax:500"; | |
else gYCscaleMM="min:-" + String(gYCscale) + ", max:" + String(gYCscale); | |
if (numHistory[0]<N_HISTORY) dataSet=0; | |
else if (tHistory[0][(numHistory[0]+1)%N_HISTORY]<tStart) dataSet=0; | |
else dataSet=1; | |
int32_t hEnd=numHistory[dataSet]; | |
int32_t hStart; | |
if (graphSpan>14*24*3600) { //longest | |
if (numHistory[dataSet]<N_HISTORY) hStart=0; | |
else hStart=hEnd-N_HISTORY; | |
} | |
else { | |
int16_t count=N_HISTORY; | |
if (numHistory[dataSet]<N_HISTORY) count=numHistory[dataSet]+1; | |
hStart=hEnd; | |
for (int16_t i=0; i<count; i++) { | |
if (tHistory[dataSet][(N_HISTORY+hEnd-i)%N_HISTORY]<tStart) break; | |
hStart=hEnd-i; | |
} | |
} | |
tStart=tHistory[dataSet][hStart%N_HISTORY]; | |
String outData = "["; | |
String errData = "["; | |
String secLabel="["; | |
for (int32_t i=hStart; i<=hEnd; i++) { | |
int32_t j=i%N_HISTORY; | |
outData += String(adjHistory[dataSet][j]) + ","; | |
errData += String(errorHistory[dataSet][j]) + ","; | |
secLabel += String(tHistory[dataSet][j]-tStart) + ","; | |
if ((int)esp_get_free_heap_size()<MIN_HEAP) break; | |
} | |
outData += "];"; | |
errData += "];"; | |
secLabel += "];"; | |
localtime_r(&tStart, &tsl); | |
strftime(buf, 20, (dateFmtMD[dateFormat] + " %H:%M").c_str(), &tsl); | |
String stgT = "<br>" + verLetter+verNum + " From "+ buf + " Dataset " + String(dataSet); | |
String stg = msgChartHead; | |
stg += "var ctx = document.getElementById('myChart').getContext('2d');"; | |
stg += "var errData=" + errData; | |
stg += "var outData=" + outData; | |
stg += "var secLabel=" + secLabel; | |
stg += "var xLabel=[]; var i;"; | |
stg += "for (i=0; i<=" + String(hEnd-hStart) + "; i++) {xLabel.push(" + String(tStart) + "*1000 + secLabel[i]*1000);errData[i]/=100;}"; | |
stg += "var errLine={label:'<-Accuracy(sec)', showLine:true, lineTension:0, fill:false, borderWidth:4, borderColor:'blue', pointRadius:0, data:errData, yAxisID:'yE'};"; | |
stg += "var outLine={label:'Current->', showLine:true, lineTension:0, fill:false, borderWidth:2, borderColor:'red', pointRadius:0, data:outData, yAxisID:'yA'};"; | |
stg += "var opt={scales: {xAxes: [{type: 'time', time:{min:" + String(gmtNow-graphSpan) + "*000,displayFormats:{millisecond:'kk:mm',second:'kk:mm',minute:'kk:mm',hour:'M/D ha',}}}],"; | |
stg += "yAxes: [{id:'yE',type:'linear',position:'left',ticks:{" + gYEscaleMM + "}},{id:'yA',type:'linear',position:'right',ticks:{" + gYCscaleMM + "}}]}};"; | |
stg += "var config={type:'line', options: opt, data: {datasets: [errLine, outLine], labels: xLabel}};"; | |
stg += "if (errChart) errChart.destroy();"; | |
stg += "else var errChart = new Chart(ctx,config);"; | |
stg += "function changeVal() {"; | |
stg += "document.formGU.submit();"; | |
stg += "}"; | |
stg += "</script>"; | |
String st = "Pend Ct=" + String(countPend) + " Hist=" + String(numHistory[0]) + | |
"<br>Error=" + csecToSecF(csecPendError) + " [" + csecToSecF(csecPendErrorMin) + ", " + csecToSecF(csecPendErrorMax) + "] sec" + | |
"<br>Current=" + String(adjLevel) + " [" + String(adjLevelMin) + ", " + String(adjLevelMax) + "] Ave=" + String(cadjLevelAve/100.0); | |
#if defined(DEVELOP) | |
st += "<br>Free Heap=" + String((int)esp_get_free_heap_size()) + " bytes" + "<br>Free Stack=" + String(uxTaskGetStackHighWaterMark(xTaskGetCurrentTaskHandle())) + " bytes"; | |
#endif | |
request->send(200, textHtml, msgHead + msgG + stgT + stg + st + msgEnd); | |
} | |
void updateErroGraph(AsyncWebServerRequest *request) { | |
if (request->hasParam("YC")) { | |
String rq=request->getParam("XU")->value(); | |
gSpanUnitIdx=(byte)atoi(rq.c_str()); | |
rq=request->getParam("XS")->value(); | |
graphSpan=atoi(rq.c_str())*gSpanUnit[gSpanUnitIdx]; | |
rq=request->getParam("YE")->value(); | |
gYEscaleIdx=atoi(rq.c_str()); | |
gYEscale=gYEscaleNum[gYEscaleIdx]; | |
rq=request->getParam("YC")->value(); | |
gYCscaleIdx=atoi(rq.c_str()); | |
gYCscale=gYCscaleNum[gYCscaleIdx]; | |
} | |
graphError(request); | |
} | |
void adjControl(AsyncWebServerRequest *request) { | |
funcPtrWeb=adjControlExe; | |
funcReqWeb=request; | |
} | |
void adjControlExe(AsyncWebServerRequest *request) { | |
if (adjOn) { | |
adjOn=false; | |
lcdDispText(0,1, "OUT= "); | |
} | |
else { | |
adjOn=true; | |
lcdDispText(0,1, " "); | |
} | |
String st = "<h2>Control= " + String(adjOn) + "</h2>"; | |
request->send (200, textHtml, msgHome + st + msgEnd); | |
} | |
void adjUp(AsyncWebServerRequest *request){ | |
if (adjLevel<=ADJ_MAX-5*(ADJ_MAX+1)*5/512) adjLevel += 5*(ADJ_MAX+1)*5/512; | |
cadjLevel=adjLevel*100; | |
drvCoil(adjLevel); | |
dispAdj(request); | |
} | |
void adjDwn(AsyncWebServerRequest *request){ | |
if (adjLevel>=ADJ_MIN+5*(1-ADJ_MIN)*5/512) adjLevel -= 5*(1-ADJ_MIN)*5/512; | |
cadjLevel=adjLevel*100; | |
drvCoil(adjLevel); | |
dispAdj(request); | |
} | |
void dispAdj(AsyncWebServerRequest *request) { | |
funcPtrWeb=dispAdjExe; | |
funcReqWeb=request; | |
} | |
void dispAdjExe(AsyncWebServerRequest *request) { | |
lcdDispNum4n(9,1,adjLevel); | |
String st = "<h2>Current= " + String(adjLevel) + " [" + String(adjLevelMin) + ", " + String(adjLevelMax) + "]</h2>"; | |
request->send (200, textHtml, msgHome + st + msgEnd); | |
} | |
void confMenu(AsyncWebServerRequest *request) { | |
setConfigStr(); | |
setConfigMsg(); | |
request->send (200, textHtml, msgConf + msgEnd); | |
} | |
void setOutput(AsyncWebServerRequest *request) { | |
if (request->hasParam("OV")) { | |
String rq=request->getParam("OV")->value(); | |
adjLevel = atoi(rq.c_str()); | |
cadjLevel=adjLevel*100; | |
drvCoil(adjLevel); | |
dispAdj(request); | |
} | |
else request->send (200, textHtml, msgConf + "Illegal format" + msgEnd); | |
} | |
void setError(AsyncWebServerRequest *request) { | |
if (request->hasParam("EV")) { | |
funcPtrYes=setErrorExe; | |
funcPtrNo=dispStatus; | |
String rq=request->getParam("EV")->value(); | |
float fnum=atof(rq.c_str()); | |
secTmAdj=(int32_t)fnum; | |
usecTmAdj=(int32_t)((fnum-(float)secTmAdj)*1000000); | |
String st="<h2>Adjust Error by " + rq + "sec?</h2>"; | |
request->send (200, textHtml, msgHead + st + msgYesNo + msgEnd); | |
} | |
else request->send (200, textHtml, msgConf + "Illegal format" + msgEnd); | |
} | |
void setErrorExe(AsyncWebServerRequest *request) { | |
int32_t temp=tvAtRef0.tv_usec + usecTmAdj; | |
int32_t carry=0; | |
if (temp>=1000000) { | |
temp -= 1000000; | |
carry=1; | |
} | |
else if (temp<0) { | |
temp += 1000000; | |
carry=-1; | |
} | |
tvAtRef0.tv_usec=temp; | |
tvAtRef0.tv_sec += carry+secTmAdj; | |
updateError(); | |
csecPendErrorLast=csecPendError; | |
String st="<h2>Ref0 adjusted and Error set to " + csecToSecF(csecPendError) + "</h2>"; | |
request->send (200, textHtml, msgHome + st + msgEnd); | |
} | |
void applyConfig(AsyncWebServerRequest *request) { | |
if (request->hasParam("DF")) { //last parameter (date format) | |
pendCycle = (int32_t)atoi(request->getParam("PC")->value().c_str()); | |
pendDur = (int32_t)atoi(request->getParam("PD")->value().c_str()); | |
pendTuneCycle = atoi(request->getParam("PT")->value().c_str()); | |
setupMechTiming(); | |
kP = atoi(request->getParam("KP")->value().c_str()); | |
kI = atoi(request->getParam("KI")->value().c_str()); | |
liftFactor = atoi(request->getParam("LF")->value().c_str()); | |
wifiSSID = request->getParam("WS")->value(); | |
wifiPass = request->getParam("WP")->value(); | |
if (request->getParam("IF")->value().charAt(0)=='1') { | |
staIpFixed=true; | |
} | |
else { | |
staIpFixed=false; | |
} | |
staIP.fromString(request->getParam("IL")->value()); | |
staGateway.fromString(request->getParam("IG")->value()); | |
staDNS.fromString(request->getParam("ID")->value()); | |
staSNM.fromString(request->getParam("IS")->value()); | |
boolean ntpByNameLast=ntpByName; | |
if (request->getParam("NF")->value().charAt(0)=='0') ntpByName=true; | |
else ntpByName=false; | |
ntpIPbyIP.fromString(request->getParam("NI")->value()); | |
ntpServerName=request->getParam("NN")->value(); | |
ntpMinInterval=(uint16_t)atoi(request->getParam("NV")->value().c_str()); | |
ntpMaxInterval=(uint16_t)atoi(request->getParam("NX")->value().c_str()); | |
if (ntpInterval>ntpMaxInterval) ntpInterval=ntpMaxInterval; | |
if (ntpInterval<ntpMinInterval) ntpInterval=ntpMinInterval; | |
if (ntpByNameLast != ntpByName) ntpChange(); | |
String tzStrLast=tzStr; | |
tzStr=request->getParam("TZ")->value(); | |
if (strcmp(tzStrLast.c_str(), tzStr.c_str())!=0) setDst(); | |
dateFormat=(byte)atoi(request->getParam("DF")->value().c_str()); | |
saveConfigFile(); | |
request->send (200, textHtml, msgHome + "<br><h2>Done</h2>" + msgEnd); | |
} | |
else { | |
request->send (200, textHtml, msgHome + "<br><h2>Transmission Error</h2>" + msgEnd); | |
} | |
} | |
boolean saveConfigFile() { | |
File file = SPIFFS.open(CONFIG_FILE, FILE_WRITE); | |
if (!file) return false; | |
setConfigStr(); | |
file.println(fileVer); | |
file.println(strPC); | |
file.println(strPD); | |
file.println(strPT); | |
file.println(strKP); | |
file.println(strKI); | |
file.println(strLF); | |
file.println(strWS); | |
file.println(strWP); | |
file.println(strIF); | |
file.println(strIL); | |
file.println(strIG); | |
file.println(strID); | |
file.println(strIS); | |
file.println(strNF); | |
file.println(strNI); | |
file.println(strNN); | |
file.println(strNV); | |
file.println(strNX); | |
file.println(strTZ); | |
file.println(strDF); | |
file.close(); | |
return true; | |
} | |
void setConfigStr() { | |
strPC=String(pendCycle); | |
strPD=String(pendDur); | |
strPT=String(pendTuneCycle); | |
strKP=String(kP); | |
strKI=String(kI); | |
strLF=String(liftFactor); | |
strWS=wifiSSID; | |
strWP=wifiPass; | |
strIF=staIpFixed?"1":"0"; | |
strIL=staIP.toString(); | |
strIG=staGateway.toString(); | |
strID=staDNS.toString(); | |
strIS=staSNM.toString(); | |
strNF=ntpByName?"0":"1"; | |
strNI=ntpIPbyIP.toString(); | |
strNN=ntpServerName; | |
strNV=String(ntpMinInterval); | |
strNX=String(ntpMaxInterval); | |
strTZ=tzStr; | |
strDF=String(dateFormat); | |
} | |
void confDefault(AsyncWebServerRequest *request) { | |
funcPtrYes=confDefaultExe; | |
funcPtrNo=dispStatus; | |
request->send (200, textHtml, msgHead + "<h2>Use Factory Setting?</h2><br><br>" + msgYesNo + msgEnd); | |
} | |
void confDefaultExe(AsyncWebServerRequest *request) { | |
loadDefaultConfig(); | |
setConfigMsg(); | |
request->send (200, textHtml, msgConf + "<br><h2>Default loaded but NOT applied</h2>" + msgEnd); | |
} | |
void setConfigPara() { | |
pendCycle=(int32_t)(atoi(strPC.c_str())); | |
pendDur=(int32_t)(atoi(strPD.c_str())); | |
pendTuneCycle=(int16_t)(atoi(strPT.c_str())); | |
kP=(int16_t)(atoi(strKP.c_str())); | |
kI=(int16_t)(atoi(strKI.c_str())); | |
liftFactor=(int16_t)(atoi(strLF.c_str())); | |
wifiSSID=strWS; | |
wifiPass=strWP; | |
staIpFixed=(strIF.charAt(0)=='1'); | |
staIP.fromString(strIL); | |
staGateway.fromString(strIG); | |
staDNS.fromString(strID); | |
staSNM.fromString(strIS); | |
ntpByName=(strNF.charAt(0)=='0'); | |
ntpIPbyIP.fromString(strNI); | |
ntpIP=ntpIPbyIP; | |
ntpServerName=strNN; | |
ntpMinInterval=(int16_t)(atoi(strNV.c_str())); | |
ntpMaxInterval=(int16_t)(atoi(strNX.c_str())); | |
tzStr=strTZ; | |
dateFormat=(byte)atoi(strDF.c_str()); | |
} | |
void loadDefaultConfig() { | |
strPC=String(DEFAULT_PEND_CYCLE); | |
strPD=String(DEFAULT_PEND_DUR); | |
strPT=String(DEFAULT_TUNE_CYCLE); | |
strKP=String(DEFAULT_KP); | |
strKI=String(DEFAULT_KI); | |
strLF=String(DEFAULT_PEND_LF); | |
strWS=DEFAULT_WIFI_SSID; | |
strWP=DEFAULT_WIFI_PASS; | |
strIF=DEFAULT_STA_IP_FIX; | |
strIL=DEFAULT_STA_IP; | |
strIG=DEFAULT_STA_GW; | |
strID=DEFAULT_STA_DNS; | |
strIS=DEFAULT_SUBNET; | |
strNF=DEFAULT_NTP_BY_NAME?"0":"1"; | |
strNI=DEFAULT_NTP_IP; | |
strNN=DEFAULT_NTP_NAME; | |
strNV=String(DEFAULT_NTP_MIN_INTERVAL); | |
strNX=String(DEFAULT_NTP_MAX_INTERVAL); | |
strTZ=DEFAULT_TZ_STR; | |
strDF=String(DEFAULT_DATE_FORMAT); | |
} | |
void setConfigMsg() { | |
msgConf= msgHead + "<script type='text/javascript'>\ | |
function funcFixed() {document.getElementById('IF').style.display = 'inline';\ | |
document.getElementById('radio1').checked = true;}\ | |
function funcDHCP() {document.getElementById('IF').style.display = 'none';\ | |
document.getElementById('radio2').checked = true;}\ | |
function funcNtpF() {document.getElementById('NF').style.display = 'inline';\ | |
document.getElementById('NN').style.display = 'none';\ | |
document.getElementById('radio3').checked = true;}\ | |
function funcNtpN() {document.getElementById('NF').style.display = 'none';\ | |
document.getElementById('NN').style.display = 'inline';\ | |
document.getElementById('radio4').checked = true;}\ | |
window.onload = function() {" + String(strIF.charAt(0)=='1'?"funcFixed();":"funcDHCP();") + | |
String(strNF.charAt(0)=='1'?"funcNtpF();":"funcNtpN();") + | |
"document.getElementById('DF').value='" + strDF.charAt(0) + "';};</script>\ | |
<form action='MM'><input type='submit' value='Home'></form> \ | |
<form action='CD'><input type='submit' value='Factory'></form> \ | |
<form action='CB'><input type='submit' value='Reboot'></form> \ | |
<form id='form1' action='CA'><button type=submit' form='form1'>Apply</button><br>\ | |
<hr width='800px'>\ | |
Pendulum:<input id='PC' type='number' min='1' name='PC' value='" + strPC + "'>cycles=\ | |
<input type='number' min='1' name='PD' value='" + strPD + "'>sec\ | |
<br>Pend Cycle between Tune:<input type='number' min='1' name='PT' value='" + strPT + "'>\ | |
<br>KP:<input type='number' min='0' name='KP' value='" + strKP + "'> \ | |
KI:<input type='number' min='0' name='KI' value='" + strKI + "'>\ | |
Lift Limit:1/<input style='width:50px' type='number' min='1' max='9' name='LF' value='" + strLF + "'>\ | |
<br><hr width='800px'>\ | |
<span>WiFi SSID:</span><input type='text' name='WS' value='" + strWS + "'><br>\ | |
<span>WiFi Pass:</span><input type='text' name='WP' value='" + strWP + "'><br>\ | |
<hr width='800px'>\ | |
<input type='radio' id='radio1' name='IF' value='1' onclick='funcFixed()' />\ | |
<label for='radio1'><span style='width:250px;text-align:left'>Fixed IP</span></label>\ | |
<input type='radio' id='radio2' name='IF' value='0' onclick='funcDHCP()' />\ | |
<label for='radio2'><span style='width:300px;text-align:left'>DHCP</span></label>\ | |
<span id='IF'><br>\ | |
<span>Local IP:</span><input type='text' name='IL' value='" + strIL + "'><br>\ | |
<span>Gateway IP:</span><input type='text' name='IG' value='" + strIG + "'><br>\ | |
<span>DNS IP:</span><input type='text' name='ID' value='" + strID + "'><br>\ | |
<span>Subnet Mask:</span><input type='text' name='IS' value='" + strIS + "'></span><br>\ | |
<hr width='800px'>\ | |
<input type='radio' id='radio3' name='NF' value='1' onclick='funcNtpF()' />\ | |
<label for='radio3'><span style='width:250px;text-align:left'>NTP IP</span></label>\ | |
<input type='radio' id='radio4' name='NF' value='0' onclick='funcNtpN()' />\ | |
<label for='radio4'><span style='width:300px;text-align:left'>NTP Name</span></label>\ | |
<span id='NF'><br><span>NTP IP:</span><input type='text' name='NI' value='" + strNI + "'></span>\ | |
<span id='NN'><br><span>NTP Name:</span><input type='text' name='NN' value='" +strNN + "'></span><br>\ | |
Interval Min:<input type='number' min='1' max='1440' name='NV' value='" + strNV + "'> \ | |
Max:<input type='number' min='1' max='1440' step='1' name='NX' value='" + strNX + "'>min<br>\ | |
<hr width='800px'>\ | |
<span>TZ/DST:</span><input type='text' list='tzList' name='TZ' value='" + strTZ + "' placeholder='Select or type in'><br>\ | |
<datalist id='tzList'>\ | |
<option value='" + TZ_STR0 + "'>\ | |
<option value='" + TZ_STR1 + "'>\ | |
<option value='" + TZ_STR2 + "'>\ | |
<option value='" + TZ_STR3 + "'>\ | |
</datalist>\ | |
<span>Date Format:</span><select id='DF' name='DF' form='form1' style='width:500px;font-size:36pt;margin-right:50px'>\ | |
<option value='0'>Select format</option>\ | |
<option value='1'>" + dateStr[1] + "</option>\ | |
<option value='2'>" + dateStr[2] + "</option>\ | |
<option value='3'>" + dateStr[3] + "</option></select><br></form>"; //when changed, make sure to check that bottom alignment | |
#if defined(DEVELOP) | |
msgConf += "<h2>Free Heap=" + String((int)esp_get_free_heap_size()) + " bytes" + | |
"<br>Free Stack=" + String(uxTaskGetStackHighWaterMark(xTaskGetCurrentTaskHandle())) + " bytes</h2>"; | |
#endif | |
} | |
boolean loadConfigFile() { | |
File file = SPIFFS.open(CONFIG_FILE, FILE_READ); | |
if (!file) return false; | |
if (file.available()) { | |
strVer=file.readStringUntil('\r');file.read(); | |
if (strVer.compareTo(fileVer)!=0) { | |
file.close(); | |
return false; | |
} | |
strPC=file.readStringUntil('\r');file.read(); | |
strPD=file.readStringUntil('\r');file.read(); | |
strPT=file.readStringUntil('\r');file.read(); | |
strKP=file.readStringUntil('\r');file.read(); | |
strKI=file.readStringUntil('\r');file.read(); | |
strLF=file.readStringUntil('\r');file.read(); | |
strWS=file.readStringUntil('\r');file.read(); | |
strWP=file.readStringUntil('\r');file.read(); | |
strIF=file.readStringUntil('\r');file.read(); | |
strIL=file.readStringUntil('\r');file.read(); | |
strIG=file.readStringUntil('\r');file.read(); | |
strID=file.readStringUntil('\r');file.read(); | |
strIS=file.readStringUntil('\r');file.read(); | |
strNF=file.readStringUntil('\r');file.read(); | |
strNI=file.readStringUntil('\r');file.read(); | |
strNN=file.readStringUntil('\r');file.read(); | |
strNV=file.readStringUntil('\r');file.read(); | |
strNX=file.readStringUntil('\r');file.read(); | |
strTZ=file.readStringUntil('\r');file.read(); | |
strDF=file.readStringUntil('\r'); | |
file.close(); | |
return true; | |
} | |
} | |
void confLoad(AsyncWebServerRequest *request) { | |
loadConfigFile(); | |
setConfigMsg(); | |
request->send (200, textHtml, msgConf + msgEnd); | |
} | |
void confReboot(AsyncWebServerRequest *request) { | |
funcPtrYes=espReset; | |
funcPtrNo=confMenu; | |
request->send (200, textHtml, msgHead + "<h2>CPU Reset?</h2><br><br>" + msgYesNo + msgEnd); | |
} | |
void espReset(AsyncWebServerRequest *request) { | |
funcPtrYes=dispStatus; | |
request->send (200, textHtml, msgHead + "<h2>CPU Resetting...</h2>" + msgEnd); | |
ESP.restart(); | |
} | |
void ansYes(AsyncWebServerRequest *request) { | |
if (funcPtrYes) funcPtrYes(request); | |
else dispStatus(request); | |
} | |
void ansNo(AsyncWebServerRequest *request) { | |
if (funcPtrNo) funcPtrNo(request); | |
else dispStatus(request); | |
} | |
void sensAdjOn(AsyncWebServerRequest *request) { | |
sensAdjMode=true; | |
oled.clear(); | |
funcPtrYes=sensAdjOff; | |
funcPtrNo=sensAdjOn; | |
request->send (200, textHtml, msgHead + "<h2>End Sensor Adj mode?</h2><br><br>" + msgYesNo + msgEnd); | |
} | |
void sensAdjOff(AsyncWebServerRequest *request) { | |
sensAdjMode=false; | |
dispStatus(request); | |
} | |
void setupServer() { | |
server.on("/", dispStatus); | |
server.on("/TC", adjControl); | |
server.on("/SO", setOutput); | |
server.on("/H0", hand0Sec); | |
server.on("/SE", setError); | |
server.on("/EZ", zeroError); | |
server.on("/MR", resetMinMax); | |
server.on("/ST", dispStatus); | |
server.on("/GU", updateErroGraph); | |
server.on("/GR", graphError); | |
server.on("/GYN", setNtpGraphScale); | |
server.on("/CF", confMenu); | |
server.on("/CA", applyConfig); | |
server.on("/CD", confDefault); | |
server.on("/MM", dispStatus); | |
server.on("/GN", graphNtp); | |
server.on("/CB", confReboot); | |
server.on("/SA", sensAdjOn); | |
server.on("/YS", ansYes); | |
server.on("/NO", ansNo); | |
server.onNotFound(handleNotFound); | |
server.begin(); | |
} | |
void lcdDispText(int16_t col, int16_t row, String s) { | |
lcdDispText2(col, row, s, 128); | |
} | |
void lcdDispText2(int16_t col, int16_t row, String s, int16_t width) { | |
oled.setColor(BLACK); | |
oled.fillRect(col*CHAR_W, row*CHAR_H, width, 18); | |
oled.setColor(WHITE); | |
oled.drawString(col*CHAR_W, row*CHAR_H, s); | |
oled.display(); | |
} | |
void lcdDispNumF(int16_t col, int16_t row, int32_t n) { | |
char pNum[10]={0}; | |
if (n>9999999) snprintf(pNum, 10, "%s", "+>"); | |
else if (n<-999999) snprintf(pNum, 10, "%s", "-<"); | |
else snprintf(pNum, 10, "%08.2f", ((float)n)/100.0); | |
oled.setColor(BLACK); | |
oled.fillRect(col*CHAR_W, row*CHAR_H, 72, 16); | |
oled.setColor(WHITE); | |
oled.drawString(col*CHAR_W, row*CHAR_H, pNum); | |
oled.display(); | |
} | |
void lcdDispNum3n(int16_t col, int16_t row, int32_t n) { | |
char pNum[5]={0}; | |
if (abs(n)<=999) snprintf(pNum, 5, "%3d", n); | |
else snprintf(pNum, 5, "%s", "***"); | |
oled.setColor(BLACK); | |
oled.fillRect(col*CHAR_W, row*CHAR_H, 30, 16); | |
oled.setColor(WHITE); | |
oled.drawString(col*CHAR_W, row*CHAR_H, pNum); | |
oled.display(); | |
} | |
void lcdDispNum4n(int16_t col, int16_t row, int32_t n) { | |
char pNum[6]={0}; | |
if (abs(n)<=9999) snprintf(pNum, 6, "%4d", n); | |
else snprintf(pNum, 6, "%s", "***"); | |
oled.setColor(BLACK); | |
oled.fillRect(col*CHAR_W, row*CHAR_H, 44, 16); | |
oled.setColor(WHITE); | |
oled.drawString(col*CHAR_W, row*CHAR_H, pNum); | |
oled.display(); | |
} | |
void lcdDispTime(int16_t col, int16_t row, byte mode) { //mode1=data and time, mode2=time only | |
struct tm tt; | |
localtime_r(&gmtAtSec, &tt); | |
tzDst=tt.tm_isdst; | |
char buf[20]={0}; | |
char tim[20]={0}; | |
if (mode==1) strftime(buf, 20, dateFmtMD[dateFormat].c_str(), &tt); | |
snprintf(tim, 20, "%s %02d:%02d:%02d", buf, tt.tm_hour, tt.tm_min, tt.tm_sec); | |
oled.setColor(BLACK); | |
oled.fillRect(col*CHAR_W, row*CHAR_H, 128, 16); | |
oled.setColor(WHITE); | |
oled.drawString(col*CHAR_W, row*CHAR_H, tim); | |
if (tzDst==0 && tzDstObs) oled.drawCircle(123, 9, 4); | |
else if (tzDst>0) oled.fillCircle(123, 9, 4); | |
oled.display(); | |
} | |
void lcdDispGraph() { | |
#if defined(HMC5883) || defined(LSM303) | |
if (sensAdjMode) return; | |
#endif | |
int32_t eMax=1; | |
int16_t aMax=1, diff; | |
byte idxA, idxE; | |
int32_t hStart=numHistory[0]-30; //SCREEN_W; //use the last 30 points for scaling | |
if (hStart<0) hStart=0; | |
for (int32_t i=hStart; i<=numHistory[0]; i++) { | |
if (abs(errorHistory[0][i%N_HISTORY])>eMax) eMax=abs(errorHistory[0][i%N_HISTORY]); | |
diff=abs(adjHistory[0][i%N_HISTORY]-adjLevel); | |
if (diff>aMax) aMax=diff; | |
} | |
for (idxE=0; idxE<numgE; idxE++) { | |
if (gscaleE[idxE]>=eMax) break; | |
} | |
if (idxE>=numgE) idxE=numgE-1; | |
for (idxA=0; idxA<numgA; idxA++) { | |
if (gscaleA[idxA]>=aMax) break; | |
} | |
if (idxA>=numgA) idxA=numgA-1; | |
clearGraph(); | |
oled.setColor(WHITE); | |
oled.drawLine(0, GYCENTER, GXORIGIN, GYCENTER); | |
oled.drawLine(GXORIGIN, GYCENTER-16, GXORIGIN, GYCENTER+16); | |
oled.drawLine(GXORIGIN, GYCENTER-10, GXORIGIN+2, GYCENTER-10); | |
oled.drawLine(GXORIGIN, GYCENTER+10, GXORIGIN+2, GYCENTER+10); | |
oled.setFont(ArialMT_Plain_10); | |
String scaleMark; | |
if ((lcdGmode==0) && (gmtAtSec%10<5) || (lcdGmode==1)) { | |
if (gscaleE[idxE]>=100) scaleMark=String(gscaleE[idxE]/100)+"s"; | |
else scaleMark=String(gscaleE[idxE]*10)+"ms"; | |
oled.drawString(GXORIGIN+4, GYCENTER-16, "+"+scaleMark); | |
oled.drawString(GXORIGIN+4, GYCENTER+4, "-"+scaleMark); | |
} | |
else if ((lcdGmode==0) && (gmtAtSec%10>=5) || (lcdGmode==2)) { | |
oled.drawString(GXORIGIN+4, GYCENTER-16, String(gscaleA[idxA]+(adjLevel/10)*10)); | |
oled.drawString(GXORIGIN+4, GYCENTER+4, String(-gscaleA[idxA]+(adjLevel/10)*10)); | |
} | |
oled.setFont(OLEDFONT); | |
time_t endSec=tHistory[0][numHistory[0]%N_HISTORY]; | |
int16_t dotPerHour=3600/tuneInterval; | |
for (int i=1; i<=GRAPH_W; i++) { | |
if (i%4==0) { | |
oled.setPixel(GRAPH_W-i, GTOP); | |
oled.setPixel(GRAPH_W-i, GBOTTOM); | |
} | |
if ((i-1)%dotPerHour==0) { //every 1 hour back from now | |
for (int y=GTOP; y<=GBOTTOM; y++) { | |
if (y%4==0) oled.setPixel(GRAPH_W-i, y); | |
} | |
} | |
} | |
oled.setColor(WHITE); | |
int32_t yE; | |
int16_t yA; | |
hStart=numHistory[0]-GRAPH_W; | |
int16_t offset=0; | |
if (hStart<0) { | |
hStart=0; | |
offset=GRAPH_W-numHistory[0]; | |
} | |
if ((lcdGmode==0) && (gmtAtSec%10<5) || (lcdGmode==1)) { | |
for (int32_t i=hStart; i<=numHistory[0]; i++) { | |
yE=errorHistory[0][i%N_HISTORY]*10/gscaleE[idxE]; | |
if (abs(yE)<=GBOTTOM-GYCENTER) oled.setPixel(GXORIGIN+i-hStart+offset, GYCENTER-yE); | |
if (i==numHistory[0]) oled.drawCircle(GXORIGIN+i-hStart+offset, constrain(GYCENTER-csecPendError*10/gscaleE[idxE], GTOP, GBOTTOM), 2); | |
} | |
for (int x=GXORIGIN; x<GXORIGIN+GRAPH_W; x++) { | |
if (x%2==0) oled.setPixel(x, GYCENTER); | |
} | |
} | |
else if ((lcdGmode==0) && (gmtAtSec%10>=5) || (lcdGmode==2)) { | |
for (int32_t i=hStart; i<=numHistory[0]; i++) { | |
yA=(adjHistory[0][i%N_HISTORY]-adjLevel)*10/gscaleA[idxA]; | |
if (abs(yA)<=GBOTTOM-GYCENTER) oled.setPixel(GXORIGIN+i-hStart+offset, GYCENTER-yA); | |
} | |
if (adjLevel>=0) oled.fillRect(0, GYCENTER-16*adjLevel/ADJ_MAX, GXORIGIN, 16*adjLevel/ADJ_MAX); | |
else oled.fillRect(0, GYCENTER, GXORIGIN, -16*adjLevel/ADJ_MAX); | |
int16_t yZeroPos=GYCENTER+10*adjLevel/gscaleA[idxA]; | |
if (yZeroPos>=GTOP && yZeroPos<=GBOTTOM) { | |
for (int x=GXORIGIN; x<GXORIGIN+GRAPH_W; x++) { | |
if (x%2==0) oled.setPixel(x, yZeroPos); | |
} | |
} | |
} | |
} | |
void clearGraph() { | |
oled.setColor(BLACK); | |
oled.fillRect(0, GTOP-2, 128, GBOTTOM-GTOP+3); | |
} | |
void setupOled() { | |
oled.init(); | |
oled.setFont(ArialMT_Plain_16); | |
} | |
void setupOta() { | |
ArduinoOTA.begin(); | |
ArduinoOTA.onStart([]() { | |
detachInterrupt(digitalPinToInterrupt(SENS_PIN)); //######### most likely not need this. | |
oled.clear(); | |
oled.setFont(ArialMT_Plain_10); | |
oled.setTextAlignment(TEXT_ALIGN_CENTER_BOTH); | |
oled.drawString(oled.getWidth()/2, oled.getHeight()/2 - 10, "OTA Update"); | |
oled.display(); | |
}); | |
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { | |
oled.drawProgressBar(4, 32, 120, 8, progress / (total / 100) ); | |
oled.display(); | |
}); | |
ArduinoOTA.onEnd([]() { | |
oled.clear(); | |
oled.setFont(ArialMT_Plain_10); | |
oled.setTextAlignment(TEXT_ALIGN_CENTER_BOTH); | |
oled.drawString(oled.getWidth()/2, oled.getHeight()/2, "Restart"); | |
oled.display(); | |
}); | |
} | |
void setupFile() { | |
SPIFFS.begin(); | |
if (!SPIFFS.exists(CONFIG_FILE)) { | |
SPIFFS.format(); | |
} | |
} | |
#ifdef HMC5883 | |
void magTrigH() { | |
if (pendDir==0 && (millis()-milliSensRelease)>minSensOffDuration) { | |
pendDir=1; | |
if (!sensAdjMode) { | |
if (tzDst>0) digitalWrite(LED_PIN, LOW); //indicates DST | |
else digitalWrite(LED_PIN, HIGH); | |
} | |
sensTrig=true; | |
milliSensTrig=millis(); | |
if (refSet) { | |
countPend++; | |
csecPend=(int64_t)countPend * (int64_t)pendDur * 100 / (int64_t)pendCycle; | |
} | |
} | |
} | |
void magTrigL() { | |
if (pendDir==1 && (millis()-milliSensTrig)>minSensOnDuration) { | |
pendDir=0; | |
if (!sensAdjMode) { | |
if (tzDst>0) digitalWrite(LED_PIN, HIGH); | |
else digitalWrite(LED_PIN, LOW); | |
} | |
sensRelease=true; | |
milliSensRelease=millis(); | |
} | |
} | |
void setupHMC5883() { | |
Wire.beginTransmission(MAG_ADDR); //start talking | |
Wire.write(0x02); // Set the Register | |
Wire.write(0x00); // Tell the HMC5883 to Continuously Measure | |
Wire.endTransmission(); | |
} | |
void getHMC5883() { | |
Wire.beginTransmission(MAG_ADDR); | |
Wire.write(0x03); //start with register 3. | |
Wire.endTransmission(); | |
Wire.requestFrom(MAG_ADDR, 6); | |
if(Wire.available()>=6) { | |
magX = Wire.read()<<8; //MSB x | |
magX |= Wire.read(); //LSB x | |
magZ = Wire.read()<<8; //MSB z | |
magZ |= Wire.read(); //LSB z | |
magY = Wire.read()<<8; //MSB y | |
magY |= Wire.read(); //LSB y | |
} | |
} | |
byte readHMC5883(byte reg) { | |
Wire.beginTransmission(MAG_ADDR); | |
Wire.write(reg); | |
Wire.endTransmission(); | |
Wire.requestFrom(MAG_ADDR, 1); | |
return Wire.read(); | |
} | |
#endif | |
#ifdef LSM303 | |
void setupLSM303() { | |
writeLSM303(0x20, 0x5C); //XY high perforamnce 80hz conversion | |
writeLSM303(0x21, 0x60); //full scale +/- 16 gauss | |
writeLSM303(0x22, 0x00); //eable continuous conversion | |
writeLSM303(0x23, 0x00); //Z low-power mode, little endian | |
writeLSM303(0x30, 0x8D); //interrupt by Y:4D X:8D, high true, not latched (manual typo) | |
writeLSM303(0x32, MAG_THRESH_LB); //thresh low byte | |
writeLSM303(0x33, MAG_THRESH_HB); //thresh high byte, thresh=HB*256+LB | |
} | |
void writeLSM303(byte reg, byte value) { | |
Wire.beginTransmission(MAG_ADDR); | |
Wire.write(reg); | |
Wire.write(value); | |
i2cStatus = Wire.endTransmission(); | |
} | |
int16_t readLSM303(byte addr) { | |
Wire.beginTransmission(MAG_ADDR); | |
Wire.write(addr); | |
i2cStatus = Wire.endTransmission(); | |
Wire.requestFrom(MAG_ADDR, 2); | |
uint32_t mstart = millis(); | |
while (Wire.available() < 2) { | |
if (i2cTimeout > 0 && (millis() - mstart) > i2cTimeout) return 0; | |
} | |
return (int16_t)(Wire.read() | Wire.read()<<8); | |
} | |
#endif | |
void setupMechTiming() { | |
float pendPeriod=(float)pendDur * 1000.0 / (float)pendCycle; //msec/cycle | |
pendPeriodMs=(uint32_t)pendPeriod; | |
minSensOffDuration = (int32_t)(pendPeriod * (pendPeriod<4000?0.5:0.75)); | |
minSensOnDuration = (int32_t)(pendPeriod / 100.0); | |
pendWindow = (int32_t)(pendPeriod * 1.3 + 1000.0); | |
tuneIntervalMs = (int32_t)pendPeriod * (int32_t)pendTuneCycle; //in msec | |
tuneInterval=tuneIntervalMs / 1000; //in sec | |
} |
0 件のコメント:
コメントを投稿