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
/*
* 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:
#!/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()