Nodemcu temp datalogger client to Rapsberry PI webserver
Posted: Fri Jan 15, 2016 3:12 pm
I did write code on the NodeMCU reading temp sensors (DHT22, BMP180, DS18B20) sending this as client to an Raspberry PI2 webserver (Used to control my house heating system, thermostat). The data is read every 2 seconds, end send to the RPI every 3 minutes.
The data is showd also on an OLED display connected to the NodeMCU and is presented at a webserver on the NodeMCU itself at port 222.
The client and webserver are also using port 222
The NodeMCU with OLED display:
The webserver on the NodeMCU:
The Thermostat website using the NodeMCU data for control heating:
Hereby the code of the NodeMCU in C and the webserver code at the Raspberry PI in Python
The webserver code on the RPI:
The data is showd also on an OLED display connected to the NodeMCU and is presented at a webserver on the NodeMCU itself at port 222.
The client and webserver are also using port 222
The NodeMCU with OLED display:
The webserver on the NodeMCU:
The Thermostat website using the NodeMCU data for control heating:
Hereby the code of the NodeMCU in C and the webserver code at the Raspberry PI in Python
Code: Select all
/*
* Edward Hogeveen. d.d. 26-11-2015
* NodeMCU ESP-12
* Leest de sensoren: DHT22, ds18b20 en BMP180 uit.
* Er kan maximaal 1 sensor per type aangesloten worden
* Voor Pin aansluiting zie hieronder.
*
* Er wordt een webstring opgebouwd, inhoud:
* NodeId ds18b20_temp dht_temp dht_humidity BMP180_temp BMP180_sealevelpressure wifi_signal_strength
* Indien een sensor niet is aangesloten wordt de waarde "NNN" verstuurd
* Deze wordt elke 3 minuten (LoopsNum = 90 x interval = 2000 miliseconden) naar de RPI gestuurd (IP=192.168.2.185)
* Er is een webserver en client socket verbinding op poort 222 gemaakt.
*
* Pin aansluitingen:
* --------------------------------------------------------
* 3.3V op Fysieke pen 6
* GND op Fysieke pen 7
* DHT22 data op Fysieke pin 5 (D4, GPIO2)
* BMP180 SDA op Fysieke pin 3 (D2, GPIO4)
* BMP180 SCL op Fysieke pin 2 (D1, GPIO5)
* DS18b20 op Fysieke pin 4 (D3, GPIO0)
*
*/
// Includes
#include "NodeMCU.h"
#include <DHT.h>
#include <OneWire.h>
#include <ESP8266WiFi.h>
#include <Wire.h>
#include <Adafruit_BMP085.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <Arduino.h>
#include <ESP8266WiFiMulti.h>
#include <WebSocketsClient.h>
#include <Hash.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <ESP_SSD1306.h>
//Defines
#define SDAPIN 04
#define SCLPIN 05
#define ROTATION_0 0
#define ROTATION_90 1
#define DHTTYPE DHT22
#define DHTPIN 2 //Dit is fysiek pin D4 -> GPI02
#define Debug false // Show yes or no serial results for debugging
#define USE_SERIAL Serial
#define versieK "v2.3" // Version number for OLED
#define versie "EWH v2.3 d.d.2-1-2016" // Long version number
// Variablen
//------------------------------------------------------------------------------
String NodeID = "NodeMCU03"; // IDnr van de Node moet per Node uniek zijn
//String NodeID = "TestMCU01"; // IDnr van de Node moet per Node uniek zijn
const long interval = 2000; // Interval wachttijd 2 sec
const int LoopsNum = 90; // Aantal keer interval in de monitor printen voordat de client een bericht stuurt (30x = 1 minuut)
//------------------------------------------------------------------------------
int NoBMP = false; // Boolean to show if BMP is connected or not
int ledState = LOW;
unsigned long previousMillis = 0;
int CurLoop = 60; // Begin bij 60 te tellen, eerste zend zal dan na 1 minuut zijn ipv 3 minuten
const char* ssid = "SSID";
const char* password = "PASSWORD";
int status = WL_IDLE_STATUS; // the Wifi radio's status
String webString = ""; // String to display and send to RPI
unsigned long ulReconncount = 0; // how often did we connect to WiFi
int MisreadNr = 0;
float Oldh, Oldt, h, t, f, hif, hic, celsiusDS, fahrenheitDS; // General variabelen voor DS en DHT22
float Temp, Bar, BarS, Alt, AltS; // General variabelen voor BMP180
ESP_SSD1306 display(false); //reset disable
OneWire ds(0); // Data pin voor DS18b20 op fysieke pin 4 (D3, GPIO0)een 4.7K resistor is necessary)
MDNSResponder mdns;
ESP8266WebServer server(222);
ESP8266WiFiMulti WiFiMulti;
WebSocketsClient webSocket;
DHT dht(DHTPIN, DHTTYPE, 12); // 12 tot nu het beste
//DHT dht;
// de derde variabele bepaald de treshold. Cijfer is afhankelijk van processorsnelheid
// 11 wordt voor ESP aangeraden. Trial en error voor de juiste waarde 12 werkt tot nu het beste
Adafruit_BMP085 bmp; // BMP180 is aangesloten op fysieke pin D2 GPIO04 SDA op pin D1 GPIO05 SCL
/*----------------------------------------------------------
*
*Send Webstring naar webpage
*
-----------------------------------------------------------*/
void handleRoot() {
String h = "<!DOCTYPE HTML>\n";
h += "<html>\n";
h += "<head>\n";
h += "<meta http-equiv='refresh' content='5'/>\n";
h += "<title>ESP8266 " + NodeID + "</title>\n";
h +=
"<style>body { background-color: #000000; font-family: Arial, Helvetica, Sans-Serif; Color: #FFFF00; }</style>\n";
h += "</head>\n";
h += "<body>\n";
String f =
"<table width=\"100%\" bgcolor=\"black\" cellpadding=\"0\" border=\"0\">\n";
f +=
"<tr><td><p style = \"color: white; background: black;font-size: 0.8em;";
f += "font-weight: bold; text-align: left; margin: 0px 10px 0px 10px;\">\n";
f += "<a><-Edward Hogeveen</a> © 2015-></p></td></tr>";
f += "</table>\n";
f += "</body>\n";
f += "</html>\n";
String p = "<p> Versie : " + String(versie) + "</p><h1>" + webString+ "</h1>\n";
p += "<p>Wifi-restarts : " + String(ulReconncount) + "<br>";
p += "Misreads : " + String(MisreadNr)+ " </p>\n";
server.send(200, "text/html", h + p + f);
}
void webSocketEvent(WStype_t type, uint8_t * payload, size_t lenght) {
switch (type) {
case WStype_DISCONNECTED:
USE_SERIAL.printf("[WSc] Disconnected!\n");
break;
case WStype_CONNECTED: {
USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload);
}
break;
case WStype_TEXT: {
if (Debug) {
USE_SERIAL.printf("[WSc] get text: %s\n", payload);
}
}
break;
case WStype_BIN:
USE_SERIAL.printf("[WSc] get binary lenght: %u\n", lenght);
hexdump(payload, lenght);
break;
case WStype_ERROR:
USE_SERIAL.printf("[WSc] ERROR: \n");
break;
}
}
void handleNotFound() {
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
}
/*-----------------------------------------------------------------
*
* (re-)start WiFi
*
------------------------------------------------------------------ */
void WiFiStart() {
ulReconncount++;
USE_SERIAL.print("Connecting to "); // Connect to WiFi
display.println("Verbinden");
display.display();
USE_SERIAL.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
USE_SERIAL.print(".");
}
USE_SERIAL.println("");
USE_SERIAL.println("WiFi connected");
USE_SERIAL.print("You're connected to the network");
IPAddress ip = WiFi.localIP();
USE_SERIAL.print("IP Address: ");
USE_SERIAL.println(ip);
display.println(ip);
display.display();
delay(2000);
// webserver setup
if (mdns.begin("esp8266", WiFi.localIP())) { // Start webserver
USE_SERIAL.println("MDNS responder started");
}
server.on("/", handleRoot);
server.onNotFound(handleNotFound);
server.begin();
USE_SERIAL.println("HTTP server started");
webSocket.begin("192.168.2.185", 222);
webSocket.onEvent(webSocketEvent);
}
void printWifiData() {
// print your WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
USE_SERIAL.print("IP Address: ");
USE_SERIAL.println(ip);
// print your MAC address:
byte mac[6];
WiFi.macAddress(mac);
USE_SERIAL.print("MAC address: ");
USE_SERIAL.print(mac[5], HEX);
USE_SERIAL.print(":");
USE_SERIAL.print(mac[4], HEX);
USE_SERIAL.print(":");
USE_SERIAL.print(mac[3], HEX);
USE_SERIAL.print(":");
USE_SERIAL.print(mac[2], HEX);
USE_SERIAL.print(":");
USE_SERIAL.print(mac[1], HEX);
USE_SERIAL.print(":");
USE_SERIAL.println(mac[0], HEX);
}
void printCurrentNet() {
// print the SSID of the network you're attached to:
USE_SERIAL.print("SSID: ");
USE_SERIAL.println(WiFi.SSID());
// print the received signal strength:
long rssi = WiFi.RSSI();
USE_SERIAL.print("signal strength (RSSI):");
USE_SERIAL.println(rssi);
}
void gettemperatureDS() {
byte i;
byte present = 0;
byte type_s;
byte data[12];
byte addr[8];
ds.reset_search(); // Reset search
delay(250);
if (!ds.search(addr)) {
if (Debug) {
USE_SERIAL.println("No addresses.");
}
webString = webString + " NNN";
return;
}
if (Debug) {
USE_SERIAL.print("ROM =");
for (i = 0; i < 8; i++) {
USE_SERIAL.write(' ');
USE_SERIAL.print(addr[i], HEX);
}
}
if (OneWire::crc8(addr, 7) != addr[7]) {
USE_SERIAL.println("CRC is not valid!");
webString = webString + " NNN";
return;
}
// USE_SERIAL.println();
// the first ROM byte indicates which chip
switch (addr[0]) {
case 0x10:
if (Debug) {
USE_SERIAL.println(" Chip = DS18S20"); // or old DS1820
}
type_s = 1;
break;
case 0x28:
if (Debug) {
USE_SERIAL.println(" Chip = DS18B20");
}
type_s = 0;
break;
case 0x22:
USE_SERIAL.println(" Chip = DS1822");
type_s = 0;
break;
default:
USE_SERIAL.println("Device is not a DS18x20 family device.");
webString = webString + " NNN";
return;
}
ds.reset();
ds.select(addr);
ds.write(0x44, 1); // start conversion, with parasite power on at the end
delay(1000); // maybe 750ms is enough, maybe not
// we might do a ds.depower() here, but the reset will take care of it.
present = ds.reset();
ds.select(addr);
ds.write(0xBE); // Read Scratchpad
if (Debug) {
USE_SERIAL.print(" Data = ");
USE_SERIAL.print(present, HEX);
USE_SERIAL.print(" ");
}
for (i = 0; i < 9; i++) { // we need 9 bytes
data[i] = ds.read();
if (Debug) {
USE_SERIAL.print(data[i], HEX);
USE_SERIAL.print(" ");
}
}
if (Debug) {
USE_SERIAL.print(" CRC=");
USE_SERIAL.print(OneWire::crc8(data, 8), HEX);
USE_SERIAL.println();
}
// Convert the data to actual temperature
// because the result is a 16 bit signed integer, it should
// be stored to an "int16_t" type, which is always 16 bits
// even when compiled on a 32 bit processor.
int16_t raw = (data[1] << 8) | data[0];
if (type_s) {
raw = raw << 3; // 9 bit resolution default
if (data[7] == 0x10) {
// "count remain" gives full 12 bit resolution
raw = (raw & 0xFFF0) + 12 - data[6];
}
} else {
byte cfg = (data[4] & 0x60);
// at lower res, the low bits are undefined, so let's zero them
if (cfg == 0x00)
raw = raw & ~7; // 9 bit resolution, 93.75 ms
else if (cfg == 0x20)
raw = raw & ~3; // 10 bit res, 187.5 ms
else if (cfg == 0x40)
raw = raw & ~1; // 11 bit res, 375 ms
//// default is 12 bit resolution, 750 ms conversion time
}
celsiusDS = (float) raw / 16.0;
fahrenheitDS = celsiusDS * 1.8 + 32.0;
if (Debug) {
USE_SERIAL.print(" Temperature = ");
USE_SERIAL.print(celsiusDS);
USE_SERIAL.print(" Celsius, ");
USE_SERIAL.print(fahrenheitDS);
USE_SERIAL.println(" Fahrenheit");
}
webString = webString + " " + String(celsiusDS);
}
void getDHT() {
// Reading temperature or humidity takes about 250 milliseconds!
// Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
h = dht.readHumidity();
t = dht.readTemperature();
if (isnan(h) || isnan(t)) {
MisreadNr++;
USE_SERIAL.println(" Failed to read from DHT sensor! -- Misreadnr : " + String(MisreadNr));
if (MisreadNr < 16) { // Use last value? or is misreading happend more then 15 times?
h = Oldh; // Use old humidity values
t = Oldt; // Use old temp value
} else { // Misreading more then once so print error string
webString = webString + " NNN NNN";
return;
}
} else {
MisreadNr = 0;
}
if (Debug) {
USE_SERIAL.print("Humidity: ");
USE_SERIAL.print(h);
USE_SERIAL.print(" %\t");
USE_SERIAL.print("Temperature: ");
USE_SERIAL.print(t);
USE_SERIAL.println(" *C ");
}
webString = webString + " " + String(t) + " " + String(h);
Oldh = h; // Save current value for one misreading workaround for misreading
Oldt = t; // Save current value for one misreading
return;
}
void GetBMP180() {
if (!bmp.begin()) {
USE_SERIAL.println(
"Could not find a valid BMP180 sensor, check wiring!");
NoBMP = true;
} else {
NoBMP = false;
}
if (!NoBMP) {
Temp = bmp.readTemperature();
if (Debug) {
USE_SERIAL.print("Temperature = ");
USE_SERIAL.print(Temp);
USE_SERIAL.println(" *C");
}
Bar = float(bmp.readPressure()) / 100;
if (Debug) {
USE_SERIAL.print("Pressure = ");
USE_SERIAL.print(Bar);
USE_SERIAL.println(" hPa");
}
// Calculate altitude assuming 'standard' barometric
// pressure of 1013.25 millibar = 101325 Pascal
Alt = bmp.readAltitude();
if (Debug) {
USE_SERIAL.print("Altitude = ");
USE_SERIAL.print(Alt);
USE_SERIAL.println(" meters");
}
BarS = float(bmp.readSealevelPressure()) / 100;
if (Debug) {
USE_SERIAL.print("Pressure at sealevel (calculated) = ");
USE_SERIAL.print(BarS);
USE_SERIAL.println(" hPa");
}
// you can get a more precise measurement of altitude
// if you know the current sea level pressure which will
// vary with weather and such. If it is 1015 millibars
// that is equal to 101500 Pascals.
AltS = bmp.readAltitude(BarS * 100);
if (Debug) {
USE_SERIAL.print("Real altitude = ");
USE_SERIAL.print(AltS);
USE_SERIAL.println(" meters");
USE_SERIAL.println();
}
webString = webString + " " + String(Temp) + " " + String(BarS);
} else {
webString = webString + " NNN NNN";
}
}
/*-------------------------------------------------
*
* SETUP
*
-----------------------------------------------------*/
void setup() {
pinMode(BUILTIN_LED, OUTPUT);
USE_SERIAL.begin(115200); //init Serial port for print strings
USE_SERIAL.setDebugOutput(true);
USE_SERIAL.println();
USE_SERIAL.println(NodeID);
USE_SERIAL.print("Versie : ");
USE_SERIAL.println(versie);
Wire.begin(SDAPIN, SCLPIN); //sda, scl
Wire.begin();
display.begin(SSD1306_SWITCHCAPVCC, 0x3c, false);
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextWrap(false);
//display.setRotation(ROTATION_90);
display.setCursor(0, 0);
display.println(NodeID + " " + versieK);
display.display();
// display.clearDisplay();
for (uint8_t t = 4; t > 0; t--) {
USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
USE_SERIAL.flush();
delay(1000);
}
WiFiStart(); // Start WiFi
if (!bmp.begin()) { // Control on BMP180 connected
USE_SERIAL.println(
"Could not find a valid BMP085 sensor, check wiring!");
NoBMP = true;
}
dht.begin(); // Start DHT reading
// dht.setup(2); // data pin 2
}
void loop() {
unsigned long currentMillis = millis();
// static unsigned long last = 0;
webSocket.loop();
if ((unsigned long) (currentMillis - previousMillis) >= interval) { // unsigned long is om millis() door 0 op te vangen
previousMillis = currentMillis;
if (Debug) {
USE_SERIAL.print("Tijd = ");
USE_SERIAL.println(currentMillis);
printCurrentNet();
printWifiData();
}
webString = NodeID;
gettemperatureDS(); // read sensor DS18b20
getDHT(); // read DHT sensor
GetBMP180(); // read BMP sensor
webString = webString + " " + WiFi.RSSI(); // Voeg signaalsterkte toe
USE_SERIAL.print(CurLoop);
USE_SERIAL.print(" : ");
USE_SERIAL.println(webString); // Print the final Webstring NNN means not readed value or not connected sensor
display.clearDisplay(); // Clear OLED display
display.setCursor(0, 0); // Put cursor to lft upper corner to start at OLED display
//display.print(NodeID + "-" + versieK + " (" + CurLoop + ")");
display.print(NodeID + "-" + versieK + " ");
if (MisreadNr > 0){
display.setTextColor(BLACK, WHITE); // 'inverted' text
} else {
display.setTextColor(WHITE); // 'Normal' text
}
display.println(" (" + String(CurLoop) + ") ");
display.setTextColor(WHITE);
display.drawFastHLine(0, 11, 124, WHITE);
display.setCursor(0, 16);
display.println("DS18b20= " + String(celsiusDS) + " C ");
display.setCursor(0, 26);
display.println("DHT22 = " + String(t) + " C");
display.setCursor(0, 36);
display.println("BMP180 = " + String(Temp) + " C");
display.setCursor(0, 46);
display.println("Hum = " + String(h) + " %");
display.setCursor(0, 56);
display.println("Bar = " + String(BarS) + " hPa");
// display.drawFastHLine(0, 63, 124, WHITE);
display.display();
// webstring content : NodeId ds18b20_temp dht_temp dht_humidity BMP180_temp BMP180_sealevelpressure
if (CurLoop >= LoopsNum) {
webSocket.sendTXT(webString);
CurLoop = 0;
if (ledState == LOW) {
ledState = HIGH; // Note that this switches the LED *off*
if (Debug) {
USE_SERIAL.println("LED uit");
USE_SERIAL.println("---------------------------");
}
digitalWrite(BUILTIN_LED, ledState);
} else {
ledState = LOW; // Note that this switches the LED *on*
if (Debug) {
USE_SERIAL.println("LED aan");
USE_SERIAL.println("---------------------------");
}
digitalWrite(BUILTIN_LED, ledState);
}
}
CurLoop += 1;
}
//////////////////////////////
// check if WLAN is connected
//////////////////////////////
if (WiFi.status() != WL_CONNECTED) {
WiFiStart();
}
server.handleClient(); // Handle webserver
}
The webserver code on the RPI:
Code: Select all
#!/usr/bin/env python
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket
import time
import datetime
import shutil
import MySQLdb as mdb
import rrdtool
TestVar = ""
from tornado.options import define, options
define("port", default=222, help="run on the given port", type=int)
class WebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
print 'new connection from : %s' % self.request.remote_ip
self.write_message("connected")
def on_message(self, message):
ts = time.time()
st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') # Lees de tijd in
# mijndatum = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d') # Lees de datum en tijd apart in voor Mysql in Mysql formaat
# mijntijd = datetime.datetime.fromtimestamp(ts).strftime('%H:%M:%S')
# print 'message received : %s from ip %s' % (message, self.request.remote_ip)
TestVar = self.request.remote_ip + " " + st + " " + message
self.write_message('message received : %s' % message)
# print TestVar
woord = []
TestVar = TestVar.strip('\r\n') # Strip eol van de string
woord = TestVar.split() # Split regel in woorden
tel = 0;
for x in woord:
if (x == "NNN"): woord[tel] = "0";
tel = tel +1;
# print "woord[3][:7] = ", woord[3][:7];
if woord[3][:7] == "NodeMCU": # Test of de gestuurde string van een NodeMCU is
# file openen
try:
fo = open("/home/ewh/NodeMCULog.txt", "a+")
except:
sys.exit ("Fout bij het openen van de file NodeMCULoglog. Programma afgebroken")
# print "open file"
fo.write (TestVar),
fo.write ('\n')
fo.close();
try:
with con:
cur = con.cursor()
cur.execute("INSERT INTO " + woord[3] + "(mydate, mytime, temp1, temp2, temp3, hum, bardruk)""VALUES( %s,%s,%s,%s,%s,%s,%s)",\
(woord[1], woord[2], woord[4], woord[5], woord[7], woord[6], woord[8]))
con.commit()
except mdb.Error, e:
if con:
con.rollback()
print "Error %d: %s" % (e.args[0],e.args[1])
sys.exit(1)
if woord[3] == "NodeMCU01":
ret = rrdtool.update ("/var/www/rrdtemp1/temp.rrd", "N:%s:%s:%s:%s:%s" % (str(woord[4]), str(woord[5]), str(woord[7]), str(woord[6]), str(woord[8])))
# print "woord : ", str(woord[4])," ", str(woord[5]), " ",str(woord[7]), " ",str(woord[6]), " ",str(woord[8])
else: # Gestuurde string komt van een niet gedefinieerde Node, sla in een errorfile op!!
# file openen
try:
fo = open("/home/ewh/NodeMCUerrorLog.txt", "a+")
except:
sys.exit ("Fout bij het openen van de file NodeMCUerrorLoglog. Programma afgebroken")
# print "open file"
fo.write (TestVar),
fo.write ('\n')
fo.close();
def on_close(self):
print 'connection closed from : %s' % self.request.remote_ip
if __name__ == "__main__":
con = mdb.connect('127.0.0.1', 'pi', 'raspberry', 'tempdb');
tornado.options.parse_command_line()
app = tornado.web.Application(
handlers=[
(r"/", WebSocketHandler)
]
)
httpServer = tornado.httpserver.HTTPServer(app)
httpServer.listen(options.port)
print "Listening on port:", options.port
tornado.ioloop.IOLoop.instance().start()
con.close()