-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathESP8266_anemometer-v3.5.ino
454 lines (401 loc) · 18.3 KB
/
ESP8266_anemometer-v3.5.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
/*
Anemometer from an idea of https://arduino.stackexchange.com/questions/62327/cannot-read-modbus-data-repetitively
https://www.cupidcontrols.com/2015/10/software-serial-modbus-master-over-rs485-transceiver/
_________________________________________________________________
| |
| author : Philippe de Craene <[email protected] |
| Any feedback is welcome |
|
_________________________________________________________________
Materials :
1* Wemos D1 mini - tested with IDE version 1.8.7 and 1.8.9
1* wind sensor - RS485 MODBUS protocol of communication
1* MAX485 DIP8
1* RTC 1307
1* LCD1602 with I2C extension
1* SD card
Versions chronology:
version 1 - 7 sept 2019 - first test on Arduino Uno
Version 3 - 9 sept 2019 - ESP8266 based with RTC and SD card
version 3.1 - 3 oct 2019 - avoid infinite while loop when wind sensor not connected
version 3.2 - 12 oct 2019 - add record of max value of wind speed
version 3.4 - 18 nov 2019 - reconnect to wifi in case of lost
version 3.5 - 27 mar 2020 - set byte Anemometer_buf[7] instead of 8
ESP8266 pinup :
D1 => SCL for LCD1602 and DS1307 (Arduino A5)
D2 => SDA for LCD1602 and DS1307 (Arduino A4)
D3 => Rx = RO of MAX485 - pin 1
D4 => Tx = DI of MAX485 - pin 4
D8 => RTS = RE/DE of MAX485 - pins 2&3
D5 => SCK for SDcard (Arduino 13)
D6 => MISO for SDcard (Arduino 12)
D7 => MOSI for SDcard (Arduino 11)
D0 => CS for SDcard (SDcard Arduino shield 10) CS should be in D8 but must be at 0
during boot, but stay stuck at Vcc....
*/
#include <ESP8266WiFi.h> // https://github.com/esp8266/Arduino
#include <WiFiUdp.h>
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
#include <ESP8266WebServer.h> // required pour WifiManager.h
#include <DNSServer.h> // required pour WifiManager.h
#include <ArduinoOTA.h> // https://github.com/marcudanf/arduinoOTA
#include <TimeLib.h> // https://github.com/PaulStoffregen/Time
#include <DS1307RTC.h> // https://github.com/PaulStoffregen/DS1307RTC
#include <SD.h> // yet include : https://github.com/adafruit/SD
#include <SoftwareSerial.h> // https://github.com/PaulStoffregen/SoftwareSerial
#include <LiquidCrystal_I2C.h> // https://github.com/lucasmaziero/LiquidCrystal_I2C
#define RX D3 // Soft Serial RS485 Receive pin
#define TX D4 // Soft Serial RS485 Transmit pin
#define RTS D8 // RS485 Direction control
#define RS485Transmit HIGH
#define RS485Receive LOW
#define CS D0 // CS for SDcard
SoftwareSerial RS485Serial(RX, TX); // additional serial port for RS485
WiFiServer server(80); // web server on www default port 80
LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the LCD address to 0x27 for a 16 chars and 2 line display
File dataFile; // initialisation of the SD card
// NTP server declaration
int TZ = 2; // timezone
unsigned int localPort = 2390; // local port to listen for UDP packets
/* Don't hardwire the IP address or we won't get the benefits of the pool.
Lookup the IP address for the host name instead */
//IPAddress timeServer(129, 6, 15, 28); // time.nist.gov NTP server
IPAddress timeServerIP; // time.nist.gov NTP server address
const char* ntpServerName = "time.nist.gov";
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; // buffer to hold incoming and outgoing packets
WiFiUDP udp; // A UDP instance to let us send and receive packets over UDP
// Variables declaration
float Anemometer = 0, memo_Anemometer = 0, max_Anemometer = 0;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
bool afficher = true; // affichage sur LCD
unsigned int delai = 2000; // delay between 2 measures in ms
unsigned int memo_actuel = 0;
String max_wind_date = "aucun vent !";
//
// SETUP
//_____________________________________________________________________________________________
void setup() {
pinMode(RTS, OUTPUT);
pinMode(CS, OUTPUT);
// Start the built-in serial port, for Serial Monitor
Serial.begin(9600);
Serial.println("Anemometer");
// Start the Modbus serial Port, for anemometer
RS485Serial.begin(9600);
delay(100);
// initialize the LCD
lcd.begin(); // Init with pin default ESP8266 or ARDUINO
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Anemometer");
lcd.setCursor(0, 1);
// see if the RTC is present and is set
tmElements_t tm;
if (RTC.read(tm)) {
Serial.print("Ok, Time = ");
Serial.print(tm.Hour); Serial.write(':');
Serial.print(tm.Minute); Serial.write(':');
Serial.print(tm.Second);
Serial.print(", Date (D/M/Y) = ");
Serial.print(tm.Day);
Serial.write('/');
Serial.print(tm.Month);
Serial.write('/');
Serial.print(tmYearToCalendar(tm.Year));
Serial.println();
setSyncProvider(RTC.get); // to get the time from the RTC
lcd.print("Time: OK ");
}
else {
if (RTC.chipPresent()) Serial.println("The DS1307 is stopped. Please set time");
else Serial.println("DS1307 read error! Please check the circuitry.");
lcd.print("Time: FAIL ");
}
Serial.println();
delay(1000);
lcd.setCursor(0, 1);
// see if the card is present and can be initialized:
if (!SD.begin(CS)) Serial.println("Card failed, or not present");
else { Serial.println("card initialized."); }
// Open up the file we're going to log to!
dataFile = SD.open("datalog.txt", FILE_WRITE);
if (!dataFile) {
Serial.println("datalog.txt error !");
lcd.print("SDcard: FAIL");
}
else {
Serial.println(" datalog.txt ready ...");
lcd.print("SDcard: OK ");
}
Serial.println();
delay(1000);
lcd.setCursor(0, 0);
lcd.print("WiFi is ");
lcd.setCursor(0, 1);
lcd.print("starting ...");
// AP will start if no wifi identifiers in memory or wrong identification
// AP can be accessed from ssid "AutoConnectAP" then IP address 192.168.4.1 within 150 seconds
// in cas of unsuccess after 150 seconds the wifi will not be defined
// for local intialization. Once its business is done, there is no need to keep it around
WiFiManager monwifi;
monwifi.setConfigPortalTimeout(180); // 150 seconds timeout
// fetches ssid and pass from eeprom and tries to connect. If it does not connect it starts
// an access point with the specified name and goes into a blocking loop awaiting configuration
if(!monwifi.autoConnect("AutoConnectAP")) Serial.println("not setup");
else {
// Connect to Wi-Fi network with SSID and password
byte i = 0; // counter of request to wifi connexion
byte imax = 10; // max number of request to wifi connexion
Serial.print("connexion au Wifi en cours ");
Serial.println();
while( (WiFi.status() != WL_CONNECTED) && i < imax ) {
i++;
if( i > imax ) {
Serial.println("pas de reseau wifi");
lcd.setCursor(0, 1);
lcd.print("not started ");
break;
}
delay(500);
Serial.print(".");
}
} // end of else monwifi.autoConnect
// if wifi is connected
if( i <= imax ) {
// show IP address
Serial.println();
Serial.println("Wifi connect.");
Serial.print("Address IP : ");
Serial.println(WiFi.localIP());
lcd.setCursor(0, 1);
lcd.print("started ! ");
// udp service startup
Serial.println("Starting UDP");
udp.begin(localPort);
Serial.print("Local port: ");
Serial.println(udp.localPort());
} // end of test i
delay(1000);
server.begin(); // web server startup
if ( !RTC.read(tm) ) getNTP(); // get the internet date and time if no RTC
// 2 of the 3 lines of code for OTA
ArduinoOTA.setHostname("Anemometer"); // device name
ArduinoOTA.begin(); // OTA initialisation
} // end of setup
//
// LOOP
//_____________________________________________________________________________________________
void loop() {
// The 3rd code line for OTA
ArduinoOTA.handle();
// to display data on a html page
if( WiFi.status() != WL_CONNECTED ) {
lcd.setCursor(0, 0);
lcd.print("Anemometer nowifi");
}
else {
lcd.setCursor(0, 0);
lcd.print("Anemometer wifi ");
}
lcd.setCursor(0, 1);
webserver();
// Daily time update
if( hour() == 1 && minute() == 0 && second() < 2 ) getNTP();
// The above of the loop is done every waitdelay seconds only
unsigned int actuel = millis();
if( actuel - memo_actuel < delai ) return;
memo_actuel = actuel;
// RS485 MODBUS Request and Receive with the anemometer
byte Anemometer_buf[7];
byte i = 0; // simple counter to avoid infinite while
memo_Anemometer = Anemometer; // remember past value
Anemometer_buf[1] = 0;
while( Anemometer_buf[1] != 0x03 ) { // if received message has an error
// MODBUS Tramsmit by sending a request to the anemometer
digitalWrite(RTS, RS485Transmit); // init Transmit
byte Anemometer_request[] = {0x01, 0x03, 0x00, 0x16, 0x00, 0x01, 0x65, 0xCE}; // inquiry frame
RS485Serial.write(Anemometer_request, sizeof(Anemometer_request));
RS485Serial.flush();
// MODBUS Reception of the anemometer's answer
digitalWrite(RTS, RS485Receive); // init Receive
RS485Serial.readBytes(Anemometer_buf, 7);
// data treatment
Serial.print("wind speed : ");
for( byte i=0; i<6; i++ ) {
Serial.print(Anemometer_buf[i], HEX);
Serial.print(" ");
}
Serial.print(" ==> ");
Serial.print(Anemometer_buf[4]);
Serial.print(" /10 m/s");
Serial.println();
delay(500);
i++;
Anemometer = Anemometer_buf[4]/10.0;
if( i == 10 ) {
Anemometer = -99.9;
break;
}
} // end of while
lcd.setCursor(0, 1);
lcd.print(Anemometer);
lcd.print(" m/s ");
// store the max value
if( Anemometer > max_Anemometer ) {
max_Anemometer = Anemometer;
String Minutes = "h";
if( minute() < 10 ) Minutes += "0"+String(minute());
else Minutes += String(minute());
max_wind_date = String(daysOfTheWeek[weekday()-1])+" "+ String(day())+"/"+String(month())+" à "+String(hour())+Minutes;
}
// Store on SDcard
if( (Anemometer != memo_Anemometer) && (Anemometer >= 0) ) { // if wind speed change
String dataString = ""; // initialisation d'une chaine de caractres
dataString += String(daysOfTheWeek[weekday()-1]);
dataString += ";";
dataString += String(day(), DEC);
dataString += ";";
dataString += String(month(), DEC);
dataString += ";";
dataString += String(year(), DEC);
dataString += ";";
dataString += String(hour(), DEC);
dataString += ";";
dataString += String(minute(), DEC);
dataString += ";";
dataString += String(second(), DEC);
dataString += ";";
dataString += String(Anemometer);
dataString += ";";
dataFile.println(dataString); // record data on SD card
dataFile.flush(); // clean buffer
Serial.println(dataString); // show record on console
} // end test Anemomter
} // end of loop
//
// webserver : display data on html page
//____________________________________________________________________________________________
void webserver() {
WiFiClient client = server.available(); // Listen for incoming clients
if( client ) { // If a new client connects,
Serial.println("Nouveau client."); // print a message out in the serial port
String entete = client.readStringUntil('\r'); // read the header until \r
Serial.print("header received => ");
Serial.println(entete);
bool raz = false;
String etat_afficher[] = {"non", "oui"};
if( entete.indexOf("GET /?A=0") >= 0) afficher = false;
if( entete.indexOf("GET /?A=1") >= 0) afficher = true;
if( entete.indexOf("GET /R") >= 0) {
raz = true;
max_Anemometer = 0;
}
Serial.print("\n Etat de l'affichage du LCD : ");
Serial.println(afficher);
if( afficher == true ) lcd.backlight();
else lcd.noBacklight();
client.flush(); //nettoie le tampon...
// HTTP header
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println("Connection: close");
client.println();
// Display the HTML web page with every 4 seconds refraish
client.println("<!DOCTYPE html><html lang=fr-FR>");
client.println("<head><meta http-equiv='refresh' content='4'/>");
client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
client.println("<link rel=\"icon\" href=\"data:,\">");
// CSS to style the on/off buttons
client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
client.println(".button { background-color: #8A0808; border: none; color: white; padding: 16px 40px;");
client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
client.println(".button2 {background-color: #32CD99;}");
client.println(".button3 {background-color: #08298A;}</style></head>");
// Web Page Heading
client.println("<body><h1>Anémomètre chez Fifi</h1>");
String Minutes = "h";
if( minute() < 10 ) Minutes += "0"+String(minute());
else Minutes += String(minute());
client.println("<p><h3>Il est " + String(hour()) + Minutes + " et " + String(second()) + " secondes;</h3></p>");
client.println("<HR size=2 align=center width=\"80%\">");
client.println("<p><h3>Vitesse du vent " + String(Anemometer) + " m/s</h3></p>");
client.println("<HR size=2 align=center width=\"80%\">");
client.println("<p><h3>Maxi mesuré " + String(max_Anemometer) + " m/s");
client.println("<br>" + max_wind_date +"</h3></p>");
client.println("<HR size=2 align=center width=\"80%\">");
if( raz == true ) client.println("<p><a href=\"/\"><button class=\"button button2\"\">Marquer Maxi</button></a></p>");
else client.println("<p><a href=\"/R\"><button class=\"button button1\"\">Effacer Maxi</button></a></p>");
client.println("<HR size=2 align=center width=\"80%\">");
client.println("<p><h3>Affichage : " + etat_afficher[afficher] +"</h2></p>");
client.println("<FORM>");
client.println("<INPUT type=\"radio\" name=\"A\" value=\"1\">Allumer");
client.println("<INPUT type=\"radio\" name=\"A\" value=\"0\">Eteindre");
client.println("<INPUT class=\"button button3\" type=\"submit\" value=\"Actualiser\"></FORM>");
client.println("</BODY></center></html>");
client.println(); // The HTTP response ends with another blank line
Serial.println("Fin de transmission web - Client disconnected.");
Serial.println("");
} // end of client
} // end of webserver
//
// getNTP : to get date and time from internet
//____________________________________________________________________________________________
void getNTP() {
byte i = 0; // NTP request counter
byte imax = 40; // max number of request
WiFi.hostByName(ntpServerName, timeServerIP); // get a random server from the pool
do {
i++;
Serial.print("sending NTP packet... ");
Serial.println(i);
memset(packetBuffer, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
// Initialize values needed to form NTP request
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(timeServerIP, 123); // NTP requests are to port 123
udp.write(packetBuffer, NTP_PACKET_SIZE);
udp.endPacket();
delay(1000); // wait to see if a reply is available
} while(!udp.parsePacket() && i<imax);
if( i<imax ) { // We've received a packet, read the data from it
udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
//the timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long. First, esxtract the two words:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
Serial.print("Seconds since Jan 1 1900 = ");
Serial.println(secsSince1900);
// now convert NTP time into everyday time:
Serial.print("Unix time = ");
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
// subtract seventy years:
unsigned long epoch = secsSince1900 - seventyYears;
// print Unix time:
Serial.println(epoch);
// // to see if it is summer or winter time
int mois = month();
int jour = day();
int joursemaine = weekday();
if( (mois > 3 && mois < 10) ||
(mois == 3 && (jour - joursemaine) > 22 ) ||
(mois == 10 && (jour - joursemaine) < 23 ) )
TZ = 2;
else TZ = 1; // heure d'hiver
RTC.set(epoch + TZ*3600);
setTime(epoch + TZ*3600); // date and time adjust
}
else setSyncProvider(RTC.get); // the function to get the time from the RTC
} // end of getNTP