TeZ
2 years ago
32 changed files with 4478 additions and 0 deletions
-
134Arduino-ESP/Pylon-MQTTeZ-Broker/Pylon-MQTTeZ-Broker.ino
-
818Arduino-ESP/PylontechMonitoring/PylontechMonitoring.ino
-
52Arduino-ESP/PylontechMonitoring/README.md
-
BINArduino-ESP/PylontechMonitoring/Schematics.png
-
113Arduino-ESP/PylontechMonitoring/libraries/Misc/circular_buffer.h
-
96Arduino-ESP/PylontechMonitoring/libraries/Misc/circular_log.h
-
81Arduino-ESP/PylontechMonitoring/libraries/NtpTime/ntp_time.h
-
2Arduino-ESP/PylontechMonitoring/libraries/SimpleTimer/README
-
250Arduino-ESP/PylontechMonitoring/libraries/SimpleTimer/SimpleTimer.cpp
-
126Arduino-ESP/PylontechMonitoring/libraries/SimpleTimer/SimpleTimer.h
-
97Arduino-ESP/PylontechMonitoring/libraries/Time-master/DateStrings.cpp
-
131Arduino-ESP/PylontechMonitoring/libraries/Time-master/Readme.txt
-
321Arduino-ESP/PylontechMonitoring/libraries/Time-master/Time.cpp
-
1Arduino-ESP/PylontechMonitoring/libraries/Time-master/Time.h
-
144Arduino-ESP/PylontechMonitoring/libraries/Time-master/TimeLib.h
-
78Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/Processing/SyncArduinoClock/SyncArduinoClock.pde
-
9Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/Processing/SyncArduinoClock/readme.txt
-
71Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeArduinoDue/TimeArduinoDue.ino
-
87Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeGPS/TimeGPS.ino
-
135Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeNTP/TimeNTP.ino
-
156Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeNTP_ESP8266WiFi/TimeNTP_ESP8266WiFi.ino
-
55Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeRTC/TimeRTC.ino
-
107Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeRTCLog/TimeRTCLog.ino
-
80Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeRTCSet/TimeRTCSet.ino
-
81Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeSerial/TimeSerial.ino
-
108Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeSerialDateStrings/TimeSerialDateStrings.ino
-
78Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeTeensy3/TimeTeensy3.ino
-
34Arduino-ESP/PylontechMonitoring/libraries/Time-master/keywords.txt
-
22Arduino-ESP/PylontechMonitoring/libraries/Time-master/library.json
-
10Arduino-ESP/PylontechMonitoring/libraries/Time-master/library.properties
-
103Arduino-ESP/Pylontez-MQTT/CACert.ino
-
898Arduino-ESP/Pylontez-MQTT/Pylontez-MQTT.ino
@ -0,0 +1,134 @@ |
|||
// MQTT server + client for ESP32
|
|||
#include"sMQTTBroker.h"
|
|||
#include <WiFi.h>
|
|||
#include <PubSubClient.h>
|
|||
#include <WiFiClientSecure.h>
|
|||
#include <MQTT.h>
|
|||
|
|||
const char* ssid = "NOS-3B26"; // Setubal
|
|||
const char* password = "RMKSX2GL"; // Setubal
|
|||
//const char* ssid = "MEO-AA9030"; // Andre
|
|||
//const char* password = "81070ce635"; // Andre
|
|||
|
|||
// Set your Static IP address
|
|||
IPAddress local_IP(192, 168, 1, 123); |
|||
// Set your Gateway IP address
|
|||
IPAddress gateway(192, 168, 1, 1); |
|||
|
|||
IPAddress subnet(255, 255, 0, 0); |
|||
IPAddress primaryDNS(8, 8, 8, 8); // optional
|
|||
IPAddress secondaryDNS(8, 8, 4, 4); // optional
|
|||
|
|||
sMQTTBroker broker; |
|||
|
|||
WiFiClient net; |
|||
//WiFiClientSecure net;
|
|||
MQTTClient mqtt_client(1024); |
|||
|
|||
//WiFiClient espClient;
|
|||
//PubSubClient client(espClient);
|
|||
//long lastMsg = 0;
|
|||
//char msg[50];
|
|||
//int value = 0;
|
|||
|
|||
unsigned long lastMillis = 0; |
|||
|
|||
//////////////////////////////////
|
|||
void WiFiconnect(){ |
|||
int nn = 0; |
|||
while (WiFi.status() != WL_CONNECTED) { // Wait for the Wi-Fi to connect
|
|||
delay(500); |
|||
if(nn<9){ |
|||
Serial.print("."); |
|||
nn++; |
|||
}else{ |
|||
nn=0; |
|||
Serial.println("."); |
|||
} |
|||
} |
|||
|
|||
if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) { |
|||
Serial.println("STA Failed to configure"); |
|||
} |
|||
|
|||
Serial.println("Connection established!"); |
|||
Serial.print("IP address:\t"); |
|||
Serial.println(WiFi.localIP()); |
|||
|
|||
|
|||
|
|||
// Serial.print("\nconnecting...");
|
|||
//// while (!mqtt_client.connect("bsidebotham", "", "")) {
|
|||
// while (!mqtt_client.connect("tezmqlientx2323", "", "")) {
|
|||
// Serial.print(".");
|
|||
// delay(1000);
|
|||
// }
|
|||
//
|
|||
// Serial.println("\nconnected!");
|
|||
//
|
|||
// mqtt_client.subscribe("/hello");
|
|||
//
|
|||
// // MQTT brokers usually use port 8883 for secure connections.
|
|||
//// client.begin("broker.shiftr.io", 8883, net);
|
|||
// mqtt_client.begin("192.168.1.123", 1883, net);
|
|||
// mqtt_client.onMessage(messageReceived);
|
|||
|
|||
} |
|||
|
|||
//////////////////////////////
|
|||
void messageReceived(String &topic, String &payload) { |
|||
Serial.println("incoming: " + topic + " - " + payload); |
|||
|
|||
// Note: Do not use the client in the callback to publish, subscribe or
|
|||
// unsubscribe as it may cause deadlocks when other things arrive while
|
|||
// sending and receiving acknowledgments. Instead, change a global variable,
|
|||
// or push to a queue and handle it in the loop after calling `client.loop()`.
|
|||
} |
|||
|
|||
//////////////////////////////
|
|||
void setup() |
|||
{ |
|||
Serial.begin(115200); |
|||
// const char* ssid = "MEO-AA9030"; // The SSID (name) of the Wi-Fi network you want to connect to
|
|||
// const char* password = "81070ce635"; // The password of the Wi-Fi network
|
|||
|
|||
Serial.print("Connecting to "); |
|||
Serial.println(ssid); |
|||
WiFi.begin(ssid, password); |
|||
|
|||
const unsigned short mqttPort=1883; |
|||
broker.init(mqttPort); |
|||
// all done
|
|||
|
|||
|
|||
WiFiconnect(); |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
} |
|||
|
|||
//////////////////////////////
|
|||
void loop() |
|||
{ |
|||
broker.update(); |
|||
|
|||
// mqtt_client.loop();
|
|||
|
|||
delay(10); // <- fixes some issues with WiFi stability
|
|||
|
|||
// publish a message roughly every second.
|
|||
// if (millis() - lastMillis > 1000) {
|
|||
// if (!mqtt_client.connected()) {
|
|||
// Serial.print("lastError: ");
|
|||
// Serial.println(mqtt_client.lastError());
|
|||
// WiFiconnect();
|
|||
// }
|
|||
// lastMillis = millis();
|
|||
// int rnum = random(1,23);
|
|||
// mqtt_client.publish("/hello", "ciccio" + String(rnum));
|
|||
// }
|
|||
//
|
|||
} |
@ -0,0 +1,818 @@ |
|||
#include <ESP8266WiFi.h>
|
|||
#include <ESP8266mDNS.h>
|
|||
#include <ArduinoOTA.h>
|
|||
#include <ESP8266WebServer.h>
|
|||
#include <SimpleTimer.h>
|
|||
#include <TimeLib.h> //https://github.com/PaulStoffregen/Time
|
|||
#include <ntp_time.h>
|
|||
#include <circular_log.h>
|
|||
|
|||
|
|||
//IMPORTANT: Specify your WIFI settings:
|
|||
//#define WIFI_SSID "NOS-3B26"
|
|||
//#define WIFI_PASS "RMKSX2GL"
|
|||
#define WIFI_SSID "MEO-AA9030"
|
|||
#define WIFI_PASS "81070ce635"
|
|||
|
|||
|
|||
//IMPORTANT: Uncomment this line if you want to enable MQTT (and fill correct MQTT_ values below):
|
|||
//#define ENABLE_MQTT
|
|||
|
|||
#ifdef ENABLE_MQTT
|
|||
//NOTE 1: if you want to change what is pushed via MQTT - edit function: pushBatteryDataToMqtt.
|
|||
//NOTE 2: MQTT_TOPIC_ROOT is where battery will push MQTT topics. For example "soc" will be pushed to: "home/grid_battery/soc"
|
|||
#define MQTT_SERVER "192.168.1.123"
|
|||
#define MQTT_PORT 1883
|
|||
#define MQTT_USER ""
|
|||
#define MQTT_PASSWORD ""
|
|||
#define MQTT_TOPIC_ROOT "home/grid_battery/" //this is where mqtt data will be pushed
|
|||
#define MQTT_PUSH_FREQ_SEC 2 //maximum mqtt update frequency in seconds
|
|||
|
|||
#include <PubSubClient.h>
|
|||
WiFiClient espClient; |
|||
PubSubClient mqttClient(espClient); |
|||
#endif //ENABLE_MQTT
|
|||
|
|||
char g_szRecvBuff[7000]; |
|||
|
|||
IPAddress thisip; |
|||
|
|||
ESP8266WebServer server(80); |
|||
SimpleTimer timer; |
|||
circular_log<7000> g_log; |
|||
bool ntpTimeReceived = false; |
|||
int g_baudRate = 0; |
|||
|
|||
void Log(const char* msg) |
|||
{ |
|||
g_log.Log(msg); |
|||
} |
|||
|
|||
|
|||
/////////////////////////////////
|
|||
void goWiFi(){ |
|||
// connect to WiFi
|
|||
WiFi.mode(WIFI_STA); |
|||
WiFi.persistent(false); //our credentials are hardcoded, so we don't need ESP saving those each boot (will save on flash wear)
|
|||
WiFi.hostname("PylonBattery"); |
|||
Serial.println(); |
|||
Serial.print("connecting to "); |
|||
Serial.println(WIFI_SSID); |
|||
|
|||
WiFi.begin(WIFI_SSID, WIFI_PASS); |
|||
while (WiFi.status() != WL_CONNECTED) { |
|||
delay(500); |
|||
Serial.print("."); |
|||
} |
|||
Serial.println(""); |
|||
Serial.println("WiFi connected"); |
|||
Serial.println("IP address: "); |
|||
Serial.println(WiFi.localIP()); |
|||
Serial.println(""); |
|||
for(int i=1; i<=3; i++){ |
|||
LedBlink(); |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
//////////////////////////////////////////////////
|
|||
void LedBlink(){ |
|||
digitalWrite(LED_BUILTIN, LOW); |
|||
delay(150); |
|||
digitalWrite(LED_BUILTIN, HIGH); // high = off
|
|||
delay(150); |
|||
} |
|||
|
|||
|
|||
|
|||
//////////////////////
|
|||
void setup() { |
|||
|
|||
Serial.begin(115200); |
|||
|
|||
pinMode(LED_BUILTIN, OUTPUT); |
|||
digitalWrite(LED_BUILTIN, HIGH);//high is off
|
|||
|
|||
// connect to WiFi
|
|||
goWiFi(); |
|||
|
|||
// // original wifi code
|
|||
// WiFi.mode(WIFI_STA);
|
|||
// WiFi.persistent(false); //our credentialss are hardcoded, so we don't need ESP saving those each boot (will save on flash wear)
|
|||
// WiFi.hostname("PylonBattery");
|
|||
// WiFi.begin(WIFI_SSID, WIFI_PASS);
|
|||
//
|
|||
// for(int ix=0; ix<10; ix++)
|
|||
// {
|
|||
// if(WiFi.status() == WL_CONNECTED)
|
|||
// {
|
|||
// break;
|
|||
// }
|
|||
//
|
|||
// delay(1000);
|
|||
// }
|
|||
|
|||
// Serial.println("");
|
|||
// Serial.println("WiFi connected");
|
|||
// Serial.println("IP address: ");
|
|||
// thisip = WiFi.localIP();
|
|||
// Serial.println( thisip );
|
|||
|
|||
|
|||
ArduinoOTA.setHostname("AndrePylon"); |
|||
ArduinoOTA.begin(); |
|||
server.on("/", handleRoot); |
|||
server.on("/log", handleLog); |
|||
server.on("/req", handleReq); |
|||
server.on("/jsonOut", handleJsonOut); |
|||
server.on("/reboot", [](){ |
|||
ESP.restart(); |
|||
}); |
|||
|
|||
server.begin(); |
|||
|
|||
syncTime(); |
|||
|
|||
#ifdef ENABLE_MQTT
|
|||
mqttClient.setServer(MQTT_SERVER, MQTT_PORT); |
|||
#endif
|
|||
|
|||
Log("Boot event"); |
|||
} |
|||
|
|||
void handleLog() |
|||
{ |
|||
server.send(200, "text/html", g_log.c_str()); |
|||
} |
|||
|
|||
void switchBaud(int newRate) |
|||
{ |
|||
if(g_baudRate == newRate) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if(g_baudRate != 0) |
|||
{ |
|||
Serial.flush(); |
|||
delay(20); |
|||
Serial.end(); |
|||
delay(20); |
|||
} |
|||
|
|||
char szMsg[50]; |
|||
snprintf(szMsg, sizeof(szMsg)-1, "New baud: %d", newRate); |
|||
Log(szMsg); |
|||
|
|||
Serial.begin(newRate); |
|||
g_baudRate = newRate; |
|||
|
|||
delay(20); |
|||
} |
|||
|
|||
void waitForSerial() |
|||
{ |
|||
for(int ix=0; ix<150;ix++) |
|||
{ |
|||
if(Serial.available()) break; |
|||
delay(10); |
|||
} |
|||
} |
|||
|
|||
int readFromSerial() |
|||
{ |
|||
memset(g_szRecvBuff, 0, sizeof(g_szRecvBuff)); |
|||
int recvBuffLen = 0; |
|||
bool foundTerminator = true; |
|||
|
|||
waitForSerial(); |
|||
|
|||
while(Serial.available()) |
|||
{ |
|||
char szResponse[256] = ""; |
|||
const int readNow = Serial.readBytesUntil('>', szResponse, sizeof(szResponse)-1); //all commands terminate with "$$\r\n\rpylon>" (no new line at the end)
|
|||
if(readNow > 0 && |
|||
szResponse[0] != '\0') |
|||
{ |
|||
if(readNow + recvBuffLen + 1 >= (int)(sizeof(g_szRecvBuff))) |
|||
{ |
|||
Log("WARNING: Read too much data on the console!"); |
|||
break; |
|||
} |
|||
|
|||
strcat(g_szRecvBuff, szResponse); |
|||
recvBuffLen += readNow; |
|||
|
|||
if(strstr(g_szRecvBuff, "$$\r\n\rpylon")) |
|||
{ |
|||
strcat(g_szRecvBuff, ">"); //readBytesUntil will skip this, so re-add
|
|||
foundTerminator = true; |
|||
break; //found end of the string
|
|||
} |
|||
|
|||
if(strstr(g_szRecvBuff, "Press [Enter] to be continued,other key to exit")) |
|||
{ |
|||
//we need to send new line character so battery continues the output
|
|||
Serial.write("\r"); |
|||
} |
|||
|
|||
waitForSerial(); |
|||
} |
|||
} |
|||
|
|||
if(recvBuffLen > 0 ) |
|||
{ |
|||
if(foundTerminator == false) |
|||
{ |
|||
Log("Failed to find pylon> terminator"); |
|||
} |
|||
} |
|||
|
|||
return recvBuffLen; |
|||
} |
|||
|
|||
bool readFromSerialAndSendResponse() |
|||
{ |
|||
const int recvBuffLen = readFromSerial(); |
|||
if(recvBuffLen > 0) |
|||
{ |
|||
server.sendContent(g_szRecvBuff); |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
bool sendCommandAndReadSerialResponse(const char* pszCommand) |
|||
{ |
|||
switchBaud(115200); |
|||
|
|||
if(pszCommand[0] != '\0') |
|||
{ |
|||
Serial.write(pszCommand); |
|||
} |
|||
Serial.write("\n"); |
|||
|
|||
const int recvBuffLen = readFromSerial(); |
|||
if(recvBuffLen > 0) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
//wake up console and try again:
|
|||
wakeUpConsole(); |
|||
|
|||
if(pszCommand[0] != '\0') |
|||
{ |
|||
Serial.write(pszCommand); |
|||
} |
|||
Serial.write("\n"); |
|||
|
|||
return readFromSerial() > 0; |
|||
} |
|||
|
|||
void handleReq() |
|||
{ |
|||
bool respOK; |
|||
if(server.hasArg("code") == false) |
|||
{ |
|||
respOK = sendCommandAndReadSerialResponse(""); |
|||
} |
|||
else |
|||
{ |
|||
respOK = sendCommandAndReadSerialResponse(server.arg("code").c_str()); |
|||
} |
|||
|
|||
if(respOK) |
|||
{ |
|||
server.send(200, "text/plain", g_szRecvBuff); |
|||
} |
|||
else |
|||
{ |
|||
server.send(500, "text/plain", "????"); |
|||
} |
|||
} |
|||
|
|||
void handleJsonOut() |
|||
{ |
|||
if(sendCommandAndReadSerialResponse("pwr") == false) |
|||
{ |
|||
server.send(500, "text/plain", "Failed to get response to 'pwr' command"); |
|||
return; |
|||
} |
|||
|
|||
parsePwrResponse(g_szRecvBuff); |
|||
prepareJsonOutput(g_szRecvBuff, sizeof(g_szRecvBuff)); |
|||
server.send(200, "application/json", g_szRecvBuff); |
|||
} |
|||
|
|||
void handleRoot() { |
|||
unsigned long days = 0, hours = 0, minutes = 0; |
|||
unsigned long val = os_getCurrentTimeSec(); |
|||
|
|||
days = val / (3600*24); |
|||
val -= days * (3600*24); |
|||
|
|||
hours = val / 3600; |
|||
val -= hours * 3600; |
|||
|
|||
minutes = val / 60; |
|||
val -= minutes*60; |
|||
|
|||
static char szTmp[2500] = ""; |
|||
snprintf(szTmp, sizeof(szTmp)-1, "<html><b>Garage Battery</b><br>Time GMT: %d/%02d/%02d %02d:%02d:%02d (%s)<br>Uptime: %02d:%02d:%02d.%02d<br><br>free heap: %u<br>Wifi RSSI: %d<BR>Wifi SSID: %s", |
|||
year(), month(), day(), hour(), minute(), second(), "GMT", |
|||
(int)days, (int)hours, (int)minutes, (int)val, |
|||
ESP.getFreeHeap(), WiFi.RSSI(), WiFi.SSID().c_str()); |
|||
|
|||
|
|||
strncat(szTmp, "<BR><a href='/log'>Runtime log</a><HR>", sizeof(szTmp)-1); |
|||
strncat(szTmp, "<form action='/req' method='get'>Command:<input type='text' name='code'/><input type='submit'></form><a href='/req?code=pwr'>Power</a> | <a href='/req?code=help'>Help</a> | <a href='/req?code=log'>Event Log</a> | <a href='/req?code=time'>Time</a>", sizeof(szTmp)-1); |
|||
strncat(szTmp, "</html>", sizeof(szTmp)-1); |
|||
|
|||
server.send(200, "text/html", szTmp); |
|||
} |
|||
|
|||
unsigned long os_getCurrentTimeSec() |
|||
{ |
|||
static unsigned int wrapCnt = 0; |
|||
static unsigned long lastVal = 0; |
|||
unsigned long currentVal = millis(); |
|||
|
|||
if(currentVal < lastVal) |
|||
{ |
|||
wrapCnt++; |
|||
} |
|||
|
|||
lastVal = currentVal; |
|||
unsigned long seconds = currentVal/1000; |
|||
|
|||
//millis will wrap each 50 days, as we are interested only in seconds, let's keep the wrap counter
|
|||
return (wrapCnt*4294967) + seconds; |
|||
} |
|||
|
|||
void syncTime() |
|||
{ |
|||
//get time from NTP
|
|||
time_t currentTimeGMT = getNtpTime(); |
|||
if(currentTimeGMT) |
|||
{ |
|||
ntpTimeReceived = true; |
|||
setTime(currentTimeGMT); |
|||
} |
|||
else |
|||
{ |
|||
timer.setTimeout(5000, syncTime); //try again in 5 seconds
|
|||
} |
|||
} |
|||
|
|||
void wakeUpConsole() |
|||
{ |
|||
switchBaud(1200); |
|||
|
|||
//byte wakeUpBuff[] = {0x7E, 0x32, 0x30, 0x30, 0x31, 0x34, 0x36, 0x38, 0x32, 0x43, 0x30, 0x30, 0x34, 0x38, 0x35, 0x32, 0x30, 0x46, 0x43, 0x43, 0x33, 0x0D};
|
|||
//Serial.write(wakeUpBuff, sizeof(wakeUpBuff));
|
|||
Serial.write("~20014682C0048520FCC3\r"); |
|||
delay(1000); |
|||
|
|||
byte newLineBuff[] = {0x0E, 0x0A}; |
|||
switchBaud(115200); |
|||
|
|||
for(int ix=0; ix<10; ix++) |
|||
{ |
|||
Serial.write(newLineBuff, sizeof(newLineBuff)); |
|||
delay(1000); |
|||
|
|||
if(Serial.available()) |
|||
{ |
|||
while(Serial.available()) |
|||
{ |
|||
Serial.read(); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
#define MAX_PYLON_BATTERIES 8
|
|||
|
|||
struct pylonBattery |
|||
{ |
|||
bool isPresent; |
|||
long soc; //Coulomb in %
|
|||
long voltage; //in mW
|
|||
long current; //in mA, negative value is discharge
|
|||
long tempr; //temp of case or BMS?
|
|||
long cellTempLow; |
|||
long cellTempHigh; |
|||
long cellVoltLow; |
|||
long cellVoltHigh; |
|||
char baseState[9]; //Charge | Dischg | Idle
|
|||
char voltageState[9]; //Normal
|
|||
char currentState[9]; //Normal
|
|||
char tempState[9]; //Normal
|
|||
char time[20]; //2019-06-08 04:00:29
|
|||
char b_v_st[9]; //Normal (battery voltage?)
|
|||
char b_t_st[9]; //Normal (battery temperature?)
|
|||
|
|||
bool isCharging() const { return strcmp(baseState, "Charge") == 0; } |
|||
bool isDischarging() const { return strcmp(baseState, "Dischg") == 0; } |
|||
bool isIdle() const { return strcmp(baseState, "Idle") == 0; } |
|||
bool isBalancing() const { return strcmp(baseState, "Balance") == 0; } |
|||
|
|||
|
|||
bool isNormal() const |
|||
{ |
|||
if(isCharging() == false && |
|||
isDischarging() == false && |
|||
isIdle() == false && |
|||
isBalancing() == false) |
|||
{ |
|||
return false; //base state looks wrong!
|
|||
} |
|||
|
|||
return strcmp(voltageState, "Normal") == 0 && |
|||
strcmp(currentState, "Normal") == 0 && |
|||
strcmp(tempState, "Normal") == 0 && |
|||
strcmp(b_v_st, "Normal") == 0 && |
|||
strcmp(b_t_st, "Normal") == 0 ; |
|||
} |
|||
}; |
|||
|
|||
struct batteryStack |
|||
{ |
|||
int batteryCount; |
|||
int soc; //in %, if charging: average SOC, otherwise: lowest SOC
|
|||
int temp; //in mC, if highest temp is > 15C, this will show the highest temp, otherwise the lowest
|
|||
long currentDC; //mAh current going in or out of the battery
|
|||
long avgVoltage; //in mV
|
|||
char baseState[9]; //Charge | Dischg | Idle | Balance | Alarm!
|
|||
|
|||
pylonBattery batts[MAX_PYLON_BATTERIES]; |
|||
|
|||
bool isNormal() const |
|||
{ |
|||
for(int ix=0; ix<MAX_PYLON_BATTERIES; ix++) |
|||
{ |
|||
if(batts[ix].isPresent && |
|||
batts[ix].isNormal() == false) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
//in wH
|
|||
long getPowerDC() const |
|||
{ |
|||
return (long)(((double)currentDC/1000.0)*((double)avgVoltage/1000.0)); |
|||
} |
|||
|
|||
//wH estimated current on AC side (taking into account Sofar ME3000SP losses)
|
|||
long getEstPowerAc() const |
|||
{ |
|||
double powerDC = (double)getPowerDC(); |
|||
if(powerDC == 0) |
|||
{ |
|||
return 0; |
|||
} |
|||
else if(powerDC < 0) |
|||
{ |
|||
//we are discharging, on AC side we will see less power due to losses
|
|||
if(powerDC < -1000) |
|||
{ |
|||
return (long)(powerDC*0.94); |
|||
} |
|||
else if(powerDC < -600) |
|||
{ |
|||
return (long)(powerDC*0.90); |
|||
} |
|||
else |
|||
{ |
|||
return (long)(powerDC*0.87); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
//we are charging, on AC side we will have more power due to losses
|
|||
if(powerDC > 1000) |
|||
{ |
|||
return (long)(powerDC*1.06); |
|||
} |
|||
else if(powerDC > 600) |
|||
{ |
|||
return (long)(powerDC*1.1); |
|||
} |
|||
else |
|||
{ |
|||
return (long)(powerDC*1.13); |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
batteryStack g_stack; |
|||
|
|||
|
|||
long extractInt(const char* pStr, int pos) |
|||
{ |
|||
return atol(pStr+pos); |
|||
} |
|||
|
|||
void extractStr(const char* pStr, int pos, char* strOut, int strOutSize) |
|||
{ |
|||
strOut[strOutSize-1] = '\0'; |
|||
strncpy(strOut, pStr+pos, strOutSize-1); |
|||
strOutSize--; |
|||
|
|||
|
|||
//trim right
|
|||
while(strOutSize > 0) |
|||
{ |
|||
if(isspace(strOut[strOutSize-1])) |
|||
{ |
|||
strOut[strOutSize-1] = '\0'; |
|||
} |
|||
else |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
strOutSize--; |
|||
} |
|||
} |
|||
|
|||
/* Output has mixed \r and \r\n
|
|||
pwr |
|||
|
|||
@ |
|||
|
|||
Power Volt Curr Tempr Tlow Thigh Vlow Vhigh Base.St Volt.St Curr.St Temp.St Coulomb Time B.V.St B.T.St |
|||
|
|||
1 49735 -1440 22000 19000 19000 3315 3317 Dischg Normal Normal Normal 93% 2019-06-08 04:00:30 Normal Normal |
|||
|
|||
.... |
|||
|
|||
8 - - - - - - - Absent - - - - - - - |
|||
|
|||
Command completed successfully |
|||
|
|||
$$ |
|||
|
|||
pylon |
|||
*/ |
|||
bool parsePwrResponse(const char* pStr) |
|||
{ |
|||
if(strstr(pStr, "Command completed successfully") == NULL) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
int chargeCnt = 0; |
|||
int dischargeCnt = 0; |
|||
int idleCnt = 0; |
|||
int alarmCnt = 0; |
|||
int socAvg = 0; |
|||
int socLow = 0; |
|||
int tempHigh = 0; |
|||
int tempLow = 0; |
|||
|
|||
memset(&g_stack, 0, sizeof(g_stack)); |
|||
|
|||
for(int ix=0; ix<MAX_PYLON_BATTERIES; ix++) |
|||
{ |
|||
char szToFind[32] = ""; |
|||
snprintf(szToFind, sizeof(szToFind)-1, "\r\r\n%d ", ix+1); |
|||
|
|||
const char* pLineStart = strstr(pStr, szToFind); |
|||
if(pLineStart == NULL) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
pLineStart += 3; //move past \r\r\n
|
|||
|
|||
extractStr(pLineStart, 55, g_stack.batts[ix].baseState, sizeof(g_stack.batts[ix].baseState)); |
|||
if(strcmp(g_stack.batts[ix].baseState, "Absent") == 0) |
|||
{ |
|||
g_stack.batts[ix].isPresent = false; |
|||
} |
|||
else |
|||
{ |
|||
g_stack.batts[ix].isPresent = true; |
|||
extractStr(pLineStart, 64, g_stack.batts[ix].voltageState, sizeof(g_stack.batts[ix].voltageState)); |
|||
extractStr(pLineStart, 73, g_stack.batts[ix].currentState, sizeof(g_stack.batts[ix].currentState)); |
|||
extractStr(pLineStart, 82, g_stack.batts[ix].tempState, sizeof(g_stack.batts[ix].tempState)); |
|||
extractStr(pLineStart, 100, g_stack.batts[ix].time, sizeof(g_stack.batts[ix].time)); |
|||
extractStr(pLineStart, 121, g_stack.batts[ix].b_v_st, sizeof(g_stack.batts[ix].b_v_st)); |
|||
extractStr(pLineStart, 130, g_stack.batts[ix].b_t_st, sizeof(g_stack.batts[ix].b_t_st)); |
|||
g_stack.batts[ix].voltage = extractInt(pLineStart, 6); |
|||
g_stack.batts[ix].current = extractInt(pLineStart, 13); |
|||
g_stack.batts[ix].tempr = extractInt(pLineStart, 20); |
|||
g_stack.batts[ix].cellTempLow = extractInt(pLineStart, 27); |
|||
g_stack.batts[ix].cellTempHigh = extractInt(pLineStart, 34); |
|||
g_stack.batts[ix].cellVoltLow = extractInt(pLineStart, 41); |
|||
g_stack.batts[ix].cellVoltHigh = extractInt(pLineStart, 48); |
|||
g_stack.batts[ix].soc = extractInt(pLineStart, 91); |
|||
|
|||
//////////////////////////////// Post-process ////////////////////////
|
|||
g_stack.batteryCount++; |
|||
g_stack.currentDC += g_stack.batts[ix].current; |
|||
g_stack.avgVoltage += g_stack.batts[ix].voltage; |
|||
socAvg += g_stack.batts[ix].soc; |
|||
|
|||
if(g_stack.batts[ix].isNormal() == false){ alarmCnt++; } |
|||
else if(g_stack.batts[ix].isCharging()){chargeCnt++;} |
|||
else if(g_stack.batts[ix].isDischarging()){dischargeCnt++;} |
|||
else if(g_stack.batts[ix].isIdle()){idleCnt++;} |
|||
else{ alarmCnt++; } //should not really happen!
|
|||
|
|||
if(g_stack.batteryCount == 1) |
|||
{ |
|||
socLow = g_stack.batts[ix].soc; |
|||
tempLow = g_stack.batts[ix].cellTempLow; |
|||
tempHigh = g_stack.batts[ix].cellTempHigh; |
|||
} |
|||
else |
|||
{ |
|||
if(socLow > g_stack.batts[ix].soc){socLow = g_stack.batts[ix].soc;} |
|||
if(tempHigh < g_stack.batts[ix].cellTempHigh){tempHigh = g_stack.batts[ix].cellTempHigh;} |
|||
if(tempLow > g_stack.batts[ix].cellTempLow){tempLow = g_stack.batts[ix].cellTempLow;} |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
|||
//now update stack state:
|
|||
g_stack.avgVoltage /= g_stack.batteryCount; |
|||
g_stack.soc = socLow; |
|||
|
|||
if(tempHigh > 15000) //15C
|
|||
{ |
|||
g_stack.temp = tempHigh; //in the summer we highlight the warmest cell
|
|||
} |
|||
else |
|||
{ |
|||
g_stack.temp = tempLow; //in the winter we focus on coldest cell
|
|||
} |
|||
|
|||
if(alarmCnt > 0) |
|||
{ |
|||
strcpy(g_stack.baseState, "Alarm!"); |
|||
} |
|||
else if(chargeCnt == g_stack.batteryCount) |
|||
{ |
|||
strcpy(g_stack.baseState, "Charge"); |
|||
g_stack.soc = (int)(socAvg / g_stack.batteryCount); |
|||
} |
|||
else if(dischargeCnt == g_stack.batteryCount) |
|||
{ |
|||
strcpy(g_stack.baseState, "Dischg"); |
|||
} |
|||
else if(idleCnt == g_stack.batteryCount) |
|||
{ |
|||
strcpy(g_stack.baseState, "Idle"); |
|||
} |
|||
else |
|||
{ |
|||
strcpy(g_stack.baseState, "Balance"); |
|||
} |
|||
|
|||
|
|||
return true; |
|||
} |
|||
|
|||
void prepareJsonOutput(char* pBuff, int buffSize) |
|||
{ |
|||
memset(pBuff, 0, buffSize); |
|||
snprintf(pBuff, buffSize-1, "{\"soc\": %d, \"temp\": %d, \"currentDC\": %ld, \"avgVoltage\": %ld, \"baseState\": \"%s\", \"batteryCount\": %d, \"powerDC\": %ld, \"estPowerAC\": %ld, \"isNormal\": %s}", g_stack.soc, |
|||
g_stack.temp, |
|||
g_stack.currentDC, |
|||
g_stack.avgVoltage, |
|||
g_stack.baseState, |
|||
g_stack.batteryCount, |
|||
g_stack.getPowerDC(), |
|||
g_stack.getEstPowerAc(), |
|||
g_stack.isNormal() ? "true" : "false"); |
|||
} |
|||
|
|||
void loop() { |
|||
#ifdef ENABLE_MQTT
|
|||
mqttLoop(); |
|||
#endif
|
|||
|
|||
ArduinoOTA.handle(); |
|||
server.handleClient(); |
|||
timer.run(); |
|||
|
|||
//if there are bytes availbe on serial here - it's unexpected
|
|||
//when we send a command to battery, we read whole response
|
|||
//if we get anything here anyways - we will log it
|
|||
int bytesAv = Serial.available(); |
|||
if(bytesAv > 0) |
|||
{ |
|||
if(bytesAv > 63) |
|||
{ |
|||
bytesAv = 63; |
|||
} |
|||
|
|||
char buff[64+4] = "RCV:"; |
|||
if(Serial.readBytes(buff+4, bytesAv) > 0) |
|||
{ |
|||
digitalWrite(LED_BUILTIN, LOW); |
|||
delay(5); |
|||
digitalWrite(LED_BUILTIN, HIGH);//high is off
|
|||
|
|||
Log(buff); |
|||
} |
|||
} |
|||
} |
|||
|
|||
#ifdef ENABLE_MQTT
|
|||
#define ABS_DIFF(a, b) (a > b ? a-b : b-a)
|
|||
void mqtt_publish_f(const char* topic, float newValue, float oldValue, float minDiff, bool force) |
|||
{ |
|||
char szTmp[16] = ""; |
|||
snprintf(szTmp, 15, "%.2f", newValue); |
|||
if(force || ABS_DIFF(newValue, oldValue) > minDiff) |
|||
{ |
|||
mqttClient.publish(topic, szTmp, false); |
|||
} |
|||
} |
|||
|
|||
void mqtt_publish_i(const char* topic, int newValue, int oldValue, int minDiff, bool force) |
|||
{ |
|||
char szTmp[16] = ""; |
|||
snprintf(szTmp, 15, "%d", newValue); |
|||
if(force || ABS_DIFF(newValue, oldValue) > minDiff) |
|||
{ |
|||
mqttClient.publish(topic, szTmp, false); |
|||
} |
|||
} |
|||
|
|||
void mqtt_publish_s(const char* topic, const char* newValue, const char* oldValue, bool force) |
|||
{ |
|||
if(force || strcmp(newValue, oldValue) != 0) |
|||
{ |
|||
mqttClient.publish(topic, newValue, false); |
|||
} |
|||
} |
|||
|
|||
void pushBatteryDataToMqtt(const batteryStack& lastSentData, bool forceUpdate /* if true - we will send all data regardless if it's the same */) |
|||
{ |
|||
mqtt_publish_f(MQTT_TOPIC_ROOT "soc", g_stack.soc, lastSentData.soc, 0, forceUpdate); |
|||
mqtt_publish_f(MQTT_TOPIC_ROOT "temp", (float)g_stack.temp/1000.0, (float)lastSentData.temp/1000.0, 0, forceUpdate); |
|||
mqtt_publish_i(MQTT_TOPIC_ROOT "estPowerAC", g_stack.getEstPowerAc(), lastSentData.getEstPowerAc(), 10, forceUpdate); |
|||
mqtt_publish_i(MQTT_TOPIC_ROOT "battery_count",g_stack.batteryCount, lastSentData.batteryCount, 0, forceUpdate); |
|||
mqtt_publish_s(MQTT_TOPIC_ROOT "base_state", g_stack.baseState, lastSentData.baseState , forceUpdate); |
|||
mqtt_publish_i(MQTT_TOPIC_ROOT "is_normal", g_stack.isNormal() ? 1:0, lastSentData.isNormal() ? 1:0, 0, forceUpdate); |
|||
} |
|||
|
|||
void mqttLoop() |
|||
{ |
|||
//if we have problems with connecting to mqtt server, we will attempt to re-estabish connection each 1minute (not more than that)
|
|||
static unsigned long g_lastConnectionAttempt = 0; |
|||
|
|||
//first: let's make sure we are connected to mqtt
|
|||
const char* topicLastWill = MQTT_TOPIC_ROOT "availability"; |
|||
if (!mqttClient.connected() && (g_lastConnectionAttempt == 0 || os_getCurrentTimeSec() - g_lastConnectionAttempt > 60)) { |
|||
if(mqttClient.connect("GarageBattery", MQTT_USER, MQTT_PASSWORD, topicLastWill, 1, true, "offline")) |
|||
{ |
|||
Log("Connected to MQTT server: " MQTT_SERVER); |
|||
mqttClient.publish(topicLastWill, "online", true); |
|||
} |
|||
else |
|||
{ |
|||
Log("Failed to connect to MQTT server."); |
|||
} |
|||
|
|||
g_lastConnectionAttempt = os_getCurrentTimeSec(); |
|||
} |
|||
|
|||
//next: read data from battery and send via MQTT (but only once per MQTT_PUSH_FREQ_SEC seconds)
|
|||
static unsigned long g_lastDataSent = 0; |
|||
if(mqttClient.connected() && |
|||
os_getCurrentTimeSec() - g_lastDataSent > MQTT_PUSH_FREQ_SEC && |
|||
sendCommandAndReadSerialResponse("pwr") == true) |
|||
{ |
|||
static batteryStack lastSentData; //this is the last state we sent to MQTT, used to prevent sending the same data over and over again
|
|||
static unsigned int callCnt = 0; |
|||
|
|||
parsePwrResponse(g_szRecvBuff); |
|||
|
|||
bool forceUpdate = (callCnt % 20 == 0); //push all the data every 20th call
|
|||
pushBatteryDataToMqtt(lastSentData, forceUpdate); |
|||
|
|||
callCnt++; |
|||
g_lastDataSent = os_getCurrentTimeSec(); |
|||
memcpy(&lastSentData, &g_stack, sizeof(batteryStack)); |
|||
} |
|||
|
|||
mqttClient.loop(); |
|||
} |
|||
|
|||
#endif //ENABLE_MQTT
|
@ -0,0 +1,52 @@ |
|||
# Pylontech Battery Monitoring via WiFi |
|||
|
|||
This project allows you to control and monitor Pylontech US2000B and US2000C batteries via console port over WiFi. |
|||
It it's a great starting point to integrate battery with your home automation. |
|||
|
|||
**I ACCEPT NO RESPONSIBILTY FOR ANY DAMAGE CAUSED, PROCEED AT YOUR OWN RISK** |
|||
|
|||
# Features: |
|||
* Low cost (around 20$ in total). |
|||
* Adds WiFi capability to your Pylontech US2000B/C battery. |
|||
* Device exposes web interface that allows to: |
|||
* send console commands and read response over WiFi (no PC needed) |
|||
* battery information can be retrevied also in JSON format for easy parsing |
|||
* MQTT support: |
|||
* device pushes basic battery data like SOC, temperature, state, etc to selected MQTT server |
|||
* Easy to modify code using Arduino IDE and flash new firmware over WiFi (no need to disconnect from the battery). |
|||
|
|||
See the project in action on [Youtube](https://youtu.be/7VyQjKU3MsU):</br> |
|||
<a href="http://www.youtube.com/watch?feature=player_embedded&v=7VyQjKU3MsU" target="_blank"><img src="http://img.youtube.com/vi/7VyQjKU3MsU/0.jpg" alt="See the project in action on YouTube" width="240" height="180" border="10" /></a> |
|||
|
|||
|
|||
# Parts needed and schematics: |
|||
* [Wemos D1 mini microcontroller](https://www.amazon.co.uk/Makerfire-NodeMcu-Development-ESP8266-Compatible/dp/B071S8MWTY/). |
|||
* [SparkFun MAX3232 Transceiver](https://www.sparkfun.com/products/11189). |
|||
* US2000B: Cable with RJ10 connector (some RJ10 cables have only two wires, make sure to buy one that has all four wires present). |
|||
* US2000C: Cable with RJ45 connector (see below for more details). |
|||
* Capacitors C1: 10uF, C2: 0.1uF (this is not strictly required, but recommended as Wemos D1 can have large current spikes). |
|||
|
|||
![Schematics](Schemetics.png) |
|||
|
|||
# US2000C notes: |
|||
This battery uses RJ45 cable instead of RJ10. Schematics is the same only plug differs: |
|||
* RJ45 Pin 3 (white-green) = R1IN |
|||
* RJ45 Pin 6 (green) = T1OUT |
|||
* RJ45 Pin 8 (brown) = GND |
|||
![image](https://user-images.githubusercontent.com/19826327/146428324-29e3f9bf-6cc3-415c-9d60-fa5ee3d65613.png) |
|||
|
|||
|
|||
# How to get going: |
|||
* Get Wemos D1 mini |
|||
* Install arduino IDE and ESP8266 libraries as [described here](https://averagemaker.com/2018/03/wemos-d1-mini-setup.html) |
|||
* Open [PylontechMonitoring.ino](PylontechMonitoring.ino) in arduino IDE |
|||
* Make sure to copy content of [libraries subdirectory](libraries) to [libraries of your Arduino IDE](https://forum.arduino.cc/index.php?topic=88380.0). |
|||
* Specify your WiFi login and password at the top of the file (line 13-14) |
|||
* If you want MQTT support, uncomment line 17 and fill details in lines 21-24 |
|||
* Upload project to your device |
|||
* Connect Wemos D1 mini to the MAX3232 transreceiver |
|||
* Connect transreceiver to RJ10/RJ45 as descibed in the schematics (all three lines need to be connected) |
|||
* Connect RJ10/RJ45 to the serial port of the Pylontech US2000 battery. If you have multiple batteries - connect to the master one. |
|||
* Connect Wemos D1 to the power via USB |
|||
* Find what IP address was assigned to your Wemos by your router and open it in the web-browser |
|||
* You should be able now to connunicate with the battery via WiFi |
After Width: 1200 | Height: 921 | Size: 504 KiB |
@ -0,0 +1,113 @@ |
|||
#ifndef circ_buffer_h |
|||
#define circ_buffer_h |
|||
|
|||
#include <stdlib.h> |
|||
|
|||
/// <summary> |
|||
/// This class allows a fixed size circular buffer. |
|||
/// When push_back is called, oldest data is overwritten. |
|||
/// Does not use any dynamic allocators. |
|||
/// </summary> |
|||
template <class ItemType, int elementCnt> class circular_buffer |
|||
{ |
|||
private: |
|||
ItemType m_arr[elementCnt]; |
|||
int m_writePos; |
|||
int m_size; |
|||
|
|||
void advanceWritePos() |
|||
{ |
|||
if(m_size < elementCnt) |
|||
{ |
|||
m_size++; |
|||
} |
|||
|
|||
m_writePos++; |
|||
if(m_writePos >= elementCnt) |
|||
{ |
|||
m_writePos = 0; |
|||
} |
|||
} |
|||
|
|||
circular_buffer(const circular_buffer<ItemType, elementCnt>& rhs); |
|||
public: |
|||
circular_buffer() |
|||
{ |
|||
clear(); |
|||
} |
|||
|
|||
void operator=(const circular_buffer<ItemType, elementCnt>& rhs) |
|||
{ |
|||
memcpy(m_arr, rhs.m_arr, sizeof(m_arr)); |
|||
m_size = rhs.m_size; |
|||
m_writePos = rhs.m_writePos; |
|||
} |
|||
|
|||
void push_back(const ItemType& item) |
|||
{ |
|||
m_arr[m_writePos] = item; |
|||
advanceWritePos(); |
|||
} |
|||
|
|||
void clear() |
|||
{ |
|||
memset(m_arr,0,sizeof(m_arr)); |
|||
m_size = m_writePos = 0; |
|||
} |
|||
|
|||
void sort() |
|||
{ |
|||
if (size() < 2) |
|||
return; |
|||
|
|||
bool swapped; |
|||
do |
|||
{ |
|||
swapped = false; |
|||
for(int ix=0; ix<size()-1; ix++) |
|||
{ |
|||
if(at(ix) > at(ix+1)) |
|||
{ |
|||
ItemType tmp = at(ix); |
|||
at(ix) = at(ix+1); |
|||
at(ix+1) = tmp; |
|||
swapped = true; |
|||
} |
|||
} |
|||
|
|||
}while(swapped); |
|||
} |
|||
|
|||
int size() const { return m_size; } |
|||
bool isFull() const { return size() == elementCnt; } |
|||
ItemType& operator[](int pos) {return at(pos);} |
|||
|
|||
ItemType& at(int pos) |
|||
{ |
|||
if(m_size < elementCnt) |
|||
{ |
|||
return m_arr[pos]; |
|||
} |
|||
|
|||
int readPos = m_writePos + pos; |
|||
if(readPos >= elementCnt) |
|||
{ |
|||
readPos -= elementCnt; |
|||
} |
|||
|
|||
return m_arr[readPos]; |
|||
} |
|||
|
|||
#if _DEBUG_ENABLED |
|||
void print() |
|||
{ |
|||
printf("---\n"); |
|||
for(int i=0; i<size(); i++) |
|||
{ |
|||
printf("%d = %d\n", i, at(i)); |
|||
} |
|||
} |
|||
#endif |
|||
}; |
|||
|
|||
#endif //circ_buffer_h |
@ -0,0 +1,96 @@ |
|||
#ifndef circ_log_h |
|||
#define circ_log_h |
|||
|
|||
#include <TimeLib.h> //https://github.com/PaulStoffregen/Time |
|||
#include <stdlib.h> |
|||
|
|||
template <int elementCnt> class circular_log |
|||
{ |
|||
private: |
|||
char m_log[elementCnt]; |
|||
|
|||
bool removeLastFromLog() |
|||
{ |
|||
char* nextLine = strstr(m_log+1, "<BR>"); |
|||
if(nextLine == NULL) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
int newLineLen = strlen(nextLine); |
|||
memmove(m_log, nextLine, newLineLen); |
|||
m_log[newLineLen] = '\0'; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
public: |
|||
circular_log() |
|||
{ |
|||
memset(m_log, 0, sizeof(m_log)); |
|||
} |
|||
|
|||
const char* c_str() const { return m_log; } |
|||
int freeSpace() const { return elementCnt - strlen(m_log) - 1; } |
|||
|
|||
void LogXml(const char* msg) |
|||
{ |
|||
char szNew[256] = ""; |
|||
snprintf(szNew, sizeof(szNew)-1, "<BR>%02d - %02d:%02d | ", day(), hour(), minute()); |
|||
|
|||
int ix = strlen(szNew); |
|||
while(*msg != '\0' && ix < 250) |
|||
{ |
|||
if(*msg == '<') |
|||
{ |
|||
szNew[ix++] = '&'; |
|||
szNew[ix++] = 'l'; |
|||
szNew[ix++] = 't'; |
|||
szNew[ix++] = ';'; |
|||
} |
|||
else if(*msg == '>') |
|||
{ |
|||
szNew[ix++] = '&'; |
|||
szNew[ix++] = 'g'; |
|||
szNew[ix++] = 't'; |
|||
szNew[ix++] = ';'; |
|||
} |
|||
else |
|||
{ |
|||
szNew[ix++] = *msg; |
|||
} |
|||
|
|||
msg++; |
|||
} |
|||
|
|||
const int newLen = strlen(szNew); |
|||
while(freeSpace() < newLen) |
|||
{ |
|||
if(removeLastFromLog() == false) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
strcat(m_log, szNew); |
|||
} |
|||
|
|||
void Log(const char* msg) |
|||
{ |
|||
char szNew[256] = ""; |
|||
snprintf(szNew, sizeof(szNew)-1, "<BR>%02d - %02d:%02d | %s", day(), hour(), minute(), msg); |
|||
|
|||
const int newLen = strlen(szNew); |
|||
while(freeSpace() < newLen) |
|||
{ |
|||
if(removeLastFromLog() == false) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
strcat(m_log, szNew); |
|||
} |
|||
}; |
|||
|
|||
#endif //circular_log_h |
@ -0,0 +1,81 @@ |
|||
#ifndef ntp_time_h |
|||
#define ntp_time_h |
|||
|
|||
#include <WiFiUdp.h> |
|||
|
|||
// NTP Servers: |
|||
static const char ntpServerName[] = "0.uk.pool.ntp.org"; |
|||
const int timeZone = 0; |
|||
unsigned int localPort = 8888; // local port to listen for UDP packets |
|||
|
|||
/*-------- NTP code ----------*/ |
|||
|
|||
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message |
|||
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets |
|||
WiFiUDP udpNtp; |
|||
|
|||
// send an NTP request to the time server at the given address |
|||
void sendNTPpacket(IPAddress &address) |
|||
{ |
|||
// set all bytes in the buffer to 0 |
|||
memset(packetBuffer, 0, NTP_PACKET_SIZE); |
|||
// Initialize values needed to form NTP request |
|||
// (see URL above for details on the packets) |
|||
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 |
|||
// 8 bytes of zero for Root Delay & Root Dispersion |
|||
packetBuffer[12] = 49; |
|||
packetBuffer[13] = 0x4E; |
|||
packetBuffer[14] = 49; |
|||
packetBuffer[15] = 52; |
|||
// all NTP fields have been given values, now |
|||
// you can send a packet requesting a timestamp: |
|||
udpNtp.beginPacket(address, 123); //NTP requests are to port 123 |
|||
udpNtp.write(packetBuffer, NTP_PACKET_SIZE); |
|||
udpNtp.endPacket(); |
|||
} |
|||
|
|||
|
|||
time_t getNtpTime() |
|||
{ |
|||
if(WiFi.status() != WL_CONNECTED) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
static bool udpStarted = false; |
|||
if(udpStarted == false) |
|||
{ |
|||
udpStarted = true; |
|||
udpNtp.begin(localPort); |
|||
} |
|||
|
|||
IPAddress ntpServerIP; // NTP server's ip address |
|||
|
|||
while (udpNtp.parsePacket() > 0) ; // discard any previously received packets |
|||
// get a random server from the pool |
|||
WiFi.hostByName(ntpServerName, ntpServerIP); |
|||
sendNTPpacket(ntpServerIP); |
|||
delay(100); |
|||
|
|||
uint32_t beginWait = millis(); |
|||
while (millis() - beginWait < 1500) { |
|||
int size = udpNtp.parsePacket(); |
|||
if (size >= NTP_PACKET_SIZE) { |
|||
udpNtp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer |
|||
unsigned long secsSince1900; |
|||
// convert four bytes starting at location 40 to a long integer |
|||
secsSince1900 = (unsigned long)packetBuffer[40] << 24; |
|||
secsSince1900 |= (unsigned long)packetBuffer[41] << 16; |
|||
secsSince1900 |= (unsigned long)packetBuffer[42] << 8; |
|||
secsSince1900 |= (unsigned long)packetBuffer[43]; |
|||
return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; |
|||
} |
|||
|
|||
delay(10); |
|||
} |
|||
return 0; // return 0 if unable to get the time |
|||
} |
|||
#endif //ntp_time_h |
@ -0,0 +1,2 @@ |
|||
Visit this page for more information: |
|||
http://playground.arduino.cc/Code/SimpleTimer |
@ -0,0 +1,250 @@ |
|||
/*
|
|||
* SimpleTimer.cpp |
|||
* |
|||
* SimpleTimer - A timer library for Arduino. |
|||
* Author: mromani@ottotecnica.com |
|||
* Copyright (c) 2010 OTTOTECNICA Italy |
|||
* |
|||
* This library is free software; you can redistribute it |
|||
* and/or modify it under the terms of the GNU Lesser |
|||
* General Public License as published by the Free Software |
|||
* Foundation; either version 2.1 of the License, or (at |
|||
* your option) any later version. |
|||
* |
|||
* This library is distributed in the hope that it will |
|||
* be useful, but WITHOUT ANY WARRANTY; without even the |
|||
* implied warranty of MERCHANTABILITY or FITNESS FOR A |
|||
* PARTICULAR PURPOSE. See the GNU Lesser General Public |
|||
* License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Lesser |
|||
* General Public License along with this library; if not, |
|||
* write to the Free Software Foundation, Inc., |
|||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|||
*/ |
|||
|
|||
|
|||
#include "SimpleTimer.h"
|
|||
|
|||
|
|||
// Select time function:
|
|||
//static inline unsigned long elapsed() { return micros(); }
|
|||
static inline unsigned long elapsed() { return millis(); } |
|||
|
|||
|
|||
SimpleTimer::SimpleTimer() |
|||
: numTimers (-1) |
|||
{ |
|||
} |
|||
|
|||
void SimpleTimer::init() { |
|||
unsigned long current_millis = elapsed(); |
|||
|
|||
for (int i = 0; i < MAX_TIMERS; i++) { |
|||
enabled[i] = false; |
|||
callbacks[i] = 0; // if the callback pointer is zero, the slot is free, i.e. doesn't "contain" any timer
|
|||
prev_millis[i] = current_millis; |
|||
numRuns[i] = 0; |
|||
} |
|||
|
|||
numTimers = 0; |
|||
} |
|||
|
|||
|
|||
void SimpleTimer::run() { |
|||
int i; |
|||
unsigned long current_millis; |
|||
|
|||
// get current time
|
|||
current_millis = elapsed(); |
|||
|
|||
for (i = 0; i < MAX_TIMERS; i++) { |
|||
|
|||
toBeCalled[i] = DEFCALL_DONTRUN; |
|||
|
|||
// no callback == no timer, i.e. jump over empty slots
|
|||
if (callbacks[i]) { |
|||
|
|||
// is it time to process this timer ?
|
|||
// see http://arduino.cc/forum/index.php/topic,124048.msg932592.html#msg932592
|
|||
|
|||
if (current_millis - prev_millis[i] >= delays[i]) { |
|||
|
|||
// update time
|
|||
//prev_millis[i] = current_millis;
|
|||
prev_millis[i] += delays[i]; |
|||
|
|||
// check if the timer callback has to be executed
|
|||
if (enabled[i]) { |
|||
|
|||
// "run forever" timers must always be executed
|
|||
if (maxNumRuns[i] == RUN_FOREVER) { |
|||
toBeCalled[i] = DEFCALL_RUNONLY; |
|||
} |
|||
// other timers get executed the specified number of times
|
|||
else if (numRuns[i] < maxNumRuns[i]) { |
|||
toBeCalled[i] = DEFCALL_RUNONLY; |
|||
numRuns[i]++; |
|||
|
|||
// after the last run, delete the timer
|
|||
if (numRuns[i] >= maxNumRuns[i]) { |
|||
toBeCalled[i] = DEFCALL_RUNANDDEL; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
for (i = 0; i < MAX_TIMERS; i++) { |
|||
switch(toBeCalled[i]) { |
|||
case DEFCALL_DONTRUN: |
|||
break; |
|||
|
|||
case DEFCALL_RUNONLY: |
|||
(*callbacks[i])(); |
|||
break; |
|||
|
|||
case DEFCALL_RUNANDDEL: |
|||
(*callbacks[i])(); |
|||
deleteTimer(i); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
// find the first available slot
|
|||
// return -1 if none found
|
|||
int SimpleTimer::findFirstFreeSlot() { |
|||
int i; |
|||
|
|||
// all slots are used
|
|||
if (numTimers >= MAX_TIMERS) { |
|||
return -1; |
|||
} |
|||
|
|||
// return the first slot with no callback (i.e. free)
|
|||
for (i = 0; i < MAX_TIMERS; i++) { |
|||
if (callbacks[i] == 0) { |
|||
return i; |
|||
} |
|||
} |
|||
|
|||
// no free slots found
|
|||
return -1; |
|||
} |
|||
|
|||
|
|||
int SimpleTimer::setTimer(long d, timer_callback f, int n) { |
|||
int freeTimer; |
|||
|
|||
if (numTimers < 0) { |
|||
init(); |
|||
} |
|||
|
|||
freeTimer = findFirstFreeSlot(); |
|||
if (freeTimer < 0) { |
|||
return -1; |
|||
} |
|||
|
|||
if (f == NULL) { |
|||
return -1; |
|||
} |
|||
|
|||
delays[freeTimer] = d; |
|||
callbacks[freeTimer] = f; |
|||
maxNumRuns[freeTimer] = n; |
|||
enabled[freeTimer] = true; |
|||
prev_millis[freeTimer] = elapsed(); |
|||
|
|||
numTimers++; |
|||
|
|||
return freeTimer; |
|||
} |
|||
|
|||
|
|||
int SimpleTimer::setInterval(long d, timer_callback f) { |
|||
return setTimer(d, f, RUN_FOREVER); |
|||
} |
|||
|
|||
|
|||
int SimpleTimer::setTimeout(long d, timer_callback f) { |
|||
return setTimer(d, f, RUN_ONCE); |
|||
} |
|||
|
|||
|
|||
void SimpleTimer::deleteTimer(int timerId) { |
|||
if (timerId >= MAX_TIMERS) { |
|||
return; |
|||
} |
|||
|
|||
// nothing to delete if no timers are in use
|
|||
if (numTimers == 0) { |
|||
return; |
|||
} |
|||
|
|||
// don't decrease the number of timers if the
|
|||
// specified slot is already empty
|
|||
if (callbacks[timerId] != NULL) { |
|||
callbacks[timerId] = 0; |
|||
enabled[timerId] = false; |
|||
toBeCalled[timerId] = DEFCALL_DONTRUN; |
|||
delays[timerId] = 0; |
|||
numRuns[timerId] = 0; |
|||
|
|||
// update number of timers
|
|||
numTimers--; |
|||
} |
|||
} |
|||
|
|||
|
|||
// function contributed by code@rowansimms.com
|
|||
void SimpleTimer::restartTimer(int numTimer) { |
|||
if (numTimer >= MAX_TIMERS) { |
|||
return; |
|||
} |
|||
|
|||
prev_millis[numTimer] = elapsed(); |
|||
} |
|||
|
|||
|
|||
boolean SimpleTimer::isEnabled(int numTimer) { |
|||
if (numTimer >= MAX_TIMERS) { |
|||
return false; |
|||
} |
|||
|
|||
return enabled[numTimer]; |
|||
} |
|||
|
|||
|
|||
void SimpleTimer::enable(int numTimer) { |
|||
if (numTimer >= MAX_TIMERS) { |
|||
return; |
|||
} |
|||
|
|||
enabled[numTimer] = true; |
|||
} |
|||
|
|||
|
|||
void SimpleTimer::disable(int numTimer) { |
|||
if (numTimer >= MAX_TIMERS) { |
|||
return; |
|||
} |
|||
|
|||
enabled[numTimer] = false; |
|||
} |
|||
|
|||
|
|||
void SimpleTimer::toggle(int numTimer) { |
|||
if (numTimer >= MAX_TIMERS) { |
|||
return; |
|||
} |
|||
|
|||
enabled[numTimer] = !enabled[numTimer]; |
|||
} |
|||
|
|||
|
|||
int SimpleTimer::getNumTimers() { |
|||
return numTimers; |
|||
} |
@ -0,0 +1,126 @@ |
|||
/* |
|||
* SimpleTimer.h |
|||
* |
|||
* SimpleTimer - A timer library for Arduino. |
|||
* Author: mromani@ottotecnica.com |
|||
* Copyright (c) 2010 OTTOTECNICA Italy |
|||
* |
|||
* This library is free software; you can redistribute it |
|||
* and/or modify it under the terms of the GNU Lesser |
|||
* General Public License as published by the Free Software |
|||
* Foundation; either version 2.1 of the License, or (at |
|||
* your option) any later version. |
|||
* |
|||
* This library is distributed in the hope that it will |
|||
* be useful, but WITHOUT ANY WARRANTY; without even the |
|||
* implied warranty of MERCHANTABILITY or FITNESS FOR A |
|||
* PARTICULAR PURPOSE. See the GNU Lesser General Public |
|||
* License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Lesser |
|||
* General Public License along with this library; if not, |
|||
* write to the Free Software Foundation, Inc., |
|||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|||
* |
|||
*/ |
|||
|
|||
|
|||
#ifndef SIMPLETIMER_H |
|||
#define SIMPLETIMER_H |
|||
|
|||
#if defined(ARDUINO) && ARDUINO >= 100 |
|||
#include <Arduino.h> |
|||
#else |
|||
#include <WProgram.h> |
|||
#endif |
|||
|
|||
typedef void (*timer_callback)(void); |
|||
|
|||
class SimpleTimer { |
|||
|
|||
public: |
|||
// maximum number of timers |
|||
const static int MAX_TIMERS = 10; |
|||
|
|||
// setTimer() constants |
|||
const static int RUN_FOREVER = 0; |
|||
const static int RUN_ONCE = 1; |
|||
|
|||
// constructor |
|||
SimpleTimer(); |
|||
|
|||
void init(); |
|||
|
|||
// this function must be called inside loop() |
|||
void run(); |
|||
|
|||
// call function f every d milliseconds |
|||
int setInterval(long d, timer_callback f); |
|||
|
|||
// call function f once after d milliseconds |
|||
int setTimeout(long d, timer_callback f); |
|||
|
|||
// call function f every d milliseconds for n times |
|||
int setTimer(long d, timer_callback f, int n); |
|||
|
|||
// destroy the specified timer |
|||
void deleteTimer(int numTimer); |
|||
|
|||
// restart the specified timer |
|||
void restartTimer(int numTimer); |
|||
|
|||
// returns true if the specified timer is enabled |
|||
boolean isEnabled(int numTimer); |
|||
|
|||
// enables the specified timer |
|||
void enable(int numTimer); |
|||
|
|||
// disables the specified timer |
|||
void disable(int numTimer); |
|||
|
|||
// enables the specified timer if it's currently disabled, |
|||
// and vice-versa |
|||
void toggle(int numTimer); |
|||
|
|||
// returns the number of used timers |
|||
int getNumTimers(); |
|||
|
|||
// returns the number of available timers |
|||
int getNumAvailableTimers() { return MAX_TIMERS - numTimers; }; |
|||
|
|||
private: |
|||
// deferred call constants |
|||
const static int DEFCALL_DONTRUN = 0; // don't call the callback function |
|||
const static int DEFCALL_RUNONLY = 1; // call the callback function but don't delete the timer |
|||
const static int DEFCALL_RUNANDDEL = 2; // call the callback function and delete the timer |
|||
|
|||
// find the first available slot |
|||
int findFirstFreeSlot(); |
|||
|
|||
// value returned by the millis() function |
|||
// in the previous run() call |
|||
unsigned long prev_millis[MAX_TIMERS]; |
|||
|
|||
// pointers to the callback functions |
|||
timer_callback callbacks[MAX_TIMERS]; |
|||
|
|||
// delay values |
|||
long delays[MAX_TIMERS]; |
|||
|
|||
// number of runs to be executed for each timer |
|||
int maxNumRuns[MAX_TIMERS]; |
|||
|
|||
// number of executed runs for each timer |
|||
int numRuns[MAX_TIMERS]; |
|||
|
|||
// which timers are enabled |
|||
boolean enabled[MAX_TIMERS]; |
|||
|
|||
// deferred function call (sort of) - N.B.: this array is only used in run() |
|||
int toBeCalled[MAX_TIMERS]; |
|||
|
|||
// actual number of timers in use |
|||
int numTimers; |
|||
}; |
|||
|
|||
#endif |
@ -0,0 +1,97 @@ |
|||
/* DateStrings.cpp
|
|||
* Definitions for date strings for use with the Time library |
|||
* |
|||
* Updated for Arduino 1.5.7 18 July 2014 |
|||
* |
|||
* No memory is consumed in the sketch if your code does not call any of the string methods |
|||
* You can change the text of the strings, make sure the short strings are each exactly 3 characters |
|||
* the long strings can be any length up to the constant dt_MAX_STRING_LEN defined in TimeLib.h |
|||
* |
|||
*/ |
|||
|
|||
#if defined(__AVR__)
|
|||
#include <avr/pgmspace.h>
|
|||
#else
|
|||
// for compatiblity with Arduino Due and Teensy 3.0 and maybe others?
|
|||
#define PROGMEM
|
|||
#define PGM_P const char *
|
|||
#define pgm_read_byte(addr) (*(const unsigned char *)(addr))
|
|||
#define pgm_read_word(addr) (*(const unsigned char **)(addr))
|
|||
#define strcpy_P(dest, src) strcpy((dest), (src))
|
|||
#endif
|
|||
#include <string.h> // for strcpy_P or strcpy
|
|||
#include "TimeLib.h"
|
|||
|
|||
// the short strings for each day or month must be exactly dt_SHORT_STR_LEN
|
|||
#define dt_SHORT_STR_LEN 3 // the length of short strings
|
|||
|
|||
static char buffer[dt_MAX_STRING_LEN+1]; // must be big enough for longest string and the terminating null
|
|||
|
|||
const char monthStr0[] PROGMEM = ""; |
|||
const char monthStr1[] PROGMEM = "January"; |
|||
const char monthStr2[] PROGMEM = "February"; |
|||
const char monthStr3[] PROGMEM = "March"; |
|||
const char monthStr4[] PROGMEM = "April"; |
|||
const char monthStr5[] PROGMEM = "May"; |
|||
const char monthStr6[] PROGMEM = "June"; |
|||
const char monthStr7[] PROGMEM = "July"; |
|||
const char monthStr8[] PROGMEM = "August"; |
|||
const char monthStr9[] PROGMEM = "September"; |
|||
const char monthStr10[] PROGMEM = "October"; |
|||
const char monthStr11[] PROGMEM = "November"; |
|||
const char monthStr12[] PROGMEM = "December"; |
|||
|
|||
const PROGMEM char * const PROGMEM monthNames_P[] = |
|||
{ |
|||
monthStr0,monthStr1,monthStr2,monthStr3,monthStr4,monthStr5,monthStr6, |
|||
monthStr7,monthStr8,monthStr9,monthStr10,monthStr11,monthStr12 |
|||
}; |
|||
|
|||
const char monthShortNames_P[] PROGMEM = "ErrJanFebMarAprMayJunJulAugSepOctNovDec"; |
|||
|
|||
const char dayStr0[] PROGMEM = "Err"; |
|||
const char dayStr1[] PROGMEM = "Sunday"; |
|||
const char dayStr2[] PROGMEM = "Monday"; |
|||
const char dayStr3[] PROGMEM = "Tuesday"; |
|||
const char dayStr4[] PROGMEM = "Wednesday"; |
|||
const char dayStr5[] PROGMEM = "Thursday"; |
|||
const char dayStr6[] PROGMEM = "Friday"; |
|||
const char dayStr7[] PROGMEM = "Saturday"; |
|||
|
|||
const PROGMEM char * const PROGMEM dayNames_P[] = |
|||
{ |
|||
dayStr0,dayStr1,dayStr2,dayStr3,dayStr4,dayStr5,dayStr6,dayStr7 |
|||
}; |
|||
|
|||
const char dayShortNames_P[] PROGMEM = "ErrSunMonTueWedThuFriSat"; |
|||
|
|||
/* functions to return date strings */ |
|||
|
|||
char* monthStr(uint8_t month) |
|||
{ |
|||
strcpy_P(buffer, (PGM_P)pgm_read_word(&(monthNames_P[month]))); |
|||
return buffer; |
|||
} |
|||
|
|||
char* monthShortStr(uint8_t month) |
|||
{ |
|||
for (int i=0; i < dt_SHORT_STR_LEN; i++) |
|||
buffer[i] = pgm_read_byte(&(monthShortNames_P[i+ (month*dt_SHORT_STR_LEN)])); |
|||
buffer[dt_SHORT_STR_LEN] = 0; |
|||
return buffer; |
|||
} |
|||
|
|||
char* dayStr(uint8_t day) |
|||
{ |
|||
strcpy_P(buffer, (PGM_P)pgm_read_word(&(dayNames_P[day]))); |
|||
return buffer; |
|||
} |
|||
|
|||
char* dayShortStr(uint8_t day) |
|||
{ |
|||
uint8_t index = day*dt_SHORT_STR_LEN; |
|||
for (int i=0; i < dt_SHORT_STR_LEN; i++) |
|||
buffer[i] = pgm_read_byte(&(dayShortNames_P[index + i])); |
|||
buffer[dt_SHORT_STR_LEN] = 0; |
|||
return buffer; |
|||
} |
@ -0,0 +1,131 @@ |
|||
Readme file for Arduino Time Library |
|||
|
|||
Time is a library that provides timekeeping functionality for Arduino. |
|||
|
|||
The code is derived from the Playground DateTime library but is updated |
|||
to provide an API that is more flexable and easier to use. |
|||
|
|||
A primary goal was to enable date and time functionality that can be used with |
|||
a variety of external time sources with minimum differences required in sketch logic. |
|||
|
|||
Example sketches illustrate how similar sketch code can be used with: a Real Time Clock, |
|||
internet NTP time service, GPS time data, and Serial time messages from a computer |
|||
for time synchronization. |
|||
|
|||
The functions available in the library include: |
|||
|
|||
hour(); // the hour now (0-23) |
|||
minute(); // the minute now (0-59) |
|||
second(); // the second now (0-59) |
|||
day(); // the day now (1-31) |
|||
weekday(); // day of the week, Sunday is day 0 |
|||
month(); // the month now (1-12) |
|||
year(); // the full four digit year: (2009, 2010 etc) |
|||
|
|||
there are also functions to return the hour in 12 hour format |
|||
hourFormat12(); // the hour now in 12 hour format |
|||
isAM(); // returns true if time now is AM |
|||
isPM(); // returns true if time now is PM |
|||
|
|||
now(); // returns the current time as seconds since Jan 1 1970 |
|||
|
|||
The time and date functions can take an optional parameter for the time. This prevents |
|||
errors if the time rolls over between elements. For example, if a new minute begins |
|||
between getting the minute and second, the values will be inconsistent. Using the |
|||
following functions eliminates this probglem |
|||
time_t t = now(); // store the current time in time variable t |
|||
hour(t); // returns the hour for the given time t |
|||
minute(t); // returns the minute for the given time t |
|||
second(t); // returns the second for the given time t |
|||
day(t); // the day for the given time t |
|||
weekday(t); // day of the week for the given time t |
|||
month(t); // the month for the given time t |
|||
year(t); // the year for the given time t |
|||
|
|||
|
|||
Functions for managing the timer services are: |
|||
setTime(t); // set the system time to the give time t |
|||
setTime(hr,min,sec,day,mnth,yr); // alternative to above, yr is 2 or 4 digit yr (2010 or 10 sets year to 2010) |
|||
adjustTime(adjustment); // adjust system time by adding the adjustment value |
|||
|
|||
timeStatus(); // indicates if time has been set and recently synchronized |
|||
// returns one of the following enumerations: |
|||
timeNotSet // the time has never been set, the clock started at Jan 1 1970 |
|||
timeNeedsSync // the time had been set but a sync attempt did not succeed |
|||
timeSet // the time is set and is synced |
|||
Time and Date values are not valid if the status is timeNotSet. Otherwise values can be used but |
|||
the returned time may have drifted if the status is timeNeedsSync. |
|||
|
|||
setSyncProvider(getTimeFunction); // set the external time provider |
|||
setSyncInterval(interval); // set the number of seconds between re-sync |
|||
|
|||
|
|||
There are many convenience macros in the time.h file for time constants and conversion of time units. |
|||
|
|||
To use the library, copy the download to the Library directory. |
|||
|
|||
The Time directory contains the Time library and some example sketches |
|||
illustrating how the library can be used with various time sources: |
|||
|
|||
- TimeSerial.pde shows Arduino as a clock without external hardware. |
|||
It is synchronized by time messages sent over the serial port. |
|||
A companion Processing sketch will automatically provide these messages |
|||
if it is running and connected to the Arduino serial port. |
|||
|
|||
- TimeSerialDateStrings.pde adds day and month name strings to the sketch above |
|||
Short (3 character) and long strings are available to print the days of |
|||
the week and names of the months. |
|||
|
|||
- TimeRTC uses a DS1307 real time clock to provide time synchronization. |
|||
A basic RTC library named DS1307RTC is included in the download. |
|||
To run this sketch the DS1307RTC library must be installed. |
|||
|
|||
- TimeRTCSet is similar to the above and adds the ability to set the Real Time Clock |
|||
|
|||
- TimeRTCLog demonstrates how to calculate the difference between times. |
|||
It is a vary simple logger application that monitors events on digtial pins |
|||
and prints (to the serial port) the time of an event and the time period since the previous event. |
|||
|
|||
- TimeNTP uses the Arduino Ethernet shield to access time using the internet NTP time service. |
|||
The NTP protocol uses UDP and the UdpBytewise library is required, see: |
|||
http://bitbucket.org/bjoern/arduino_osc/src/14667490521f/libraries/Ethernet/ |
|||
|
|||
- TimeGPS gets time from a GPS |
|||
This requires the TinyGPS library from Mikal Hart: |
|||
http://arduiniana.org/libraries/TinyGPS |
|||
|
|||
Differences between this code and the playground DateTime library |
|||
although the Time library is based on the DateTime codebase, the API has changed. |
|||
Changes in the Time library API: |
|||
- time elements are functions returning int (they are variables in DateTime) |
|||
- Years start from 1970 |
|||
- days of the week and months start from 1 (they start from 0 in DateTime) |
|||
- DateStrings do not require a seperate library |
|||
- time elements can be accessed non-atomically (in DateTime they are always atomic) |
|||
- function added to automatically sync time with extrnal source |
|||
- localTime and maketime parameters changed, localTime renamed to breakTime |
|||
|
|||
Technical notes: |
|||
|
|||
Internal system time is based on the standard Unix time_t. |
|||
The value is the number of seconds since Jan 1 1970. |
|||
System time begins at zero when the sketch starts. |
|||
|
|||
The internal time can be automatically synchronized at regular intervals to an external time source. |
|||
This is enabled by calling the setSyncProvider(provider) function - the provider argument is |
|||
the address of a function that returns the current time as a time_t. |
|||
See the sketches in the examples directory for usage. |
|||
|
|||
The default interval for re-syncing the time is 5 minutes but can be changed by calling the |
|||
setSyncInterval( interval) method to set the number of seconds between re-sync attempts. |
|||
|
|||
The Time library defines a structure for holding time elements that is a compact version of the C tm structure. |
|||
All the members of the Arduino tm structure are bytes and the year is offset from 1970. |
|||
Convenience macros provide conversion to and from the Arduino format. |
|||
|
|||
Low level functions to convert between system time and individual time elements are provided: |
|||
breakTime( time, &tm); // break time_t into elements stored in tm struct |
|||
makeTime( &tm); // return time_t from elements stored in tm struct |
|||
|
|||
The DS1307RTC library included in the download provides an example of how a time provider |
|||
can use the low level functions to interface with the Time library. |
@ -0,0 +1,321 @@ |
|||
/*
|
|||
time.c - low level time and date functions |
|||
Copyright (c) Michael Margolis 2009-2014 |
|||
|
|||
This library is free software; you can redistribute it and/or |
|||
modify it under the terms of the GNU Lesser General Public |
|||
License as published by the Free Software Foundation; either |
|||
version 2.1 of the License, or (at your option) any later version. |
|||
|
|||
This library is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
Lesser General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU Lesser General Public |
|||
License along with this library; if not, write to the Free Software |
|||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|||
|
|||
1.0 6 Jan 2010 - initial release |
|||
1.1 12 Feb 2010 - fixed leap year calculation error |
|||
1.2 1 Nov 2010 - fixed setTime bug (thanks to Korman for this) |
|||
1.3 24 Mar 2012 - many edits by Paul Stoffregen: fixed timeStatus() to update |
|||
status, updated examples for Arduino 1.0, fixed ARM |
|||
compatibility issues, added TimeArduinoDue and TimeTeensy3 |
|||
examples, add error checking and messages to RTC examples, |
|||
add examples to DS1307RTC library. |
|||
1.4 5 Sep 2014 - compatibility with Arduino 1.5.7 |
|||
*/ |
|||
|
|||
#if ARDUINO >= 100
|
|||
#include <Arduino.h>
|
|||
#else
|
|||
#include <WProgram.h>
|
|||
#endif
|
|||
|
|||
#include "TimeLib.h"
|
|||
|
|||
static tmElements_t tm; // a cache of time elements
|
|||
static time_t cacheTime; // the time the cache was updated
|
|||
static uint32_t syncInterval = 300; // time sync will be attempted after this many seconds
|
|||
|
|||
void refreshCache(time_t t) { |
|||
if (t != cacheTime) { |
|||
breakTime(t, tm); |
|||
cacheTime = t; |
|||
} |
|||
} |
|||
|
|||
int hour() { // the hour now
|
|||
return hour(now()); |
|||
} |
|||
|
|||
int hour(time_t t) { // the hour for the given time
|
|||
refreshCache(t); |
|||
return tm.Hour; |
|||
} |
|||
|
|||
int hourFormat12() { // the hour now in 12 hour format
|
|||
return hourFormat12(now()); |
|||
} |
|||
|
|||
int hourFormat12(time_t t) { // the hour for the given time in 12 hour format
|
|||
refreshCache(t); |
|||
if( tm.Hour == 0 ) |
|||
return 12; // 12 midnight
|
|||
else if( tm.Hour > 12) |
|||
return tm.Hour - 12 ; |
|||
else |
|||
return tm.Hour ; |
|||
} |
|||
|
|||
uint8_t isAM() { // returns true if time now is AM
|
|||
return !isPM(now()); |
|||
} |
|||
|
|||
uint8_t isAM(time_t t) { // returns true if given time is AM
|
|||
return !isPM(t); |
|||
} |
|||
|
|||
uint8_t isPM() { // returns true if PM
|
|||
return isPM(now()); |
|||
} |
|||
|
|||
uint8_t isPM(time_t t) { // returns true if PM
|
|||
return (hour(t) >= 12); |
|||
} |
|||
|
|||
int minute() { |
|||
return minute(now()); |
|||
} |
|||
|
|||
int minute(time_t t) { // the minute for the given time
|
|||
refreshCache(t); |
|||
return tm.Minute; |
|||
} |
|||
|
|||
int second() { |
|||
return second(now()); |
|||
} |
|||
|
|||
int second(time_t t) { // the second for the given time
|
|||
refreshCache(t); |
|||
return tm.Second; |
|||
} |
|||
|
|||
int day(){ |
|||
return(day(now())); |
|||
} |
|||
|
|||
int day(time_t t) { // the day for the given time (0-6)
|
|||
refreshCache(t); |
|||
return tm.Day; |
|||
} |
|||
|
|||
int weekday() { // Sunday is day 1
|
|||
return weekday(now()); |
|||
} |
|||
|
|||
int weekday(time_t t) { |
|||
refreshCache(t); |
|||
return tm.Wday; |
|||
} |
|||
|
|||
int month(){ |
|||
return month(now()); |
|||
} |
|||
|
|||
int month(time_t t) { // the month for the given time
|
|||
refreshCache(t); |
|||
return tm.Month; |
|||
} |
|||
|
|||
int year() { // as in Processing, the full four digit year: (2009, 2010 etc)
|
|||
return year(now()); |
|||
} |
|||
|
|||
int year(time_t t) { // the year for the given time
|
|||
refreshCache(t); |
|||
return tmYearToCalendar(tm.Year); |
|||
} |
|||
|
|||
/*============================================================================*/ |
|||
/* functions to convert to and from system time */ |
|||
/* These are for interfacing with time serivces and are not normally needed in a sketch */ |
|||
|
|||
// leap year calulator expects year argument as years offset from 1970
|
|||
#define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) )
|
|||
|
|||
static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0
|
|||
|
|||
void breakTime(time_t timeInput, tmElements_t &tm){ |
|||
// break the given time_t into time components
|
|||
// this is a more compact version of the C library localtime function
|
|||
// note that year is offset from 1970 !!!
|
|||
|
|||
uint8_t year; |
|||
uint8_t month, monthLength; |
|||
uint32_t time; |
|||
unsigned long days; |
|||
|
|||
time = (uint32_t)timeInput; |
|||
tm.Second = time % 60; |
|||
time /= 60; // now it is minutes
|
|||
tm.Minute = time % 60; |
|||
time /= 60; // now it is hours
|
|||
tm.Hour = time % 24; |
|||
time /= 24; // now it is days
|
|||
tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1
|
|||
|
|||
year = 0; |
|||
days = 0; |
|||
while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { |
|||
year++; |
|||
} |
|||
tm.Year = year; // year is offset from 1970
|
|||
|
|||
days -= LEAP_YEAR(year) ? 366 : 365; |
|||
time -= days; // now it is days in this year, starting at 0
|
|||
|
|||
days=0; |
|||
month=0; |
|||
monthLength=0; |
|||
for (month=0; month<12; month++) { |
|||
if (month==1) { // february
|
|||
if (LEAP_YEAR(year)) { |
|||
monthLength=29; |
|||
} else { |
|||
monthLength=28; |
|||
} |
|||
} else { |
|||
monthLength = monthDays[month]; |
|||
} |
|||
|
|||
if (time >= monthLength) { |
|||
time -= monthLength; |
|||
} else { |
|||
break; |
|||
} |
|||
} |
|||
tm.Month = month + 1; // jan is month 1
|
|||
tm.Day = time + 1; // day of month
|
|||
} |
|||
|
|||
time_t makeTime(tmElements_t &tm){ |
|||
// assemble time elements into time_t
|
|||
// note year argument is offset from 1970 (see macros in time.h to convert to other formats)
|
|||
// previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9
|
|||
|
|||
int i; |
|||
uint32_t seconds; |
|||
|
|||
// seconds from 1970 till 1 jan 00:00:00 of the given year
|
|||
seconds= tm.Year*(SECS_PER_DAY * 365); |
|||
for (i = 0; i < tm.Year; i++) { |
|||
if (LEAP_YEAR(i)) { |
|||
seconds += SECS_PER_DAY; // add extra days for leap years
|
|||
} |
|||
} |
|||
|
|||
// add days for this year, months start from 1
|
|||
for (i = 1; i < tm.Month; i++) { |
|||
if ( (i == 2) && LEAP_YEAR(tm.Year)) { |
|||
seconds += SECS_PER_DAY * 29; |
|||
} else { |
|||
seconds += SECS_PER_DAY * monthDays[i-1]; //monthDay array starts from 0
|
|||
} |
|||
} |
|||
seconds+= (tm.Day-1) * SECS_PER_DAY; |
|||
seconds+= tm.Hour * SECS_PER_HOUR; |
|||
seconds+= tm.Minute * SECS_PER_MIN; |
|||
seconds+= tm.Second; |
|||
return (time_t)seconds; |
|||
} |
|||
/*=====================================================*/ |
|||
/* Low level system time functions */ |
|||
|
|||
static uint32_t sysTime = 0; |
|||
static uint32_t prevMillis = 0; |
|||
static uint32_t nextSyncTime = 0; |
|||
static timeStatus_t Status = timeNotSet; |
|||
|
|||
getExternalTime getTimePtr; // pointer to external sync function
|
|||
//setExternalTime setTimePtr; // not used in this version
|
|||
|
|||
#ifdef TIME_DRIFT_INFO // define this to get drift data
|
|||
time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync
|
|||
#endif
|
|||
|
|||
|
|||
time_t now() { |
|||
// calculate number of seconds passed since last call to now()
|
|||
while (millis() - prevMillis >= 1000) { |
|||
// millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference
|
|||
sysTime++; |
|||
prevMillis += 1000; |
|||
#ifdef TIME_DRIFT_INFO
|
|||
sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift
|
|||
#endif
|
|||
} |
|||
if (nextSyncTime <= sysTime) { |
|||
if (getTimePtr != 0) { |
|||
time_t t = getTimePtr(); |
|||
if (t != 0) { |
|||
setTime(t); |
|||
} else { |
|||
nextSyncTime = sysTime + syncInterval; |
|||
Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync; |
|||
} |
|||
} |
|||
} |
|||
return (time_t)sysTime; |
|||
} |
|||
|
|||
void setTime(time_t t) { |
|||
#ifdef TIME_DRIFT_INFO
|
|||
if(sysUnsyncedTime == 0) |
|||
sysUnsyncedTime = t; // store the time of the first call to set a valid Time
|
|||
#endif
|
|||
|
|||
sysTime = (uint32_t)t; |
|||
nextSyncTime = (uint32_t)t + syncInterval; |
|||
Status = timeSet; |
|||
prevMillis = millis(); // restart counting from now (thanks to Korman for this fix)
|
|||
} |
|||
|
|||
void setTime(int hr,int min,int sec,int dy, int mnth, int yr){ |
|||
// year can be given as full four digit year or two digts (2010 or 10 for 2010);
|
|||
//it is converted to years since 1970
|
|||
if( yr > 99) |
|||
yr = yr - 1970; |
|||
else |
|||
yr += 30; |
|||
tm.Year = yr; |
|||
tm.Month = mnth; |
|||
tm.Day = dy; |
|||
tm.Hour = hr; |
|||
tm.Minute = min; |
|||
tm.Second = sec; |
|||
setTime(makeTime(tm)); |
|||
} |
|||
|
|||
void adjustTime(long adjustment) { |
|||
sysTime += adjustment; |
|||
} |
|||
|
|||
// indicates if time has been set and recently synchronized
|
|||
timeStatus_t timeStatus() { |
|||
now(); // required to actually update the status
|
|||
return Status; |
|||
} |
|||
|
|||
void setSyncProvider( getExternalTime getTimeFunction){ |
|||
getTimePtr = getTimeFunction; |
|||
nextSyncTime = sysTime; |
|||
now(); // this will sync the clock
|
|||
} |
|||
|
|||
void setSyncInterval(time_t interval){ // set the number of seconds between re-sync
|
|||
syncInterval = (uint32_t)interval; |
|||
nextSyncTime = sysTime + syncInterval; |
|||
} |
@ -0,0 +1 @@ |
|||
#include "TimeLib.h" |
@ -0,0 +1,144 @@ |
|||
/* |
|||
time.h - low level time and date functions |
|||
*/ |
|||
|
|||
/* |
|||
July 3 2011 - fixed elapsedSecsThisWeek macro (thanks Vincent Valdy for this) |
|||
- fixed daysToTime_t macro (thanks maniacbug) |
|||
*/ |
|||
|
|||
#ifndef _Time_h |
|||
#ifdef __cplusplus |
|||
#define _Time_h |
|||
|
|||
#include <inttypes.h> |
|||
#ifndef __AVR__ |
|||
#include <sys/types.h> // for __time_t_defined, but avr libc lacks sys/types.h |
|||
#endif |
|||
|
|||
|
|||
#if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc |
|||
typedef unsigned long time_t; |
|||
#endif |
|||
|
|||
|
|||
// This ugly hack allows us to define C++ overloaded functions, when included |
|||
// from within an extern "C", as newlib's sys/stat.h does. Actually it is |
|||
// intended to include "time.h" from the C library (on ARM, but AVR does not |
|||
// have that file at all). On Mac and Windows, the compiler will find this |
|||
// "Time.h" instead of the C library "time.h", so we may cause other weird |
|||
// and unpredictable effects by conflicting with the C library header "time.h", |
|||
// but at least this hack lets us define C++ functions as intended. Hopefully |
|||
// nothing too terrible will result from overriding the C library header?! |
|||
extern "C++" { |
|||
typedef enum {timeNotSet, timeNeedsSync, timeSet |
|||
} timeStatus_t ; |
|||
|
|||
typedef enum { |
|||
dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday |
|||
} timeDayOfWeek_t; |
|||
|
|||
typedef enum { |
|||
tmSecond, tmMinute, tmHour, tmWday, tmDay,tmMonth, tmYear, tmNbrFields |
|||
} tmByteFields; |
|||
|
|||
typedef struct { |
|||
uint8_t Second; |
|||
uint8_t Minute; |
|||
uint8_t Hour; |
|||
uint8_t Wday; // day of week, sunday is day 1 |
|||
uint8_t Day; |
|||
uint8_t Month; |
|||
uint8_t Year; // offset from 1970; |
|||
} tmElements_t, TimeElements, *tmElementsPtr_t; |
|||
|
|||
//convenience macros to convert to and from tm years |
|||
#define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year |
|||
#define CalendarYrToTm(Y) ((Y) - 1970) |
|||
#define tmYearToY2k(Y) ((Y) - 30) // offset is from 2000 |
|||
#define y2kYearToTm(Y) ((Y) + 30) |
|||
|
|||
typedef time_t(*getExternalTime)(); |
|||
//typedef void (*setExternalTime)(const time_t); // not used in this version |
|||
|
|||
|
|||
/*==============================================================================*/ |
|||
/* Useful Constants */ |
|||
#define SECS_PER_MIN ((time_t)(60UL)) |
|||
#define SECS_PER_HOUR ((time_t)(3600UL)) |
|||
#define SECS_PER_DAY ((time_t)(SECS_PER_HOUR * 24UL)) |
|||
#define DAYS_PER_WEEK ((time_t)(7UL)) |
|||
#define SECS_PER_WEEK ((time_t)(SECS_PER_DAY * DAYS_PER_WEEK)) |
|||
#define SECS_PER_YEAR ((time_t)(SECS_PER_WEEK * 52UL)) |
|||
#define SECS_YR_2000 ((time_t)(946684800UL)) // the time at the start of y2k |
|||
|
|||
/* Useful Macros for getting elapsed time */ |
|||
#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN) |
|||
#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) |
|||
#define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR) |
|||
#define dayOfWeek(_time_) ((( _time_ / SECS_PER_DAY + 4) % DAYS_PER_WEEK)+1) // 1 = Sunday |
|||
#define elapsedDays(_time_) ( _time_ / SECS_PER_DAY) // this is number of days since Jan 1 1970 |
|||
#define elapsedSecsToday(_time_) (_time_ % SECS_PER_DAY) // the number of seconds since last midnight |
|||
// The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971 |
|||
// Always set the correct time before settting alarms |
|||
#define previousMidnight(_time_) (( _time_ / SECS_PER_DAY) * SECS_PER_DAY) // time at the start of the given day |
|||
#define nextMidnight(_time_) ( previousMidnight(_time_) + SECS_PER_DAY ) // time at the end of the given day |
|||
#define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY) ) // note that week starts on day 1 |
|||
#define previousSunday(_time_) (_time_ - elapsedSecsThisWeek(_time_)) // time at the start of the week for the given time |
|||
#define nextSunday(_time_) ( previousSunday(_time_)+SECS_PER_WEEK) // time at the end of the week for the given time |
|||
|
|||
|
|||
/* Useful Macros for converting elapsed time to a time_t */ |
|||
#define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN) |
|||
#define hoursToTime_t ((H)) ( (H) * SECS_PER_HOUR) |
|||
#define daysToTime_t ((D)) ( (D) * SECS_PER_DAY) // fixed on Jul 22 2011 |
|||
#define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK) |
|||
|
|||
/*============================================================================*/ |
|||
/* time and date functions */ |
|||
int hour(); // the hour now |
|||
int hour(time_t t); // the hour for the given time |
|||
int hourFormat12(); // the hour now in 12 hour format |
|||
int hourFormat12(time_t t); // the hour for the given time in 12 hour format |
|||
uint8_t isAM(); // returns true if time now is AM |
|||
uint8_t isAM(time_t t); // returns true the given time is AM |
|||
uint8_t isPM(); // returns true if time now is PM |
|||
uint8_t isPM(time_t t); // returns true the given time is PM |
|||
int minute(); // the minute now |
|||
int minute(time_t t); // the minute for the given time |
|||
int second(); // the second now |
|||
int second(time_t t); // the second for the given time |
|||
int day(); // the day now |
|||
int day(time_t t); // the day for the given time |
|||
int weekday(); // the weekday now (Sunday is day 1) |
|||
int weekday(time_t t); // the weekday for the given time |
|||
int month(); // the month now (Jan is month 1) |
|||
int month(time_t t); // the month for the given time |
|||
int year(); // the full four digit year: (2009, 2010 etc) |
|||
int year(time_t t); // the year for the given time |
|||
|
|||
time_t now(); // return the current time as seconds since Jan 1 1970 |
|||
void setTime(time_t t); |
|||
void setTime(int hr,int min,int sec,int day, int month, int yr); |
|||
void adjustTime(long adjustment); |
|||
|
|||
/* date strings */ |
|||
#define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null) |
|||
char* monthStr(uint8_t month); |
|||
char* dayStr(uint8_t day); |
|||
char* monthShortStr(uint8_t month); |
|||
char* dayShortStr(uint8_t day); |
|||
|
|||
/* time sync functions */ |
|||
timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized |
|||
void setSyncProvider( getExternalTime getTimeFunction); // identify the external time provider |
|||
void setSyncInterval(time_t interval); // set the number of seconds between re-sync |
|||
|
|||
/* low level functions to convert to and from system time */ |
|||
void breakTime(time_t time, tmElements_t &tm); // break time_t into elements |
|||
time_t makeTime(tmElements_t &tm); // convert time elements into time_t |
|||
|
|||
} // extern "C++" |
|||
#endif // __cplusplus |
|||
#endif /* _Time_h */ |
|||
|
@ -0,0 +1,78 @@ |
|||
/** |
|||
* SyncArduinoClock. |
|||
* |
|||
* portIndex must be set to the port connected to the Arduino |
|||
* |
|||
* The current time is sent in response to request message from Arduino |
|||
* or by clicking the display window |
|||
* |
|||
* The time message is 11 ASCII text characters; a header (the letter 'T') |
|||
* followed by the ten digit system time (unix time) |
|||
*/ |
|||
|
|||
|
|||
import processing.serial.*; |
|||
import java.util.Date; |
|||
import java.util.Calendar; |
|||
import java.util.GregorianCalendar; |
|||
|
|||
public static final short portIndex = 0; // select the com port, 0 is the first port |
|||
public static final String TIME_HEADER = "T"; //header for arduino serial time message |
|||
public static final char TIME_REQUEST = 7; // ASCII bell character |
|||
public static final char LF = 10; // ASCII linefeed |
|||
public static final char CR = 13; // ASCII linefeed |
|||
Serial myPort; // Create object from Serial class |
|||
|
|||
void setup() { |
|||
size(200, 200); |
|||
println(Serial.list()); |
|||
println(" Connecting to -> " + Serial.list()[portIndex]); |
|||
myPort = new Serial(this,Serial.list()[portIndex], 9600); |
|||
println(getTimeNow()); |
|||
} |
|||
|
|||
void draw() |
|||
{ |
|||
textSize(20); |
|||
textAlign(CENTER); |
|||
fill(0); |
|||
text("Click to send\nTime Sync", 0, 75, 200, 175); |
|||
if ( myPort.available() > 0) { // If data is available, |
|||
char val = char(myPort.read()); // read it and store it in val |
|||
if(val == TIME_REQUEST){ |
|||
long t = getTimeNow(); |
|||
sendTimeMessage(TIME_HEADER, t); |
|||
} |
|||
else |
|||
{ |
|||
if(val == LF) |
|||
; //igonore |
|||
else if(val == CR) |
|||
println(); |
|||
else |
|||
print(val); // echo everying but time request |
|||
} |
|||
} |
|||
} |
|||
|
|||
void mousePressed() { |
|||
sendTimeMessage( TIME_HEADER, getTimeNow()); |
|||
} |
|||
|
|||
|
|||
void sendTimeMessage(String header, long time) { |
|||
String timeStr = String.valueOf(time); |
|||
myPort.write(header); // send header and time to arduino |
|||
myPort.write(timeStr); |
|||
myPort.write('\n'); |
|||
} |
|||
|
|||
long getTimeNow(){ |
|||
// java time is in ms, we want secs |
|||
Date d = new Date(); |
|||
Calendar cal = new GregorianCalendar(); |
|||
long current = d.getTime()/1000; |
|||
long timezone = cal.get(cal.ZONE_OFFSET)/1000; |
|||
long daylight = cal.get(cal.DST_OFFSET)/1000; |
|||
return current + timezone + daylight; |
|||
} |
@ -0,0 +1,9 @@ |
|||
SyncArduinoClock is a Processing sketch that responds to Arduino requests for |
|||
time synchronization messages. |
|||
|
|||
The portIndex must be set the Serial port connected to Arduino. |
|||
|
|||
Download TimeSerial.pde onto Arduino and you should see the time |
|||
message displayed when you run SyncArduinoClock in Processing. |
|||
The Arduino time is set from the time on your computer through the |
|||
Processing sketch. |
@ -0,0 +1,71 @@ |
|||
/*
|
|||
* TimeRTC.pde |
|||
* example code illustrating Time library with Real Time Clock. |
|||
* |
|||
* This example requires Markus Lange's Arduino Due RTC Library |
|||
* https://github.com/MarkusLange/Arduino-Due-RTC-Library
|
|||
*/ |
|||
|
|||
#include <TimeLib.h>
|
|||
#include <rtc_clock.h>
|
|||
|
|||
// Select the Slowclock source
|
|||
//RTC_clock rtc_clock(RC);
|
|||
RTC_clock rtc_clock(XTAL); |
|||
|
|||
void setup() { |
|||
Serial.begin(9600); |
|||
rtc_clock.init(); |
|||
if (rtc_clock.date_already_set() == 0) { |
|||
// Unfortunately, the Arduino Due hardware does not seem to
|
|||
// be designed to maintain the RTC clock state when the
|
|||
// board resets. Markus described it thusly: "Uhh the Due
|
|||
// does reset with the NRSTB pin. This resets the full chip
|
|||
// with all backup regions including RTC, RTT and SC. Only
|
|||
// if the reset is done with the NRST pin will these regions
|
|||
// stay with their old values."
|
|||
rtc_clock.set_time(__TIME__); |
|||
rtc_clock.set_date(__DATE__); |
|||
// However, this might work on other unofficial SAM3X boards
|
|||
// with different reset circuitry than Arduino Due?
|
|||
} |
|||
setSyncProvider(getArduinoDueTime); |
|||
if(timeStatus()!= timeSet) |
|||
Serial.println("Unable to sync with the RTC"); |
|||
else |
|||
Serial.println("RTC has set the system time"); |
|||
} |
|||
|
|||
time_t getArduinoDueTime() |
|||
{ |
|||
return rtc_clock.unixtime(); |
|||
} |
|||
|
|||
void loop() |
|||
{ |
|||
digitalClockDisplay(); |
|||
delay(1000); |
|||
} |
|||
|
|||
void digitalClockDisplay(){ |
|||
// digital clock display of the time
|
|||
Serial.print(hour()); |
|||
printDigits(minute()); |
|||
printDigits(second()); |
|||
Serial.print(" "); |
|||
Serial.print(day()); |
|||
Serial.print(" "); |
|||
Serial.print(month()); |
|||
Serial.print(" "); |
|||
Serial.print(year()); |
|||
Serial.println(); |
|||
} |
|||
|
|||
void printDigits(int digits){ |
|||
// utility function for digital clock display: prints preceding colon and leading 0
|
|||
Serial.print(":"); |
|||
if(digits < 10) |
|||
Serial.print('0'); |
|||
Serial.print(digits); |
|||
} |
|||
|
@ -0,0 +1,87 @@ |
|||
/*
|
|||
* TimeGPS.pde |
|||
* example code illustrating time synced from a GPS |
|||
* |
|||
*/ |
|||
|
|||
#include <TimeLib.h>
|
|||
#include <TinyGPS.h> // http://arduiniana.org/libraries/TinyGPS/
|
|||
#include <SoftwareSerial.h>
|
|||
// TinyGPS and SoftwareSerial libraries are the work of Mikal Hart
|
|||
|
|||
SoftwareSerial SerialGPS = SoftwareSerial(10, 11); // receive on pin 10
|
|||
TinyGPS gps; |
|||
|
|||
// To use a hardware serial port, which is far more efficient than
|
|||
// SoftwareSerial, uncomment this line and remove SoftwareSerial
|
|||
//#define SerialGPS Serial1
|
|||
|
|||
// Offset hours from gps time (UTC)
|
|||
const int offset = 1; // Central European Time
|
|||
//const int offset = -5; // Eastern Standard Time (USA)
|
|||
//const int offset = -4; // Eastern Daylight Time (USA)
|
|||
//const int offset = -8; // Pacific Standard Time (USA)
|
|||
//const int offset = -7; // Pacific Daylight Time (USA)
|
|||
|
|||
// Ideally, it should be possible to learn the time zone
|
|||
// based on the GPS position data. However, that would
|
|||
// require a complex library, probably incorporating some
|
|||
// sort of database using Eric Muller's time zone shape
|
|||
// maps, at http://efele.net/maps/tz/
|
|||
|
|||
time_t prevDisplay = 0; // when the digital clock was displayed
|
|||
|
|||
void setup() |
|||
{ |
|||
Serial.begin(9600); |
|||
while (!Serial) ; // Needed for Leonardo only
|
|||
SerialGPS.begin(4800); |
|||
Serial.println("Waiting for GPS time ... "); |
|||
} |
|||
|
|||
void loop() |
|||
{ |
|||
while (SerialGPS.available()) { |
|||
if (gps.encode(SerialGPS.read())) { // process gps messages
|
|||
// when TinyGPS reports new data...
|
|||
unsigned long age; |
|||
int Year; |
|||
byte Month, Day, Hour, Minute, Second; |
|||
gps.crack_datetime(&Year, &Month, &Day, &Hour, &Minute, &Second, NULL, &age); |
|||
if (age < 500) { |
|||
// set the Time to the latest GPS reading
|
|||
setTime(Hour, Minute, Second, Day, Month, Year); |
|||
adjustTime(offset * SECS_PER_HOUR); |
|||
} |
|||
} |
|||
} |
|||
if (timeStatus()!= timeNotSet) { |
|||
if (now() != prevDisplay) { //update the display only if the time has changed
|
|||
prevDisplay = now(); |
|||
digitalClockDisplay(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void digitalClockDisplay(){ |
|||
// digital clock display of the time
|
|||
Serial.print(hour()); |
|||
printDigits(minute()); |
|||
printDigits(second()); |
|||
Serial.print(" "); |
|||
Serial.print(day()); |
|||
Serial.print(" "); |
|||
Serial.print(month()); |
|||
Serial.print(" "); |
|||
Serial.print(year()); |
|||
Serial.println(); |
|||
} |
|||
|
|||
void printDigits(int digits) { |
|||
// utility function for digital clock display: prints preceding colon and leading 0
|
|||
Serial.print(":"); |
|||
if(digits < 10) |
|||
Serial.print('0'); |
|||
Serial.print(digits); |
|||
} |
|||
|
@ -0,0 +1,135 @@ |
|||
/*
|
|||
* Time_NTP.pde |
|||
* Example showing time sync to NTP time source |
|||
* |
|||
* This sketch uses the Ethernet library |
|||
*/ |
|||
|
|||
#include <TimeLib.h>
|
|||
#include <Ethernet.h>
|
|||
#include <EthernetUdp.h>
|
|||
#include <SPI.h>
|
|||
|
|||
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; |
|||
// NTP Servers:
|
|||
IPAddress timeServer(132, 163, 4, 101); // time-a.timefreq.bldrdoc.gov
|
|||
// IPAddress timeServer(132, 163, 4, 102); // time-b.timefreq.bldrdoc.gov
|
|||
// IPAddress timeServer(132, 163, 4, 103); // time-c.timefreq.bldrdoc.gov
|
|||
|
|||
|
|||
const int timeZone = 1; // Central European Time
|
|||
//const int timeZone = -5; // Eastern Standard Time (USA)
|
|||
//const int timeZone = -4; // Eastern Daylight Time (USA)
|
|||
//const int timeZone = -8; // Pacific Standard Time (USA)
|
|||
//const int timeZone = -7; // Pacific Daylight Time (USA)
|
|||
|
|||
|
|||
EthernetUDP Udp; |
|||
unsigned int localPort = 8888; // local port to listen for UDP packets
|
|||
|
|||
void setup() |
|||
{ |
|||
Serial.begin(9600); |
|||
while (!Serial) ; // Needed for Leonardo only
|
|||
delay(250); |
|||
Serial.println("TimeNTP Example"); |
|||
if (Ethernet.begin(mac) == 0) { |
|||
// no point in carrying on, so do nothing forevermore:
|
|||
while (1) { |
|||
Serial.println("Failed to configure Ethernet using DHCP"); |
|||
delay(10000); |
|||
} |
|||
} |
|||
Serial.print("IP number assigned by DHCP is "); |
|||
Serial.println(Ethernet.localIP()); |
|||
Udp.begin(localPort); |
|||
Serial.println("waiting for sync"); |
|||
setSyncProvider(getNtpTime); |
|||
} |
|||
|
|||
time_t prevDisplay = 0; // when the digital clock was displayed
|
|||
|
|||
void loop() |
|||
{ |
|||
if (timeStatus() != timeNotSet) { |
|||
if (now() != prevDisplay) { //update the display only if time has changed
|
|||
prevDisplay = now(); |
|||
digitalClockDisplay(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void digitalClockDisplay(){ |
|||
// digital clock display of the time
|
|||
Serial.print(hour()); |
|||
printDigits(minute()); |
|||
printDigits(second()); |
|||
Serial.print(" "); |
|||
Serial.print(day()); |
|||
Serial.print(" "); |
|||
Serial.print(month()); |
|||
Serial.print(" "); |
|||
Serial.print(year()); |
|||
Serial.println(); |
|||
} |
|||
|
|||
void printDigits(int digits){ |
|||
// utility for digital clock display: prints preceding colon and leading 0
|
|||
Serial.print(":"); |
|||
if(digits < 10) |
|||
Serial.print('0'); |
|||
Serial.print(digits); |
|||
} |
|||
|
|||
/*-------- NTP code ----------*/ |
|||
|
|||
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
|
|||
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
|
|||
|
|||
time_t getNtpTime() |
|||
{ |
|||
while (Udp.parsePacket() > 0) ; // discard any previously received packets
|
|||
Serial.println("Transmit NTP Request"); |
|||
sendNTPpacket(timeServer); |
|||
uint32_t beginWait = millis(); |
|||
while (millis() - beginWait < 1500) { |
|||
int size = Udp.parsePacket(); |
|||
if (size >= NTP_PACKET_SIZE) { |
|||
Serial.println("Receive NTP Response"); |
|||
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
|
|||
unsigned long secsSince1900; |
|||
// convert four bytes starting at location 40 to a long integer
|
|||
secsSince1900 = (unsigned long)packetBuffer[40] << 24; |
|||
secsSince1900 |= (unsigned long)packetBuffer[41] << 16; |
|||
secsSince1900 |= (unsigned long)packetBuffer[42] << 8; |
|||
secsSince1900 |= (unsigned long)packetBuffer[43]; |
|||
return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; |
|||
} |
|||
} |
|||
Serial.println("No NTP Response :-("); |
|||
return 0; // return 0 if unable to get the time
|
|||
} |
|||
|
|||
// send an NTP request to the time server at the given address
|
|||
void sendNTPpacket(IPAddress &address) |
|||
{ |
|||
// set all bytes in the buffer to 0
|
|||
memset(packetBuffer, 0, NTP_PACKET_SIZE); |
|||
// Initialize values needed to form NTP request
|
|||
// (see URL above for details on the packets)
|
|||
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
|
|||
// 8 bytes of zero for Root Delay & Root Dispersion
|
|||
packetBuffer[12] = 49; |
|||
packetBuffer[13] = 0x4E; |
|||
packetBuffer[14] = 49; |
|||
packetBuffer[15] = 52; |
|||
// all NTP fields have been given values, now
|
|||
// you can send a packet requesting a timestamp:
|
|||
Udp.beginPacket(address, 123); //NTP requests are to port 123
|
|||
Udp.write(packetBuffer, NTP_PACKET_SIZE); |
|||
Udp.endPacket(); |
|||
} |
|||
|
@ -0,0 +1,156 @@ |
|||
/*
|
|||
* TimeNTP_ESP8266WiFi.ino |
|||
* Example showing time sync to NTP time source |
|||
* |
|||
* This sketch uses the ESP8266WiFi library |
|||
*/ |
|||
|
|||
#include <TimeLib.h>
|
|||
#include <ESP8266WiFi.h>
|
|||
#include <WiFiUdp.h>
|
|||
|
|||
const char ssid[] = "*************"; // your network SSID (name)
|
|||
const char pass[] = "********"; // your network password
|
|||
|
|||
// NTP Servers:
|
|||
static const char ntpServerName[] = "us.pool.ntp.org"; |
|||
//static const char ntpServerName[] = "time.nist.gov";
|
|||
//static const char ntpServerName[] = "time-a.timefreq.bldrdoc.gov";
|
|||
//static const char ntpServerName[] = "time-b.timefreq.bldrdoc.gov";
|
|||
//static const char ntpServerName[] = "time-c.timefreq.bldrdoc.gov";
|
|||
|
|||
const int timeZone = 1; // Central European Time
|
|||
//const int timeZone = -5; // Eastern Standard Time (USA)
|
|||
//const int timeZone = -4; // Eastern Daylight Time (USA)
|
|||
//const int timeZone = -8; // Pacific Standard Time (USA)
|
|||
//const int timeZone = -7; // Pacific Daylight Time (USA)
|
|||
|
|||
|
|||
WiFiUDP Udp; |
|||
unsigned int localPort = 8888; // local port to listen for UDP packets
|
|||
|
|||
time_t getNtpTime(); |
|||
void digitalClockDisplay(); |
|||
void printDigits(int digits); |
|||
void sendNTPpacket(IPAddress &address); |
|||
|
|||
void setup() |
|||
{ |
|||
Serial.begin(9600); |
|||
while (!Serial) ; // Needed for Leonardo only
|
|||
delay(250); |
|||
Serial.println("TimeNTP Example"); |
|||
Serial.print("Connecting to "); |
|||
Serial.println(ssid); |
|||
WiFi.begin(ssid, pass); |
|||
|
|||
while (WiFi.status() != WL_CONNECTED) { |
|||
delay(500); |
|||
Serial.print("."); |
|||
} |
|||
|
|||
Serial.print("IP number assigned by DHCP is "); |
|||
Serial.println(WiFi.localIP()); |
|||
Serial.println("Starting UDP"); |
|||
Udp.begin(localPort); |
|||
Serial.print("Local port: "); |
|||
Serial.println(Udp.localPort()); |
|||
Serial.println("waiting for sync"); |
|||
setSyncProvider(getNtpTime); |
|||
setSyncInterval(300); |
|||
} |
|||
|
|||
time_t prevDisplay = 0; // when the digital clock was displayed
|
|||
|
|||
void loop() |
|||
{ |
|||
if (timeStatus() != timeNotSet) { |
|||
if (now() != prevDisplay) { //update the display only if time has changed
|
|||
prevDisplay = now(); |
|||
digitalClockDisplay(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void digitalClockDisplay() |
|||
{ |
|||
// digital clock display of the time
|
|||
Serial.print(hour()); |
|||
printDigits(minute()); |
|||
printDigits(second()); |
|||
Serial.print(" "); |
|||
Serial.print(day()); |
|||
Serial.print("."); |
|||
Serial.print(month()); |
|||
Serial.print("."); |
|||
Serial.print(year()); |
|||
Serial.println(); |
|||
} |
|||
|
|||
void printDigits(int digits) |
|||
{ |
|||
// utility for digital clock display: prints preceding colon and leading 0
|
|||
Serial.print(":"); |
|||
if (digits < 10) |
|||
Serial.print('0'); |
|||
Serial.print(digits); |
|||
} |
|||
|
|||
/*-------- NTP code ----------*/ |
|||
|
|||
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
|
|||
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
|
|||
|
|||
time_t getNtpTime() |
|||
{ |
|||
IPAddress ntpServerIP; // NTP server's ip address
|
|||
|
|||
while (Udp.parsePacket() > 0) ; // discard any previously received packets
|
|||
Serial.println("Transmit NTP Request"); |
|||
// get a random server from the pool
|
|||
WiFi.hostByName(ntpServerName, ntpServerIP); |
|||
Serial.print(ntpServerName); |
|||
Serial.print(": "); |
|||
Serial.println(ntpServerIP); |
|||
sendNTPpacket(ntpServerIP); |
|||
uint32_t beginWait = millis(); |
|||
while (millis() - beginWait < 1500) { |
|||
int size = Udp.parsePacket(); |
|||
if (size >= NTP_PACKET_SIZE) { |
|||
Serial.println("Receive NTP Response"); |
|||
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
|
|||
unsigned long secsSince1900; |
|||
// convert four bytes starting at location 40 to a long integer
|
|||
secsSince1900 = (unsigned long)packetBuffer[40] << 24; |
|||
secsSince1900 |= (unsigned long)packetBuffer[41] << 16; |
|||
secsSince1900 |= (unsigned long)packetBuffer[42] << 8; |
|||
secsSince1900 |= (unsigned long)packetBuffer[43]; |
|||
return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; |
|||
} |
|||
} |
|||
Serial.println("No NTP Response :-("); |
|||
return 0; // return 0 if unable to get the time
|
|||
} |
|||
|
|||
// send an NTP request to the time server at the given address
|
|||
void sendNTPpacket(IPAddress &address) |
|||
{ |
|||
// set all bytes in the buffer to 0
|
|||
memset(packetBuffer, 0, NTP_PACKET_SIZE); |
|||
// Initialize values needed to form NTP request
|
|||
// (see URL above for details on the packets)
|
|||
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
|
|||
// 8 bytes of zero for Root Delay & Root Dispersion
|
|||
packetBuffer[12] = 49; |
|||
packetBuffer[13] = 0x4E; |
|||
packetBuffer[14] = 49; |
|||
packetBuffer[15] = 52; |
|||
// all NTP fields have been given values, now
|
|||
// you can send a packet requesting a timestamp:
|
|||
Udp.beginPacket(address, 123); //NTP requests are to port 123
|
|||
Udp.write(packetBuffer, NTP_PACKET_SIZE); |
|||
Udp.endPacket(); |
|||
} |
@ -0,0 +1,55 @@ |
|||
/*
|
|||
* TimeRTC.pde |
|||
* example code illustrating Time library with Real Time Clock. |
|||
* |
|||
*/ |
|||
|
|||
#include <TimeLib.h>
|
|||
#include <Wire.h>
|
|||
#include <DS1307RTC.h> // a basic DS1307 library that returns time as a time_t
|
|||
|
|||
void setup() { |
|||
Serial.begin(9600); |
|||
while (!Serial) ; // wait until Arduino Serial Monitor opens
|
|||
setSyncProvider(RTC.get); // the function to get the time from the RTC
|
|||
if(timeStatus()!= timeSet) |
|||
Serial.println("Unable to sync with the RTC"); |
|||
else |
|||
Serial.println("RTC has set the system time"); |
|||
} |
|||
|
|||
void loop() |
|||
{ |
|||
if (timeStatus() == timeSet) { |
|||
digitalClockDisplay(); |
|||
} else { |
|||
Serial.println("The time has not been set. Please run the Time"); |
|||
Serial.println("TimeRTCSet example, or DS1307RTC SetTime example."); |
|||
Serial.println(); |
|||
delay(4000); |
|||
} |
|||
delay(1000); |
|||
} |
|||
|
|||
void digitalClockDisplay(){ |
|||
// digital clock display of the time
|
|||
Serial.print(hour()); |
|||
printDigits(minute()); |
|||
printDigits(second()); |
|||
Serial.print(" "); |
|||
Serial.print(day()); |
|||
Serial.print(" "); |
|||
Serial.print(month()); |
|||
Serial.print(" "); |
|||
Serial.print(year()); |
|||
Serial.println(); |
|||
} |
|||
|
|||
void printDigits(int digits){ |
|||
// utility function for digital clock display: prints preceding colon and leading 0
|
|||
Serial.print(":"); |
|||
if(digits < 10) |
|||
Serial.print('0'); |
|||
Serial.print(digits); |
|||
} |
|||
|
@ -0,0 +1,107 @@ |
|||
/*
|
|||
* TimeRTCLogger.pde |
|||
* example code illustrating adding and subtracting Time. |
|||
* |
|||
* this sketch logs pin state change events |
|||
* the time of the event and time since the previous event is calculated and sent to the serial port. |
|||
*/ |
|||
|
|||
#include <TimeLib.h>
|
|||
#include <Wire.h>
|
|||
#include <DS1307RTC.h> // a basic DS1307 library that returns time as a time_t
|
|||
|
|||
const int nbrInputPins = 6; // monitor 6 digital pins
|
|||
const int inputPins[nbrInputPins] = {2,3,4,5,6,7}; // pins to monitor
|
|||
boolean state[nbrInputPins] ; // the state of the monitored pins
|
|||
time_t prevEventTime[nbrInputPins] ; // the time of the previous event
|
|||
|
|||
void setup() { |
|||
Serial.begin(9600); |
|||
setSyncProvider(RTC.get); // the function to sync the time from the RTC
|
|||
for(int i=0; i < nbrInputPins; i++){ |
|||
pinMode( inputPins[i], INPUT); |
|||
// uncomment these lines if pull-up resistors are wanted
|
|||
// pinMode( inputPins[i], INPUT_PULLUP);
|
|||
// state[i] = HIGH;
|
|||
} |
|||
} |
|||
|
|||
void loop() |
|||
{ |
|||
for(int i=0; i < nbrInputPins; i++) |
|||
{ |
|||
boolean val = digitalRead(inputPins[i]); |
|||
if(val != state[i]) |
|||
{ |
|||
time_t duration = 0; // the time since the previous event
|
|||
state[i] = val; |
|||
time_t timeNow = now(); |
|||
if(prevEventTime[i] > 0) |
|||
// if this was not the first state change, calculate the time from the previous change
|
|||
duration = duration = timeNow - prevEventTime[i]; |
|||
logEvent(inputPins[i], val, timeNow, duration ); // log the event
|
|||
prevEventTime[i] = timeNow; // store the time for this event
|
|||
} |
|||
} |
|||
} |
|||
|
|||
void logEvent( int pin, boolean state, time_t timeNow, time_t duration) |
|||
{ |
|||
Serial.print("Pin "); |
|||
Serial.print(pin); |
|||
if( state == HIGH) |
|||
Serial.print(" went High at "); |
|||
else |
|||
Serial.print(" went Low at "); |
|||
showTime(timeNow); |
|||
if(duration > 0){ |
|||
// only display duration if greater than 0
|
|||
Serial.print(", Duration was "); |
|||
showDuration(duration); |
|||
} |
|||
Serial.println(); |
|||
} |
|||
|
|||
|
|||
void showTime(time_t t){ |
|||
// display the given time
|
|||
Serial.print(hour(t)); |
|||
printDigits(minute(t)); |
|||
printDigits(second(t)); |
|||
Serial.print(" "); |
|||
Serial.print(day(t)); |
|||
Serial.print(" "); |
|||
Serial.print(month(t)); |
|||
Serial.print(" "); |
|||
Serial.print(year(t)); |
|||
} |
|||
|
|||
void printDigits(int digits){ |
|||
// utility function for digital clock display: prints preceding colon and leading 0
|
|||
Serial.print(":"); |
|||
if(digits < 10) |
|||
Serial.print('0'); |
|||
Serial.print(digits); |
|||
} |
|||
|
|||
void showDuration(time_t duration){ |
|||
// prints the duration in days, hours, minutes and seconds
|
|||
if(duration >= SECS_PER_DAY){ |
|||
Serial.print(duration / SECS_PER_DAY); |
|||
Serial.print(" day(s) "); |
|||
duration = duration % SECS_PER_DAY; |
|||
} |
|||
if(duration >= SECS_PER_HOUR){ |
|||
Serial.print(duration / SECS_PER_HOUR); |
|||
Serial.print(" hour(s) "); |
|||
duration = duration % SECS_PER_HOUR; |
|||
} |
|||
if(duration >= SECS_PER_MIN){ |
|||
Serial.print(duration / SECS_PER_MIN); |
|||
Serial.print(" minute(s) "); |
|||
duration = duration % SECS_PER_MIN; |
|||
} |
|||
Serial.print(duration); |
|||
Serial.print(" second(s) "); |
|||
} |
|||
|
@ -0,0 +1,80 @@ |
|||
/*
|
|||
* TimeRTCSet.pde |
|||
* example code illustrating Time library with Real Time Clock. |
|||
* |
|||
* RTC clock is set in response to serial port time message |
|||
* A Processing example sketch to set the time is included in the download |
|||
* On Linux, you can use "date +T%s > /dev/ttyACM0" (UTC time zone) |
|||
*/ |
|||
|
|||
#include <TimeLib.h>
|
|||
#include <Wire.h>
|
|||
#include <DS1307RTC.h> // a basic DS1307 library that returns time as a time_t
|
|||
|
|||
|
|||
void setup() { |
|||
Serial.begin(9600); |
|||
while (!Serial) ; // Needed for Leonardo only
|
|||
setSyncProvider(RTC.get); // the function to get the time from the RTC
|
|||
if (timeStatus() != timeSet) |
|||
Serial.println("Unable to sync with the RTC"); |
|||
else |
|||
Serial.println("RTC has set the system time"); |
|||
} |
|||
|
|||
void loop() |
|||
{ |
|||
if (Serial.available()) { |
|||
time_t t = processSyncMessage(); |
|||
if (t != 0) { |
|||
RTC.set(t); // set the RTC and the system time to the received value
|
|||
setTime(t); |
|||
} |
|||
} |
|||
digitalClockDisplay(); |
|||
delay(1000); |
|||
} |
|||
|
|||
void digitalClockDisplay(){ |
|||
// digital clock display of the time
|
|||
Serial.print(hour()); |
|||
printDigits(minute()); |
|||
printDigits(second()); |
|||
Serial.print(" "); |
|||
Serial.print(day()); |
|||
Serial.print(" "); |
|||
Serial.print(month()); |
|||
Serial.print(" "); |
|||
Serial.print(year()); |
|||
Serial.println(); |
|||
} |
|||
|
|||
void printDigits(int digits){ |
|||
// utility function for digital clock display: prints preceding colon and leading 0
|
|||
Serial.print(":"); |
|||
if(digits < 10) |
|||
Serial.print('0'); |
|||
Serial.print(digits); |
|||
} |
|||
|
|||
/* code to process time sync messages from the serial port */ |
|||
#define TIME_HEADER "T" // Header tag for serial time sync message
|
|||
|
|||
unsigned long processSyncMessage() { |
|||
unsigned long pctime = 0L; |
|||
const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013
|
|||
|
|||
if(Serial.find(TIME_HEADER)) { |
|||
pctime = Serial.parseInt(); |
|||
return pctime; |
|||
if( pctime < DEFAULT_TIME) { // check the value is a valid time (greater than Jan 1 2013)
|
|||
pctime = 0L; // return 0 to indicate that the time is not valid
|
|||
} |
|||
} |
|||
return pctime; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
@ -0,0 +1,81 @@ |
|||
/*
|
|||
* TimeSerial.pde |
|||
* example code illustrating Time library set through serial port messages. |
|||
* |
|||
* Messages consist of the letter T followed by ten digit time (as seconds since Jan 1 1970) |
|||
* you can send the text on the next line using Serial Monitor to set the clock to noon Jan 1 2013 |
|||
T1357041600 |
|||
* |
|||
* A Processing example sketch to automatically send the messages is included in the download |
|||
* On Linux, you can use "date +T%s\n > /dev/ttyACM0" (UTC time zone) |
|||
*/ |
|||
|
|||
#include <TimeLib.h>
|
|||
|
|||
#define TIME_HEADER "T" // Header tag for serial time sync message
|
|||
#define TIME_REQUEST 7 // ASCII bell character requests a time sync message
|
|||
|
|||
void setup() { |
|||
Serial.begin(9600); |
|||
while (!Serial) ; // Needed for Leonardo only
|
|||
pinMode(13, OUTPUT); |
|||
setSyncProvider( requestSync); //set function to call when sync required
|
|||
Serial.println("Waiting for sync message"); |
|||
} |
|||
|
|||
void loop(){ |
|||
if (Serial.available()) { |
|||
processSyncMessage(); |
|||
} |
|||
if (timeStatus()!= timeNotSet) { |
|||
digitalClockDisplay(); |
|||
} |
|||
if (timeStatus() == timeSet) { |
|||
digitalWrite(13, HIGH); // LED on if synced
|
|||
} else { |
|||
digitalWrite(13, LOW); // LED off if needs refresh
|
|||
} |
|||
delay(1000); |
|||
} |
|||
|
|||
void digitalClockDisplay(){ |
|||
// digital clock display of the time
|
|||
Serial.print(hour()); |
|||
printDigits(minute()); |
|||
printDigits(second()); |
|||
Serial.print(" "); |
|||
Serial.print(day()); |
|||
Serial.print(" "); |
|||
Serial.print(month()); |
|||
Serial.print(" "); |
|||
Serial.print(year()); |
|||
Serial.println(); |
|||
} |
|||
|
|||
void printDigits(int digits){ |
|||
// utility function for digital clock display: prints preceding colon and leading 0
|
|||
Serial.print(":"); |
|||
if(digits < 10) |
|||
Serial.print('0'); |
|||
Serial.print(digits); |
|||
} |
|||
|
|||
|
|||
void processSyncMessage() { |
|||
unsigned long pctime; |
|||
const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013
|
|||
|
|||
if(Serial.find(TIME_HEADER)) { |
|||
pctime = Serial.parseInt(); |
|||
if( pctime >= DEFAULT_TIME) { // check the integer is a valid time (greater than Jan 1 2013)
|
|||
setTime(pctime); // Sync Arduino clock to the time received on the serial port
|
|||
} |
|||
} |
|||
} |
|||
|
|||
time_t requestSync() |
|||
{ |
|||
Serial.write(TIME_REQUEST); |
|||
return 0; // the time will be sent later in response to serial mesg
|
|||
} |
|||
|
@ -0,0 +1,108 @@ |
|||
/*
|
|||
* TimeSerialDateStrings.pde |
|||
* example code illustrating Time library date strings |
|||
* |
|||
* This sketch adds date string functionality to TimeSerial sketch |
|||
* Also shows how to handle different messages |
|||
* |
|||
* A message starting with a time header sets the time |
|||
* A Processing example sketch to automatically send the messages is inclided in the download |
|||
* On Linux, you can use "date +T%s\n > /dev/ttyACM0" (UTC time zone) |
|||
* |
|||
* A message starting with a format header sets the date format |
|||
|
|||
* send: Fs\n for short date format |
|||
* send: Fl\n for long date format |
|||
*/ |
|||
|
|||
#include <TimeLib.h>
|
|||
|
|||
// single character message tags
|
|||
#define TIME_HEADER 'T' // Header tag for serial time sync message
|
|||
#define FORMAT_HEADER 'F' // Header tag indicating a date format message
|
|||
#define FORMAT_SHORT 's' // short month and day strings
|
|||
#define FORMAT_LONG 'l' // (lower case l) long month and day strings
|
|||
|
|||
#define TIME_REQUEST 7 // ASCII bell character requests a time sync message
|
|||
|
|||
static boolean isLongFormat = true; |
|||
|
|||
void setup() { |
|||
Serial.begin(9600); |
|||
while (!Serial) ; // Needed for Leonardo only
|
|||
setSyncProvider( requestSync); //set function to call when sync required
|
|||
Serial.println("Waiting for sync message"); |
|||
} |
|||
|
|||
void loop(){ |
|||
if (Serial.available() > 1) { // wait for at least two characters
|
|||
char c = Serial.read(); |
|||
if( c == TIME_HEADER) { |
|||
processSyncMessage(); |
|||
} |
|||
else if( c== FORMAT_HEADER) { |
|||
processFormatMessage(); |
|||
} |
|||
} |
|||
if (timeStatus()!= timeNotSet) { |
|||
digitalClockDisplay(); |
|||
} |
|||
delay(1000); |
|||
} |
|||
|
|||
void digitalClockDisplay() { |
|||
// digital clock display of the time
|
|||
Serial.print(hour()); |
|||
printDigits(minute()); |
|||
printDigits(second()); |
|||
Serial.print(" "); |
|||
if(isLongFormat) |
|||
Serial.print(dayStr(weekday())); |
|||
else |
|||
Serial.print(dayShortStr(weekday())); |
|||
Serial.print(" "); |
|||
Serial.print(day()); |
|||
Serial.print(" "); |
|||
if(isLongFormat) |
|||
Serial.print(monthStr(month())); |
|||
else |
|||
Serial.print(monthShortStr(month())); |
|||
Serial.print(" "); |
|||
Serial.print(year()); |
|||
Serial.println(); |
|||
} |
|||
|
|||
void printDigits(int digits) { |
|||
// utility function for digital clock display: prints preceding colon and leading 0
|
|||
Serial.print(":"); |
|||
if(digits < 10) |
|||
Serial.print('0'); |
|||
Serial.print(digits); |
|||
} |
|||
|
|||
void processFormatMessage() { |
|||
char c = Serial.read(); |
|||
if( c == FORMAT_LONG){ |
|||
isLongFormat = true; |
|||
Serial.println(F("Setting long format")); |
|||
} |
|||
else if( c == FORMAT_SHORT) { |
|||
isLongFormat = false; |
|||
Serial.println(F("Setting short format")); |
|||
} |
|||
} |
|||
|
|||
void processSyncMessage() { |
|||
unsigned long pctime; |
|||
const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 - paul, perhaps we define in time.h?
|
|||
|
|||
pctime = Serial.parseInt(); |
|||
if( pctime >= DEFAULT_TIME) { // check the integer is a valid time (greater than Jan 1 2013)
|
|||
setTime(pctime); // Sync Arduino clock to the time received on the serial port
|
|||
} |
|||
} |
|||
|
|||
time_t requestSync() { |
|||
Serial.write(TIME_REQUEST); |
|||
return 0; // the time will be sent later in response to serial mesg
|
|||
} |
@ -0,0 +1,78 @@ |
|||
/*
|
|||
* TimeRTC.pde |
|||
* example code illustrating Time library with Real Time Clock. |
|||
* |
|||
*/ |
|||
|
|||
#include <TimeLib.h>
|
|||
|
|||
void setup() { |
|||
// set the Time library to use Teensy 3.0's RTC to keep time
|
|||
setSyncProvider(getTeensy3Time); |
|||
|
|||
Serial.begin(115200); |
|||
while (!Serial); // Wait for Arduino Serial Monitor to open
|
|||
delay(100); |
|||
if (timeStatus()!= timeSet) { |
|||
Serial.println("Unable to sync with the RTC"); |
|||
} else { |
|||
Serial.println("RTC has set the system time"); |
|||
} |
|||
} |
|||
|
|||
void loop() { |
|||
if (Serial.available()) { |
|||
time_t t = processSyncMessage(); |
|||
if (t != 0) { |
|||
Teensy3Clock.set(t); // set the RTC
|
|||
setTime(t); |
|||
} |
|||
} |
|||
digitalClockDisplay(); |
|||
delay(1000); |
|||
} |
|||
|
|||
void digitalClockDisplay() { |
|||
// digital clock display of the time
|
|||
Serial.print(hour()); |
|||
printDigits(minute()); |
|||
printDigits(second()); |
|||
Serial.print(" "); |
|||
Serial.print(day()); |
|||
Serial.print(" "); |
|||
Serial.print(month()); |
|||
Serial.print(" "); |
|||
Serial.print(year()); |
|||
Serial.println(); |
|||
} |
|||
|
|||
time_t getTeensy3Time() |
|||
{ |
|||
return Teensy3Clock.get(); |
|||
} |
|||
|
|||
/* code to process time sync messages from the serial port */ |
|||
#define TIME_HEADER "T" // Header tag for serial time sync message
|
|||
|
|||
unsigned long processSyncMessage() { |
|||
unsigned long pctime = 0L; |
|||
const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013
|
|||
|
|||
if(Serial.find(TIME_HEADER)) { |
|||
pctime = Serial.parseInt(); |
|||
return pctime; |
|||
if( pctime < DEFAULT_TIME) { // check the value is a valid time (greater than Jan 1 2013)
|
|||
pctime = 0L; // return 0 to indicate that the time is not valid
|
|||
} |
|||
} |
|||
return pctime; |
|||
} |
|||
|
|||
void printDigits(int digits){ |
|||
// utility function for digital clock display: prints preceding colon and leading 0
|
|||
Serial.print(":"); |
|||
if(digits < 10) |
|||
Serial.print('0'); |
|||
Serial.print(digits); |
|||
} |
|||
|
@ -0,0 +1,34 @@ |
|||
####################################### |
|||
# Syntax Coloring Map For Time |
|||
####################################### |
|||
|
|||
####################################### |
|||
# Datatypes (KEYWORD1) |
|||
####################################### |
|||
time_t KEYWORD1 |
|||
####################################### |
|||
# Methods and Functions (KEYWORD2) |
|||
####################################### |
|||
now KEYWORD2 |
|||
second KEYWORD2 |
|||
minute KEYWORD2 |
|||
hour KEYWORD2 |
|||
day KEYWORD2 |
|||
month KEYWORD2 |
|||
year KEYWORD2 |
|||
isAM KEYWORD2 |
|||
isPM KEYWORD2 |
|||
weekday KEYWORD2 |
|||
setTime KEYWORD2 |
|||
adjustTime KEYWORD2 |
|||
setSyncProvider KEYWORD2 |
|||
setSyncInterval KEYWORD2 |
|||
timeStatus KEYWORD2 |
|||
TimeLib KEYWORD2 |
|||
####################################### |
|||
# Instances (KEYWORD2) |
|||
####################################### |
|||
|
|||
####################################### |
|||
# Constants (LITERAL1) |
|||
####################################### |
@ -0,0 +1,22 @@ |
|||
{ |
|||
"name": "Time", |
|||
"frameworks": "Arduino", |
|||
"keywords": "Time, date, hour, minute, second, day, week, month, year, RTC", |
|||
"description": "Time keeping library", |
|||
"url": "http://playground.arduino.cc/Code/Time", |
|||
"authors": |
|||
[ |
|||
{ |
|||
"name": "Michael Margolis" |
|||
}, |
|||
{ |
|||
"name": "Paul Stoffregen" |
|||
} |
|||
], |
|||
"repository": |
|||
{ |
|||
"type": "git", |
|||
"url": "https://github.com/PaulStoffregen/Time" |
|||
} |
|||
} |
|||
|
@ -0,0 +1,10 @@ |
|||
name=Time |
|||
version=1.5 |
|||
author=Michael Margolis |
|||
maintainer=Paul Stoffregen |
|||
sentence=Timekeeping functionality for Arduino |
|||
paragraph=Date and Time functions, with provisions to synchronize to external time sources like GPS and NTP (Internet). This library is often used together with TimeAlarms and DS1307RTC. |
|||
category=Timing |
|||
url=http://playground.arduino.cc/code/time |
|||
architectures=* |
|||
|
@ -0,0 +1,103 @@ |
|||
// rootca1.cer
|
|||
const unsigned char caCert[] PROGMEM = { |
|||
|
|||
0x30, 0x82, 0x04, 0x92, 0x30, 0x82, 0x03, 0x7a, 0xa0, 0x03, 0x02, 0x01, |
|||
0x02, 0x02, 0x13, 0x06, 0x7f, 0x94, 0x4a, 0x2a, 0x27, 0xcd, 0xf3, 0xfa, |
|||
0xc2, 0xae, 0x2b, 0x01, 0xf9, 0x08, 0xee, 0xb9, 0xc4, 0xc6, 0x30, 0x0d, |
|||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, |
|||
0x00, 0x30, 0x81, 0x98, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, |
|||
0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, |
|||
0x04, 0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, |
|||
0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, |
|||
0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x25, 0x30, 0x23, |
|||
0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, |
|||
0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, |
|||
0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, |
|||
0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x32, 0x53, 0x74, |
|||
0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x65, 0x72, 0x76, |
|||
0x69, 0x63, 0x65, 0x73, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, |
|||
0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, |
|||
0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, |
|||
0x30, 0x1e, 0x17, 0x0d, 0x31, 0x35, 0x30, 0x35, 0x32, 0x35, 0x31, 0x32, |
|||
0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x33, 0x37, 0x31, 0x32, 0x33, |
|||
0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x39, 0x31, 0x0b, |
|||
0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, |
|||
0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x06, 0x41, 0x6d, |
|||
0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, |
|||
0x03, 0x13, 0x10, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x20, 0x52, 0x6f, |
|||
0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x31, 0x30, 0x82, 0x01, 0x22, 0x30, |
|||
0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, |
|||
0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, |
|||
0x82, 0x01, 0x01, 0x00, 0xb2, 0x78, 0x80, 0x71, 0xca, 0x78, 0xd5, 0xe3, |
|||
0x71, 0xaf, 0x47, 0x80, 0x50, 0x74, 0x7d, 0x6e, 0xd8, 0xd7, 0x88, 0x76, |
|||
0xf4, 0x99, 0x68, 0xf7, 0x58, 0x21, 0x60, 0xf9, 0x74, 0x84, 0x01, 0x2f, |
|||
0xac, 0x02, 0x2d, 0x86, 0xd3, 0xa0, 0x43, 0x7a, 0x4e, 0xb2, 0xa4, 0xd0, |
|||
0x36, 0xba, 0x01, 0xbe, 0x8d, 0xdb, 0x48, 0xc8, 0x07, 0x17, 0x36, 0x4c, |
|||
0xf4, 0xee, 0x88, 0x23, 0xc7, 0x3e, 0xeb, 0x37, 0xf5, 0xb5, 0x19, 0xf8, |
|||
0x49, 0x68, 0xb0, 0xde, 0xd7, 0xb9, 0x76, 0x38, 0x1d, 0x61, 0x9e, 0xa4, |
|||
0xfe, 0x82, 0x36, 0xa5, 0xe5, 0x4a, 0x56, 0xe4, 0x45, 0xe1, 0xf9, 0xfd, |
|||
0xb4, 0x16, 0xfa, 0x74, 0xda, 0x9c, 0x9b, 0x35, 0x39, 0x2f, 0xfa, 0xb0, |
|||
0x20, 0x50, 0x06, 0x6c, 0x7a, 0xd0, 0x80, 0xb2, 0xa6, 0xf9, 0xaf, 0xec, |
|||
0x47, 0x19, 0x8f, 0x50, 0x38, 0x07, 0xdc, 0xa2, 0x87, 0x39, 0x58, 0xf8, |
|||
0xba, 0xd5, 0xa9, 0xf9, 0x48, 0x67, 0x30, 0x96, 0xee, 0x94, 0x78, 0x5e, |
|||
0x6f, 0x89, 0xa3, 0x51, 0xc0, 0x30, 0x86, 0x66, 0xa1, 0x45, 0x66, 0xba, |
|||
0x54, 0xeb, 0xa3, 0xc3, 0x91, 0xf9, 0x48, 0xdc, 0xff, 0xd1, 0xe8, 0x30, |
|||
0x2d, 0x7d, 0x2d, 0x74, 0x70, 0x35, 0xd7, 0x88, 0x24, 0xf7, 0x9e, 0xc4, |
|||
0x59, 0x6e, 0xbb, 0x73, 0x87, 0x17, 0xf2, 0x32, 0x46, 0x28, 0xb8, 0x43, |
|||
0xfa, 0xb7, 0x1d, 0xaa, 0xca, 0xb4, 0xf2, 0x9f, 0x24, 0x0e, 0x2d, 0x4b, |
|||
0xf7, 0x71, 0x5c, 0x5e, 0x69, 0xff, 0xea, 0x95, 0x02, 0xcb, 0x38, 0x8a, |
|||
0xae, 0x50, 0x38, 0x6f, 0xdb, 0xfb, 0x2d, 0x62, 0x1b, 0xc5, 0xc7, 0x1e, |
|||
0x54, 0xe1, 0x77, 0xe0, 0x67, 0xc8, 0x0f, 0x9c, 0x87, 0x23, 0xd6, 0x3f, |
|||
0x40, 0x20, 0x7f, 0x20, 0x80, 0xc4, 0x80, 0x4c, 0x3e, 0x3b, 0x24, 0x26, |
|||
0x8e, 0x04, 0xae, 0x6c, 0x9a, 0xc8, 0xaa, 0x0d, 0x02, 0x03, 0x01, 0x00, |
|||
0x01, 0xa3, 0x82, 0x01, 0x31, 0x30, 0x82, 0x01, 0x2d, 0x30, 0x0f, 0x06, |
|||
0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, |
|||
0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, |
|||
0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, |
|||
0x0e, 0x04, 0x16, 0x04, 0x14, 0x84, 0x18, 0xcc, 0x85, 0x34, 0xec, 0xbc, |
|||
0x0c, 0x94, 0x94, 0x2e, 0x08, 0x59, 0x9c, 0xc7, 0xb2, 0x10, 0x4e, 0x0a, |
|||
0x08, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, |
|||
0x80, 0x14, 0x9c, 0x5f, 0x00, 0xdf, 0xaa, 0x01, 0xd7, 0x30, 0x2b, 0x38, |
|||
0x88, 0xa2, 0xb8, 0x6d, 0x4a, 0x9c, 0xf2, 0x11, 0x91, 0x83, 0x30, 0x78, |
|||
0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x6c, |
|||
0x30, 0x6a, 0x30, 0x2e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, |
|||
0x30, 0x01, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, |
|||
0x63, 0x73, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, 0x2e, 0x61, |
|||
0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, |
|||
0x6f, 0x6d, 0x30, 0x38, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, |
|||
0x30, 0x02, 0x86, 0x2c, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, |
|||
0x72, 0x74, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, 0x2e, 0x61, 0x6d, |
|||
0x61, 0x7a, 0x6f, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, |
|||
0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, 0x2e, 0x63, 0x65, 0x72, |
|||
0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x36, 0x30, 0x34, 0x30, |
|||
0x32, 0xa0, 0x30, 0xa0, 0x2e, 0x86, 0x2c, 0x68, 0x74, 0x74, 0x70, 0x3a, |
|||
0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, |
|||
0x2e, 0x61, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, |
|||
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, 0x2e, |
|||
0x63, 0x72, 0x6c, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, |
|||
0x30, 0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x0d, |
|||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, |
|||
0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x62, 0x37, 0x42, 0x5c, 0xbc, 0x10, |
|||
0xb5, 0x3e, 0x8b, 0x2c, 0xe9, 0x0c, 0x9b, 0x6c, 0x45, 0xe2, 0x07, 0x00, |
|||
0x7a, 0xf9, 0xc5, 0x58, 0x0b, 0xb9, 0x08, 0x8c, 0x3e, 0xed, 0xb3, 0x25, |
|||
0x3c, 0xb5, 0x6f, 0x50, 0xe4, 0xcd, 0x35, 0x6a, 0xa7, 0x93, 0x34, 0x96, |
|||
0x32, 0x21, 0xa9, 0x48, 0x44, 0xab, 0x9c, 0xed, 0x3d, 0xb4, 0xaa, 0x73, |
|||
0x6d, 0xe4, 0x7f, 0x16, 0x80, 0x89, 0x6c, 0xcf, 0x28, 0x03, 0x18, 0x83, |
|||
0x47, 0x79, 0xa3, 0x10, 0x7e, 0x30, 0x5b, 0xac, 0x3b, 0xb0, 0x60, 0xe0, |
|||
0x77, 0xd4, 0x08, 0xa6, 0xe1, 0x1d, 0x7c, 0x5e, 0xc0, 0xbb, 0xf9, 0x9a, |
|||
0x7b, 0x22, 0x9d, 0xa7, 0x00, 0x09, 0x7e, 0xac, 0x46, 0x17, 0x83, 0xdc, |
|||
0x9c, 0x26, 0x57, 0x99, 0x30, 0x39, 0x62, 0x96, 0x8f, 0xed, 0xda, 0xde, |
|||
0xaa, 0xc5, 0xcc, 0x1b, 0x3e, 0xca, 0x43, 0x68, 0x6c, 0x57, 0x16, 0xbc, |
|||
0xd5, 0x0e, 0x20, 0x2e, 0xfe, 0xff, 0xc2, 0x6a, 0x5d, 0x2e, 0xa0, 0x4a, |
|||
0x6d, 0x14, 0x58, 0x87, 0x94, 0xe6, 0x39, 0x31, 0x5f, 0x7c, 0x73, 0xcb, |
|||
0x90, 0x88, 0x6a, 0x84, 0x11, 0x96, 0x27, 0xa6, 0xed, 0xd9, 0x81, 0x46, |
|||
0xa6, 0x7e, 0xa3, 0x72, 0x00, 0x0a, 0x52, 0x3e, 0x83, 0x88, 0x07, 0x63, |
|||
0x77, 0x89, 0x69, 0x17, 0x0f, 0x39, 0x85, 0xd2, 0xab, 0x08, 0x45, 0x4d, |
|||
0xd0, 0x51, 0x3a, 0xfd, 0x5d, 0x5d, 0x37, 0x64, 0x4c, 0x7e, 0x30, 0xb2, |
|||
0x55, 0x24, 0x42, 0x9d, 0x36, 0xb0, 0x5d, 0x9c, 0x17, 0x81, 0x61, 0xf1, |
|||
0xca, 0xf9, 0x10, 0x02, 0x24, 0xab, 0xeb, 0x0d, 0x74, 0x91, 0x8d, 0x7b, |
|||
0x45, 0x29, 0x50, 0x39, 0x88, 0xb2, 0xa6, 0x89, 0x35, 0x25, 0x1e, 0x14, |
|||
0x6a, 0x47, 0x23, 0x31, 0x2f, 0x5c, 0x9a, 0xfa, 0xad, 0x9a, 0x0e, 0x62, |
|||
0x51, 0xa4, 0x2a, 0xa9, 0xc4, 0xf9, 0x34, 0x9d, 0x21, 0x18}; |
|||
|
|||
const unsigned int caCertLen = 1174; |
@ -0,0 +1,898 @@ |
|||
#include <ESP8266WiFi.h>
|
|||
#include <ESP8266mDNS.h>
|
|||
#include <ArduinoOTA.h>
|
|||
#include <ESP8266WebServer.h>
|
|||
#include <SimpleTimer.h>
|
|||
#include <TimeLib.h> //https://github.com/PaulStoffregen/Time
|
|||
#include <ntp_time.h>
|
|||
#include <circular_log.h>
|
|||
#include <WiFiClientSecure.h>
|
|||
#include <PubSubClient.h>
|
|||
|
|||
extern const unsigned char caCert[] PROGMEM; |
|||
extern const unsigned int caCertLen; |
|||
|
|||
//IMPORTANT: Specify your WIFI settings:
|
|||
//#define WIFI_SSID "NOS-3B26" // setubal
|
|||
//#define WIFI_PASS "RMKSX2GL" // Setubal
|
|||
#define WIFI_SSID "MEO-AA9030"// Andre
|
|||
#define WIFI_PASS "81070ce635" // andre
|
|||
|
|||
//IMPORTANT: Uncomment this line if you want to enable MQTT (and fill correct MQTT_ values below):
|
|||
//#define ENABLE_MQTT
|
|||
|
|||
#ifdef ENABLE_MQTT
|
|||
//NOTE 1: if you want to change what is pushed via MQTT - edit function: pushBatteryDataToMqtt.
|
|||
//NOTE 2: MQTT_TOPIC_ROOT is where battery will push MQTT topics. For example "soc" will be pushed to: "home/grid_battery/soc"
|
|||
//#define MQTT_SERVER "cc42fcb4f1eb492fa3c2e07a9e617830.s2.eu.hivemq.cloud" //"192.168.1.123"
|
|||
#define MQTT_SERVER "192.168.1.123"
|
|||
#define MQTT_PORT 1883 // 8883
|
|||
#define MQTT_USER "tezmqtt"
|
|||
#define MQTT_PASSWORD "mqtTez_123"
|
|||
#define MQTT_TOPIC_ROOT "home/grid_battery/" //this is where mqtt data will be pushed
|
|||
#define MQTT_PUSH_FREQ_SEC 2 //maximum mqtt update frequency in seconds
|
|||
#endif ENABLE_MQTT
|
|||
|
|||
|
|||
WiFiClient espClient; |
|||
//WiFiClientSecure espClient;
|
|||
PubSubClient mqttClient(espClient); |
|||
IPAddress thisip; |
|||
ESP8266WebServer server(80); |
|||
|
|||
SimpleTimer timer; |
|||
|
|||
char g_szRecvBuff[7000]; |
|||
circular_log<7000> g_log; |
|||
bool ntpTimeReceived = false; |
|||
int g_baudRate = 0; |
|||
|
|||
void Log(const char* msg) |
|||
{ |
|||
g_log.Log(msg); |
|||
} |
|||
|
|||
int LEDPIN = 2; // The on-board Wemos D1 mini LED
|
|||
|
|||
//////////////////////////////////////
|
|||
void setup() { |
|||
Serial.begin(115200); |
|||
Serial.println("---"); |
|||
Serial.println("Serial started"); |
|||
|
|||
pinMode(LED_BUILTIN, OUTPUT); |
|||
|
|||
digitalWrite(LED_BUILTIN, LOW); // LOW = on
|
|||
delay(2000); |
|||
digitalWrite(LED_BUILTIN, HIGH); // HIGH = off
|
|||
|
|||
|
|||
|
|||
// connect to WiFi
|
|||
WiFi.mode(WIFI_STA); |
|||
WiFi.persistent(false); //our credentials are hardcoded, so we don't need ESP saving those each boot (will save on flash wear)
|
|||
WiFi.hostname("PylonBattery"); |
|||
Serial.println(); |
|||
Serial.print("connecting to "); |
|||
Serial.println(WIFI_SSID); |
|||
|
|||
WiFi.begin(WIFI_SSID, WIFI_PASS); |
|||
while (WiFi.status() != WL_CONNECTED) { |
|||
delay(500); |
|||
Serial.print("."); |
|||
} |
|||
Serial.println(""); |
|||
Serial.println("WiFi connected"); |
|||
Serial.println("IP address: "); |
|||
Serial.println(WiFi.localIP()); |
|||
Serial.println(""); |
|||
for(int i=1; i<=3; i++){ |
|||
LedBlink(); |
|||
} |
|||
|
|||
|
|||
ArduinoOTA.setHostname("AndrePylon"); |
|||
ArduinoOTA.begin(); |
|||
server.on("/", handleRoot); |
|||
server.on("/log", handleLog); |
|||
server.on("/req", handleReq); |
|||
server.on("/jsonOut", handleJsonOut); |
|||
server.on("/reboot", [](){ |
|||
ESP.restart(); |
|||
}); |
|||
|
|||
server.begin(); |
|||
Serial.println("web server started"); |
|||
Serial.println(""); |
|||
|
|||
delay(1000); |
|||
|
|||
syncTime(); |
|||
Serial.println("SNTP time synced"); |
|||
Serial.println(""); |
|||
|
|||
for(int i=1; i<=3; i++){ |
|||
LedBlink(); |
|||
} |
|||
|
|||
/* COMMENTED BY TEZ */ |
|||
/*
|
|||
// https://links2004.github.io/Arduino/d2/d2f/class_wi_fi_client_secure.html
|
|||
// Load root certificate in DER format into WiFiClientSecure object
|
|||
// bool res = espClient.setCACert_P(caCert, caCertLen);
|
|||
bool res = espClient.setCertificate(caCert, caCertLen); |
|||
if (!res) { |
|||
Serial.println("Failed to load root CA certificate!"); |
|||
while (true) { |
|||
yield(); |
|||
} |
|||
} |
|||
Serial.println("local load root CA certificate OK!"); |
|||
|
|||
|
|||
// VERIFY SSL FINGERPRINT
|
|||
if (!espClient.verify( "49 7E 82 A3 DB B4 19 1E 73 E5 19 A6 D3 C6 C5 31 DE D9 DE 97 BA B1 D8 19 80 A3 88 96 70 8D 94 3D", "websocketclient.hivemq.cloud") ) { |
|||
Serial.println( "Fingerprint certificate NOT verified sorry!" ); |
|||
// mqttClient.disconnect();
|
|||
// return false;
|
|||
}else{ |
|||
Serial.println( "Fingerprint verified OK!"); |
|||
} |
|||
|
|||
*/ |
|||
// END COMMENTED BY TEZ
|
|||
|
|||
#ifdef ENABLE_MQTT
|
|||
mqttClient.setServer(MQTT_SERVER, MQTT_PORT); |
|||
#endif
|
|||
|
|||
Log("Boot event"); |
|||
} |
|||
|
|||
|
|||
//////////////////////////////////////////////////
|
|||
void LedBlink(){ |
|||
digitalWrite(LED_BUILTIN, LOW); |
|||
delay(150); |
|||
digitalWrite(LED_BUILTIN, HIGH); // high = off
|
|||
delay(150); |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
void handleLog() |
|||
{ |
|||
server.send(200, "text/html", g_log.c_str()); |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
void switchBaud(int newRate) |
|||
{ |
|||
if(g_baudRate == newRate) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if(g_baudRate != 0) |
|||
{ |
|||
Serial.flush(); |
|||
delay(20); |
|||
Serial.end(); |
|||
delay(20); |
|||
} |
|||
|
|||
char szMsg[50]; |
|||
snprintf(szMsg, sizeof(szMsg)-1, "New baud: %d", newRate); |
|||
Log(szMsg); |
|||
|
|||
Serial.begin(newRate); |
|||
g_baudRate = newRate; |
|||
|
|||
delay(20); |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
void waitForSerial() |
|||
{ |
|||
for(int ix=0; ix<150;ix++) |
|||
{ |
|||
if(Serial.available()) break; |
|||
delay(10); |
|||
} |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
int readFromSerial() |
|||
{ |
|||
memset(g_szRecvBuff, 0, sizeof(g_szRecvBuff)); |
|||
int recvBuffLen = 0; |
|||
bool foundTerminator = true; |
|||
|
|||
waitForSerial(); |
|||
|
|||
while(Serial.available()) |
|||
{ |
|||
char szResponse[256] = ""; |
|||
const int readNow = Serial.readBytesUntil('>', szResponse, sizeof(szResponse)-1); //all commands terminate with "$$\r\n\rpylon>" (no new line at the end)
|
|||
if(readNow > 0 && |
|||
szResponse[0] != '\0') |
|||
{ |
|||
if(readNow + recvBuffLen + 1 >= (int)(sizeof(g_szRecvBuff))) |
|||
{ |
|||
Log("WARNING: Read too much data on the console!"); |
|||
break; |
|||
} |
|||
|
|||
strcat(g_szRecvBuff, szResponse); |
|||
recvBuffLen += readNow; |
|||
|
|||
if(strstr(g_szRecvBuff, "$$\r\n\rpylon")) |
|||
{ |
|||
strcat(g_szRecvBuff, ">"); //readBytesUntil will skip this, so re-add
|
|||
foundTerminator = true; |
|||
break; //found end of the string
|
|||
} |
|||
|
|||
if(strstr(g_szRecvBuff, "Press [Enter] to be continued,other key to exit")) |
|||
{ |
|||
//we need to send new line character so battery continues the output
|
|||
Serial.write("\r"); |
|||
} |
|||
|
|||
waitForSerial(); |
|||
} |
|||
} |
|||
|
|||
if(recvBuffLen > 0 ) |
|||
{ |
|||
if(foundTerminator == false) |
|||
{ |
|||
Log("Failed to find pylon> terminator"); |
|||
} |
|||
} |
|||
|
|||
return recvBuffLen; |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
bool readFromSerialAndSendResponse() |
|||
{ |
|||
const int recvBuffLen = readFromSerial(); |
|||
if(recvBuffLen > 0) |
|||
{ |
|||
server.sendContent(g_szRecvBuff); |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
bool sendCommandAndReadSerialResponse(const char* pszCommand) |
|||
{ |
|||
switchBaud(115200); |
|||
|
|||
if(pszCommand[0] != '\0') |
|||
{ |
|||
Serial.write(pszCommand); |
|||
} |
|||
Serial.write("\n"); |
|||
|
|||
const int recvBuffLen = readFromSerial(); |
|||
if(recvBuffLen > 0) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
//wake up console and try again:
|
|||
wakeUpConsole(); |
|||
|
|||
if(pszCommand[0] != '\0') |
|||
{ |
|||
Serial.write(pszCommand); |
|||
} |
|||
Serial.write("\n"); |
|||
|
|||
return readFromSerial() > 0; |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
void handleReq() |
|||
{ |
|||
bool respOK; |
|||
if(server.hasArg("code") == false) |
|||
{ |
|||
respOK = sendCommandAndReadSerialResponse(""); |
|||
} |
|||
else |
|||
{ |
|||
respOK = sendCommandAndReadSerialResponse(server.arg("code").c_str()); |
|||
} |
|||
|
|||
if(respOK) |
|||
{ |
|||
server.send(200, "text/plain", g_szRecvBuff); |
|||
} |
|||
else |
|||
{ |
|||
server.send(500, "text/plain", "????"); |
|||
} |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
void handleJsonOut() |
|||
{ |
|||
if(sendCommandAndReadSerialResponse("pwr") == false) |
|||
{ |
|||
server.send(500, "text/plain", "Failed to get response to 'pwr' command"); |
|||
return; |
|||
} |
|||
|
|||
parsePwrResponse(g_szRecvBuff); |
|||
prepareJsonOutput(g_szRecvBuff, sizeof(g_szRecvBuff)); |
|||
server.send(200, "application/json", g_szRecvBuff); |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
void handleRoot() { |
|||
unsigned long days = 0, hours = 0, minutes = 0; |
|||
unsigned long val = os_getCurrentTimeSec(); |
|||
|
|||
days = val / (3600*24); |
|||
val -= days * (3600*24); |
|||
|
|||
hours = val / 3600; |
|||
val -= hours * 3600; |
|||
|
|||
minutes = val / 60; |
|||
val -= minutes*60; |
|||
|
|||
static char szTmp[2500] = ""; |
|||
snprintf(szTmp, sizeof(szTmp)-1, "<html><b>Garage Battery</b><br>Time GMT: %d/%02d/%02d %02d:%02d:%02d (%s)<br>Uptime: %02d:%02d:%02d.%02d<br><br>free heap: %u<br>Wifi RSSI: %d<BR>Wifi SSID: %s", |
|||
year(), month(), day(), hour(), minute(), second(), "GMT", |
|||
(int)days, (int)hours, (int)minutes, (int)val, |
|||
ESP.getFreeHeap(), WiFi.RSSI(), WiFi.SSID().c_str()); |
|||
|
|||
|
|||
strncat(szTmp, "<BR><a href='/log'>Runtime log</a><HR>", sizeof(szTmp)-1); |
|||
strncat(szTmp, "<form action='/req' method='get'>Command:<input type='text' name='code'/><input type='submit'></form><a href='/req?code=pwr'>Power</a> | <a href='/req?code=help'>Help</a> | <a href='/req?code=log'>Event Log</a> | <a href='/req?code=time'>Time</a>", sizeof(szTmp)-1); |
|||
strncat(szTmp, "</html>", sizeof(szTmp)-1); |
|||
|
|||
server.send(200, "text/html", szTmp); |
|||
} |
|||
|
|||
unsigned long os_getCurrentTimeSec() |
|||
{ |
|||
static unsigned int wrapCnt = 0; |
|||
static unsigned long lastVal = 0; |
|||
unsigned long currentVal = millis(); |
|||
|
|||
if(currentVal < lastVal) |
|||
{ |
|||
wrapCnt++; |
|||
} |
|||
|
|||
lastVal = currentVal; |
|||
unsigned long seconds = currentVal/1000; |
|||
|
|||
//millis will wrap each 50 days, as we are interested only in seconds, let's keep the wrap counter
|
|||
return (wrapCnt*4294967) + seconds; |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
void syncTime() |
|||
{ |
|||
|
|||
// configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
|
|||
|
|||
//get time from NTP
|
|||
time_t currentTimeGMT = getNtpTime(); |
|||
if(currentTimeGMT) |
|||
{ |
|||
ntpTimeReceived = true; |
|||
setTime(currentTimeGMT); |
|||
} |
|||
else |
|||
{ |
|||
timer.setTimeout(3000, syncTime); //try again in 5 seconds
|
|||
} |
|||
|
|||
|
|||
struct tm timeinfo; |
|||
gmtime_r(¤tTimeGMT, &timeinfo); |
|||
Serial.print("Current time: "); |
|||
Serial.print(asctime(&timeinfo)); |
|||
|
|||
Serial.println("------------------"); |
|||
|
|||
|
|||
// Synchronize time using SNTP. This is necessary to verify that
|
|||
// the TLS certificates offered by the server are currently valid.
|
|||
// Serial.print("Setting time using SNTP");
|
|||
// configTime(8 * 3600, 0, "pool.ntp.org", "time.nist.gov");
|
|||
// time_t now = time(nullptr);
|
|||
// while (now < 8 * 3600 * 2) {
|
|||
// delay(500);
|
|||
// Serial.print(".");
|
|||
// now = time(nullptr);
|
|||
// }
|
|||
//
|
|||
// setTime(now);
|
|||
//
|
|||
// Serial.println("");
|
|||
// struct tm timeinfo;
|
|||
// gmtime_r(&now, &timeinfo);
|
|||
// Serial.print("Current time: ");
|
|||
// Serial.print(asctime(&timeinfo));
|
|||
|
|||
|
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
void wakeUpConsole() |
|||
{ |
|||
switchBaud(1200); |
|||
|
|||
//byte wakeUpBuff[] = {0x7E, 0x32, 0x30, 0x30, 0x31, 0x34, 0x36, 0x38, 0x32, 0x43, 0x30, 0x30, 0x34, 0x38, 0x35, 0x32, 0x30, 0x46, 0x43, 0x43, 0x33, 0x0D};
|
|||
//Serial.write(wakeUpBuff, sizeof(wakeUpBuff));
|
|||
Serial.write("~20014682C0048520FCC3\r"); |
|||
delay(1000); |
|||
|
|||
byte newLineBuff[] = {0x0E, 0x0A}; |
|||
switchBaud(115200); |
|||
|
|||
for(int ix=0; ix<10; ix++) |
|||
{ |
|||
Serial.write(newLineBuff, sizeof(newLineBuff)); |
|||
delay(1000); |
|||
|
|||
if(Serial.available()) |
|||
{ |
|||
while(Serial.available()) |
|||
{ |
|||
Serial.read(); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
#define MAX_PYLON_BATTERIES 2
|
|||
|
|||
struct pylonBattery |
|||
{ |
|||
bool isPresent; |
|||
long soc; //Coulomb in %
|
|||
long voltage; //in mW
|
|||
long current; //in mA, negative value is discharge
|
|||
long tempr; //temp of case or BMS?
|
|||
long cellTempLow; |
|||
long cellTempHigh; |
|||
long cellVoltLow; |
|||
long cellVoltHigh; |
|||
char baseState[9]; //Charge | Dischg | Idle
|
|||
char voltageState[9]; //Normal
|
|||
char currentState[9]; //Normal
|
|||
char tempState[9]; //Normal
|
|||
char time[20]; //2019-06-08 04:00:29
|
|||
char b_v_st[9]; //Normal (battery voltage?)
|
|||
char b_t_st[9]; //Normal (battery temperature?)
|
|||
|
|||
bool isCharging() const { return strcmp(baseState, "Charge") == 0; } |
|||
bool isDischarging() const { return strcmp(baseState, "Dischg") == 0; } |
|||
bool isIdle() const { return strcmp(baseState, "Idle") == 0; } |
|||
bool isBalancing() const { return strcmp(baseState, "Balance") == 0; } |
|||
|
|||
|
|||
bool isNormal() const |
|||
{ |
|||
if(isCharging() == false && |
|||
isDischarging() == false && |
|||
isIdle() == false && |
|||
isBalancing() == false) |
|||
{ |
|||
return false; //base state looks wrong!
|
|||
} |
|||
|
|||
return strcmp(voltageState, "Normal") == 0 && |
|||
strcmp(currentState, "Normal") == 0 && |
|||
strcmp(tempState, "Normal") == 0 && |
|||
strcmp(b_v_st, "Normal") == 0 && |
|||
strcmp(b_t_st, "Normal") == 0 ; |
|||
} |
|||
}; |
|||
|
|||
struct batteryStack |
|||
{ |
|||
int batteryCount; |
|||
int soc; //in %, if charging: average SOC, otherwise: lowest SOC
|
|||
int temp; //in mC, if highest temp is > 15C, this will show the highest temp, otherwise the lowest
|
|||
long currentDC; //mAh current going in or out of the battery
|
|||
long avgVoltage; //in mV
|
|||
char baseState[9]; //Charge | Dischg | Idle | Balance | Alarm!
|
|||
|
|||
pylonBattery batts[MAX_PYLON_BATTERIES]; |
|||
|
|||
bool isNormal() const |
|||
{ |
|||
for(int ix=0; ix<MAX_PYLON_BATTERIES; ix++) |
|||
{ |
|||
if(batts[ix].isPresent && |
|||
batts[ix].isNormal() == false) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
//in wH
|
|||
long getPowerDC() const |
|||
{ |
|||
return (long)(((double)currentDC/1000.0)*((double)avgVoltage/1000.0)); |
|||
} |
|||
|
|||
//wH estimated current on AC side (taking into account Sofar ME3000SP losses)
|
|||
long getEstPowerAc() const |
|||
{ |
|||
double powerDC = (double)getPowerDC(); |
|||
if(powerDC == 0) |
|||
{ |
|||
return 0; |
|||
} |
|||
else if(powerDC < 0) |
|||
{ |
|||
//we are discharging, on AC side we will see less power due to losses
|
|||
if(powerDC < -1000) |
|||
{ |
|||
return (long)(powerDC*0.94); |
|||
} |
|||
else if(powerDC < -600) |
|||
{ |
|||
return (long)(powerDC*0.90); |
|||
} |
|||
else |
|||
{ |
|||
return (long)(powerDC*0.87); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
//we are charging, on AC side we will have more power due to losses
|
|||
if(powerDC > 1000) |
|||
{ |
|||
return (long)(powerDC*1.06); |
|||
} |
|||
else if(powerDC > 600) |
|||
{ |
|||
return (long)(powerDC*1.1); |
|||
} |
|||
else |
|||
{ |
|||
return (long)(powerDC*1.13); |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
batteryStack g_stack; |
|||
|
|||
//////////////////////////////////////////////////
|
|||
long extractInt(const char* pStr, int pos) |
|||
{ |
|||
return atol(pStr+pos); |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
void extractStr(const char* pStr, int pos, char* strOut, int strOutSize) |
|||
{ |
|||
strOut[strOutSize-1] = '\0'; |
|||
strncpy(strOut, pStr+pos, strOutSize-1); |
|||
strOutSize--; |
|||
|
|||
|
|||
//trim right
|
|||
while(strOutSize > 0) |
|||
{ |
|||
if(isspace(strOut[strOutSize-1])) |
|||
{ |
|||
strOut[strOutSize-1] = '\0'; |
|||
} |
|||
else |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
strOutSize--; |
|||
} |
|||
} |
|||
|
|||
/* Output has mixed \r and \r\n
|
|||
pwr |
|||
|
|||
@ |
|||
|
|||
Power Volt Curr Tempr Tlow Thigh Vlow Vhigh Base.St Volt.St Curr.St Temp.St Coulomb Time B.V.St B.T.St |
|||
|
|||
1 49735 -1440 22000 19000 19000 3315 3317 Dischg Normal Normal Normal 93% 2019-06-08 04:00:30 Normal Normal |
|||
|
|||
.... |
|||
|
|||
8 - - - - - - - Absent - - - - - - - |
|||
|
|||
Command completed successfully |
|||
|
|||
$$ |
|||
|
|||
pylon |
|||
*/ |
|||
|
|||
//////////////////////////////////////////////////
|
|||
bool parsePwrResponse(const char* pStr) |
|||
{ |
|||
if(strstr(pStr, "Command completed successfully") == NULL) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
int chargeCnt = 0; |
|||
int dischargeCnt = 0; |
|||
int idleCnt = 0; |
|||
int alarmCnt = 0; |
|||
int socAvg = 0; |
|||
int socLow = 0; |
|||
int tempHigh = 0; |
|||
int tempLow = 0; |
|||
|
|||
memset(&g_stack, 0, sizeof(g_stack)); |
|||
|
|||
for(int ix=0; ix<MAX_PYLON_BATTERIES; ix++) |
|||
{ |
|||
char szToFind[32] = ""; |
|||
snprintf(szToFind, sizeof(szToFind)-1, "\r\r\n%d ", ix+1); |
|||
|
|||
const char* pLineStart = strstr(pStr, szToFind); |
|||
if(pLineStart == NULL) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
pLineStart += 3; //move past \r\r\n
|
|||
|
|||
extractStr(pLineStart, 55, g_stack.batts[ix].baseState, sizeof(g_stack.batts[ix].baseState)); |
|||
if(strcmp(g_stack.batts[ix].baseState, "Absent") == 0) |
|||
{ |
|||
g_stack.batts[ix].isPresent = false; |
|||
} |
|||
else |
|||
{ |
|||
g_stack.batts[ix].isPresent = true; |
|||
extractStr(pLineStart, 64, g_stack.batts[ix].voltageState, sizeof(g_stack.batts[ix].voltageState)); |
|||
extractStr(pLineStart, 73, g_stack.batts[ix].currentState, sizeof(g_stack.batts[ix].currentState)); |
|||
extractStr(pLineStart, 82, g_stack.batts[ix].tempState, sizeof(g_stack.batts[ix].tempState)); |
|||
extractStr(pLineStart, 100, g_stack.batts[ix].time, sizeof(g_stack.batts[ix].time)); |
|||
extractStr(pLineStart, 121, g_stack.batts[ix].b_v_st, sizeof(g_stack.batts[ix].b_v_st)); |
|||
extractStr(pLineStart, 130, g_stack.batts[ix].b_t_st, sizeof(g_stack.batts[ix].b_t_st)); |
|||
g_stack.batts[ix].voltage = extractInt(pLineStart, 6); |
|||
g_stack.batts[ix].current = extractInt(pLineStart, 13); |
|||
g_stack.batts[ix].tempr = extractInt(pLineStart, 20); |
|||
g_stack.batts[ix].cellTempLow = extractInt(pLineStart, 27); |
|||
g_stack.batts[ix].cellTempHigh = extractInt(pLineStart, 34); |
|||
g_stack.batts[ix].cellVoltLow = extractInt(pLineStart, 41); |
|||
g_stack.batts[ix].cellVoltHigh = extractInt(pLineStart, 48); |
|||
g_stack.batts[ix].soc = extractInt(pLineStart, 91); |
|||
|
|||
//////////////////////////////// Post-process ////////////////////////
|
|||
g_stack.batteryCount++; |
|||
g_stack.currentDC += g_stack.batts[ix].current; |
|||
g_stack.avgVoltage += g_stack.batts[ix].voltage; |
|||
socAvg += g_stack.batts[ix].soc; |
|||
|
|||
if(g_stack.batts[ix].isNormal() == false){ alarmCnt++; } |
|||
else if(g_stack.batts[ix].isCharging()){chargeCnt++;} |
|||
else if(g_stack.batts[ix].isDischarging()){dischargeCnt++;} |
|||
else if(g_stack.batts[ix].isIdle()){idleCnt++;} |
|||
else{ alarmCnt++; } //should not really happen!
|
|||
|
|||
if(g_stack.batteryCount == 1) |
|||
{ |
|||
socLow = g_stack.batts[ix].soc; |
|||
tempLow = g_stack.batts[ix].cellTempLow; |
|||
tempHigh = g_stack.batts[ix].cellTempHigh; |
|||
} |
|||
else |
|||
{ |
|||
if(socLow > g_stack.batts[ix].soc){socLow = g_stack.batts[ix].soc;} |
|||
if(tempHigh < g_stack.batts[ix].cellTempHigh){tempHigh = g_stack.batts[ix].cellTempHigh;} |
|||
if(tempLow > g_stack.batts[ix].cellTempLow){tempLow = g_stack.batts[ix].cellTempLow;} |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
|||
//now update stack state:
|
|||
g_stack.avgVoltage /= g_stack.batteryCount; |
|||
g_stack.soc = socLow; |
|||
|
|||
if(tempHigh > 15000) //15C
|
|||
{ |
|||
g_stack.temp = tempHigh; //in the summer we highlight the warmest cell
|
|||
} |
|||
else |
|||
{ |
|||
g_stack.temp = tempLow; //in the winter we focus on coldest cell
|
|||
} |
|||
|
|||
if(alarmCnt > 0) |
|||
{ |
|||
strcpy(g_stack.baseState, "Alarm!"); |
|||
} |
|||
else if(chargeCnt == g_stack.batteryCount) |
|||
{ |
|||
strcpy(g_stack.baseState, "Charge"); |
|||
g_stack.soc = (int)(socAvg / g_stack.batteryCount); |
|||
} |
|||
else if(dischargeCnt == g_stack.batteryCount) |
|||
{ |
|||
strcpy(g_stack.baseState, "Dischg"); |
|||
} |
|||
else if(idleCnt == g_stack.batteryCount) |
|||
{ |
|||
strcpy(g_stack.baseState, "Idle"); |
|||
} |
|||
else |
|||
{ |
|||
strcpy(g_stack.baseState, "Balance"); |
|||
} |
|||
|
|||
|
|||
return true; |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
void prepareJsonOutput(char* pBuff, int buffSize) |
|||
{ |
|||
memset(pBuff, 0, buffSize); |
|||
snprintf(pBuff, buffSize-1, "{\"soc\": %d, \"temp\": %d, \"currentDC\": %ld, \"avgVoltage\": %ld, \"baseState\": \"%s\", \"batteryCount\": %d, \"powerDC\": %ld, \"estPowerAC\": %ld, \"isNormal\": %s}", g_stack.soc, |
|||
g_stack.temp, g_stack.isNormal() ? "true" : "false"); |
|||
} |
|||
|
|||
|
|||
//////////////////////////////////////////////////
|
|||
void loop() { |
|||
#ifdef ENABLE_MQTT
|
|||
mqttLoop(); |
|||
#endif
|
|||
|
|||
ArduinoOTA.handle(); |
|||
server.handleClient(); |
|||
timer.run(); |
|||
|
|||
//if there are bytes availbe on serial here - it's unexpected
|
|||
//when we send a command to battery, we read whole response
|
|||
//if we get anything here anyways - we will log it
|
|||
int bytesAv = Serial.available(); |
|||
if(bytesAv > 0) |
|||
{ |
|||
if(bytesAv > 63) |
|||
{ |
|||
bytesAv = 63; |
|||
} |
|||
|
|||
char buff[64+4] = "RCV:"; |
|||
if(Serial.readBytes(buff+4, bytesAv) > 0) |
|||
{ |
|||
digitalWrite(LED_BUILTIN, LOW); |
|||
delay(5); |
|||
digitalWrite(LED_BUILTIN, HIGH);//high is off
|
|||
|
|||
Log(buff); |
|||
} |
|||
} |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
#ifdef ENABLE_MQTT
|
|||
#define ABS_DIFF(a, b) (a > b ? a-b : b-a)
|
|||
|
|||
//////////////////////////////////////////////////
|
|||
void mqtt_publish_f(const char* topic, float newValue, float oldValue, float minDiff, bool force) |
|||
{ |
|||
char szTmp[16] = ""; |
|||
snprintf(szTmp, 15, "%.2f", newValue); |
|||
if(force || ABS_DIFF(newValue, oldValue) > minDiff) |
|||
{ |
|||
mqttClient.publish(topic, szTmp, false); |
|||
} |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
void mqtt_publish_i(const char* topic, int newValue, int oldValue, int minDiff, bool force) |
|||
{ |
|||
char szTmp[16] = ""; |
|||
snprintf(szTmp, 15, "%d", newValue); |
|||
if(force || ABS_DIFF(newValue, oldValue) > minDiff) |
|||
{ |
|||
mqttClient.publish(topic, szTmp, false); |
|||
} |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
void mqtt_publish_s(const char* topic, const char* newValue, const char* oldValue, bool force) |
|||
{ |
|||
if(force || strcmp(newValue, oldValue) != 0) |
|||
{ |
|||
mqttClient.publish(topic, newValue, false); |
|||
} |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
void pushBatteryDataToMqtt(const batteryStack& lastSentData, bool forceUpdate /* if true - we will send all data regardless if it's the same */) |
|||
{ |
|||
Serial.println("entered publish"); |
|||
mqtt_publish_f(MQTT_TOPIC_ROOT "soc", g_stack.soc, lastSentData.soc, 0, forceUpdate); |
|||
mqtt_publish_f(MQTT_TOPIC_ROOT "temp", (float)g_stack.temp/1000.0, (float)lastSentData.temp/1000.0, 0, forceUpdate); |
|||
mqtt_publish_i(MQTT_TOPIC_ROOT "estPowerAC", g_stack.getEstPowerAc(), lastSentData.getEstPowerAc(), 10, forceUpdate); |
|||
mqtt_publish_i(MQTT_TOPIC_ROOT "battery_count",g_stack.batteryCount, lastSentData.batteryCount, 0, forceUpdate); |
|||
mqtt_publish_s(MQTT_TOPIC_ROOT "base_state", g_stack.baseState, lastSentData.baseState , forceUpdate); |
|||
mqtt_publish_i(MQTT_TOPIC_ROOT "is_normal", g_stack.isNormal() ? 1:0, lastSentData.isNormal() ? 1:0, 0, forceUpdate); |
|||
Serial.println("finished publish"); |
|||
} |
|||
|
|||
//////////////////////////////////////////////////
|
|||
void mqttLoop() |
|||
{ |
|||
//if we have problems with connecting to mqtt server, we will attempt to re-estabish connection each 1minute (not more than that)
|
|||
static unsigned long g_lastConnectionAttempt = 0; |
|||
|
|||
//first: let's make sure we are connected to mqtt
|
|||
const char* topicLastWill = MQTT_TOPIC_ROOT "availability"; |
|||
|
|||
if (!mqttClient.connected() && (g_lastConnectionAttempt == 0 || os_getCurrentTimeSec() - g_lastConnectionAttempt > 60)) { |
|||
if(mqttClient.connect("GarageBattery", MQTT_USER, MQTT_PASSWORD, topicLastWill, 1, true, "offline")) |
|||
{ |
|||
Log("Connected to MQTT server: " MQTT_SERVER); |
|||
Serial.println("Connected to MQTT server!!!"); |
|||
mqttClient.publish(topicLastWill, "online", true); |
|||
// test by TeZ
|
|||
mqttClient.publish("home/grid_battery/tez", "cicciobello", true); |
|||
mqttClient.publish("testbytez", "232323", true); |
|||
|
|||
} |
|||
else |
|||
{ |
|||
Log("Failed to connect to MQTT server."); |
|||
Serial.println("Failed to connect to MQTT server."); |
|||
} |
|||
|
|||
g_lastConnectionAttempt = os_getCurrentTimeSec(); |
|||
} |
|||
|
|||
|
|||
|
|||
//next: read data from battery and send via MQTT (but only once per MQTT_PUSH_FREQ_SEC seconds)
|
|||
static unsigned long g_lastDataSent = 0; |
|||
if(mqttClient.connected() && |
|||
os_getCurrentTimeSec() - g_lastDataSent > MQTT_PUSH_FREQ_SEC && |
|||
sendCommandAndReadSerialResponse("pwr") == true) |
|||
{ |
|||
Serial.println("Connected and sending to MQTT server!!!"); |
|||
|
|||
static batteryStack lastSentData; //this is the last state we sent to MQTT, used to prevent sending the same data over and over again
|
|||
static unsigned int callCnt = 0; |
|||
|
|||
parsePwrResponse(g_szRecvBuff); |
|||
|
|||
bool forceUpdate = (callCnt % 20 == 0); //push all the data every 20th call
|
|||
pushBatteryDataToMqtt(lastSentData, forceUpdate); |
|||
|
|||
callCnt++; |
|||
g_lastDataSent = os_getCurrentTimeSec(); |
|||
memcpy(&lastSentData, &g_stack, sizeof(batteryStack)); |
|||
} |
|||
|
|||
mqttClient.loop(); |
|||
} |
|||
|
|||
#endif //ENABLE_MQTT
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue