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