Eigenschappen
Meet de temperatuur via twee digitale sensoren en geeft deze weer op het display van de ESP32, je kunt ook een ESP32 gebruiken zonder display. Het display kan via een aanraaktoets worden gewisseld tussen:
1) De ingaande en uitgaande temperatuur met een grafiek die een indicatie geeft van het verschil tussen de twee temperaturen of de wifiverbinding actief is en wanneer de temperatuur wordt uitgelezen. De temperatuur wordt met een interval van 1 seconden gemeten.
2) Het verschilscherm geef het verschil weer tussen de twee temperaturen en wanneer de meting plaats vindt.
3) Info scherm: geeft de softwareversie weer, de gebruikt Service Set Identifier SSID (de naam van Wifi-netwerk) en het IP-adres dat de Eps32DeltTemp gebruikt.
4) Tot slot: het scherm kan ook op zwart worden gezet.
De informatie wordt optioneel verzonden via wifi UDP-berichten die door elke ander applicatie/programma gelezen kan worden op het lokale netwerk. Het Wifi IP-adres wordt via DHCP opgehaald iets wat de meeste thuis routers standaard doen.
Benodigde hardware
De lijst die ik hier geef werkt en ik heb geen relatie met de genoemde leveranciers.
• 1 x Heltec EPS32 met oled display (10,55): link
• 2 x DS18B20 digitale temperatuur sensor (~€ 1,00): link
• 1 x Weerstand van 4K7: (€ 1,98) link
• Behuizing (optioneel) (€ 1,86): link
• Een USB – Naar micro usb kabel voor het programmeren en of de 5V voeding.
• Een Micro USB 5V voeding van minimaal 0.7 Ampère, een Raspberry Pi voeding is uitstekend. (Ongeveer € 4)
• Experimenteer print, een paar euro bijvoorbeeld link ( ongeveer € 7).
• Iets wat als tiptoets knop kan werken. Ik gebruik een 4mm schroefje.
Totaal ongeveer: € 30 euro afhankelijk van je voorkeur.
Uiteraard een soldeerbout, soldeertin en e.d. om de boel in elkaar te zetten.
Aansluitschema
Benodigde software
De ESP32 is eenvoudig te programmeren via de Arduino IDE als je deze nog niet hebt geïnstalleerd volg dan deze handleiding van randomnerdtutorials.com. Deze handleiding legt tevens uit hoe je de ESP32 uitbreiding moet toevoegen die zijn standaard niet geïnstalleerd.
De code maakt gebruik van een extra softwarebibliotheken die eenvoudig via de Arduino IDE te installeren zijn. Deze kun je via de IDE Sketch -> Include Libary -> Manage Libraries toevoegen.
- OneWire
- U8G2
- DallasTemperature
Laad onderstaande code in de editor en pas eventueel de WIFI instellingen aan als je daar gebruik van wil maken en compileer deze door linksboven op verify te klikken. Als dat goed gaat kun je de ESP32 aansluiten via de USB-kabel en de code uploaden naar de ESP32. Om de code te kunnen testen moeten we de temperatuur sensors worden aangesloten zijn.
Code: Selecteer alles
#include <U8g2lib.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <WiFi.h>
#include <WiFiUdp.h>
/* note, the bat(tery) led rapid blinking is a hardware "feature" of the heltec board and can not be disabled by software */
/* user setting, change these to your needs. */
/*********************************************/
/* leave SSID empty if you don't need the UDP / WIFI messages *ssid = "" */
const char *ssid = ""; // your SSID.
const char *pwd = ""; // your WPA WIFI password.
const uint8_t udp_send_interval = 45; // interval when an udp message will be send in 0.1 secs interval 45 = ~5 secs.
// use a value between 1 and a maximum of 599.
const float temp_in_adjustmend = 0.36; // test en adjust these to values to compensate for differences
const float temp_out_adjustmend = 1.4; // in the readout of the temp sensors and general offset error.
/***************** end of user settings ******/
#define TOUCH_PIN T7 // connected to pin 27
#define ONE_WIRE_BUS 13 // DS18B20 on GPIO 13.
#define STR_BUF_SIZE 32
#define VERSION "versie 1.0" // Software version.
#define TEMPERATURE_PRECISION 12 // set temp precision (9-12).
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
OneWire oneWire(ONE_WIRE_BUS); // setup OneWire devices.
DallasTemperature tempSensors(&oneWire); // tempSensor data.
DeviceAddress tempDeviceAddress; // temp adress for devices.
WiFiUDP udp; // UDP sender.
int numberOfDevices = 0; // number of temperature devices found.
char strbuf[STR_BUF_SIZE]; // buffer for display and serial print.
float temp_in;
float temp_out;
float temp_in_avg;
float temp_out_avg;
uint8_t temp_vdelta_arr_graphic[DISPLAY_WIDTH];
int touch_value = 300;
boolean WIFIconnected = false;
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ 16, /* clock=*/15, /* data=*/4); // (rotation, [reset [, clock, data]])
// IP address to send UDP data to.
const char *udpAddress = "255.255.255.255"; // udp broadcast address for the local lan only
const int udpPort = 30721; // udp port number to listen to as a client.
void setup() {
Serial.begin(115200);
u8g2.begin();
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB12_tf);
u8g2.drawStr(20,40,"ZTATZ.NL");
u8g2.sendBuffer();
delay(3000);
// load temperature sensors.
tempSensors.begin();
// check if we can find the two temp sensors.
u8g2.clearBuffer();
while ( setTempPrecision() < 2 ) {
tempSensors.begin();
u8g2.drawStr(1,12,"wacht op");
u8g2.drawStr(1,26,"temperatuur");
u8g2.drawStr(1,40,"sensors.");
u8g2.sendBuffer();
delay(500);
}
// make sure we have some temperature values.
readTemperatures();
if ( String(ssid).length() > 0 ) {
// try to connect to wifi.
connectToWiFi(ssid, pwd);
// Wait for connection
for (uint8_t i = 0; i < 60; i++) {
if (WiFi.status() == WL_CONNECTED ) {
break; // we have a network connection
}
u8g2.clearBuffer();
u8g2.drawStr(1,12,"wacht op");
u8g2.drawStr(1,27,"wifi");
u8g2.drawStr(1,41,"verbinding.");
String str = "seconden: " + String( i );
str.toCharArray(strbuf, STR_BUF_SIZE);
u8g2.drawStr(1,57,strbuf);
u8g2.sendBuffer();
delay(1000); // try every 1 secs
}
}
}
void loop()
{
static uint8_t display_select_counter = 0;
static uint8_t pseudo_secs = 0;
static uint8_t touch_counter = 0;
static uint8_t udp_send_counter = 0;
static bool wifi_retry_is_on = false;
// pseudo timer.
pseudo_secs++;
if ( pseudo_secs > 599 ) {pseudo_secs = 0; } // reset secs to 0
// read temperature values from sensors and update data buffers.
// every second
if ( (pseudo_secs%10 ) == 0 ) {
readTemperatures();
fillGraphicBufferValues();
calcAvgTemperature( temp_in, temp_out );
showDisplay(display_select_counter);
}
// Send UDP broadcast messages.
if (WIFIconnected) {
udp_send_counter++;
if ( udp_send_counter > udp_send_interval ) {
udpSendMessage( makeJsonMessage() );
udp_send_counter=0;
}
}
// touch selection.
touch_value = touchRead(TOUCH_PIN);
if (touch_value < 50 ) {
touch_counter++;
if ( touch_counter > 4 ) { // must held the touch contact to filter out noise.
touch_counter = 0;
display_select_counter++;
if ( display_select_counter > 3 ) { display_select_counter = 0; }
showDisplay(display_select_counter); // to get quick feedback on pressing touch "button".
}
} else {
touch_counter = 0; //reset the touch counter,
}
delay(100); // 0.1 sec delay.
}
void connectToWiFi(const char * ssid, const char * pwd){
Serial.println("Connecting to WiFi network: " + String(ssid));
// delete old config
WiFi.disconnect(true);
//register event handler
WiFi.onEvent(WiFiEvent);
//Initiate connection
WiFi.begin(ssid, pwd);
}
//wifi event handler
void WiFiEvent(WiFiEvent_t event){
Serial.print("WiFiEvent_t: ");
Serial.println(event);
switch(event) {
case SYSTEM_EVENT_STA_GOT_IP:
//When connected set
Serial.print("WiFi verbonden! IP address: ");
Serial.println(WiFi.localIP());
//initializes the UDP state
//This initializes the transfer buffer
udp.begin(WiFi.localIP(),udpPort);
WIFIconnected = true;
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
Serial.println("WiFi verbinding veloren.");
WIFIconnected = false;
WiFi.disconnect(true);
WiFi.begin(ssid, pwd);
break;
}
}
// function select display mode
void showDisplay(uint8_t index) {
switch(index) {
case 0:
showTempInfo();
break;
case 1:
showDeltaTempInfo();
break;
case 2:
showInitInfo();
break;
case 3:
// blank screen
u8g2.clearBuffer();
u8g2.sendBuffer();
break;
}
}
// function to show temperature on display.
void showDeltaTempInfo() {
static bool temp_icon;
floatTempFormater(temp_in_avg - temp_out_avg).toCharArray(strbuf, STR_BUF_SIZE);
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_fub30_tf);
u8g2.drawStr( 5, 35, strbuf);
u8g2.drawGlyph( 85, 35, 176 );
u8g2.drawStr( 95, 35, "C" );
u8g2.setFont(u8g2_font_ncenB18_tf);
u8g2.drawStr( 8, 60, "verschil" );
// activity icon toggle.
u8g2.setFont(u8g2_font_open_iconic_gui_2x_t);
if (temp_icon == true ) {
u8g2.drawGlyph(112,60,66);
temp_icon = false;
} else {
u8g2.drawGlyph(112,60,67);
temp_icon = true;
}
u8g2.sendBuffer();
}
void showInitInfo() {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_courB10_tf);
String str = VERSION;
str.toCharArray(strbuf, STR_BUF_SIZE);
u8g2.drawStr(1,15,strbuf);
str = "SSID:" + String( ssid );
str.toCharArray(strbuf, STR_BUF_SIZE);
u8g2.drawStr(1,30,strbuf);
str = IpAddress2String( WiFi.localIP() );
str.toCharArray(strbuf, STR_BUF_SIZE);
u8g2.drawStr(1,45,strbuf);
u8g2.sendBuffer();
}
// function to show temperature on display.
void showTempInfo() {
static bool temp_icon;
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB18_tf);
u8g2.drawStr( 0,18,"in" );
u8g2.drawStr( 0,40,"uit" );
floatTempFormater(temp_in_avg).toCharArray(strbuf, STR_BUF_SIZE);
u8g2.drawStr(38,18,strbuf);
floatTempFormater(temp_out_avg).toCharArray(strbuf, STR_BUF_SIZE);
u8g2.drawStr(38,40,strbuf);
u8g2.setFont(u8g2_font_ncenB14_tf);
u8g2.drawStr( 97,16,"C" );
u8g2.drawStr( 97,38,"C" );
u8g2.drawGlyph( 90,16,176 );
u8g2.drawGlyph( 90,38,176 );
for (uint8_t i=0; i <DISPLAY_WIDTH; i++ ) {
u8g2.drawLine(i,64, i, DISPLAY_HEIGHT - temp_vdelta_arr_graphic[i] );
}
// activity icon toggle.
//u8g2.setFont(u8g2_font_siji_t_6x10);
u8g2.setFont(u8g2_font_open_iconic_all_2x_t);
if (temp_icon == true ) {
u8g2.drawGlyph(112,20,141);
//u8g2.drawGlyph(111,20,57551);
temp_icon = false;
} else {
//u8g2.drawGlyph(113,20,57373);
temp_icon = true;
}
if (WIFIconnected) {
u8g2.setFont(u8g2_font_open_iconic_all_2x_t);
u8g2.drawGlyph( 112,40,247 );
}
u8g2.sendBuffer();
}
// function IPAdress conversion to string
String IpAddress2String(const IPAddress& ipAddress)
{
return String(ipAddress[0]) + String(".") +\
String(ipAddress[1]) + String(".") +\
String(ipAddress[2]) + String(".") +\
String(ipAddress[3]) ;
}
// function to format json string.
String makeJsonMessage() {
static unsigned int sequenceNumber;
char tmpbuf[8];
dtostrf( temp_in, 2, 3, tmpbuf );
String str_tmp_in = String(tmpbuf);
dtostrf( temp_out, 2, 3, tmpbuf );
String str_tmp_out = String(tmpbuf);
//{ "t_in": 23.1, "t_out":45.6, "seq":65000 }
String msg = "{\"id\":\"ztatz_dt\",\"version\":1.0,\"t_unit\":\"C\"";
msg = msg + ",\"t_in_avg\":" +
floatTempFormater(temp_in_avg) +
",\"t_out_avg\":" +
floatTempFormater(temp_out_avg) +
",\"t_in\":" +
str_tmp_in +
",\"t_out\":" +
str_tmp_out +
",\"seq\":" + sequenceNumber +
"}";
sequenceNumber++;
if ( sequenceNumber > 32768 ) { sequenceNumber = 0; } //wrap around to zero.
return msg;
}
void udpSendMessage(String msg) {
//alloc buffer space
uint8_t bufsize = msg.length()+1;
uint8_t buffer[bufsize];
msg.getBytes(buffer, bufsize);
udp.beginPacket(udpAddress, udpPort);
udp.write(buffer, bufsize-1); //skip trailing zero.
udp.endPacket();
}
// function to calculate the average values, based in AVG_TEMP_ARRAY_SIZE value
void calcAvgTemperature(float in, float out) {
#define AVG_TEMP_ARRAY_SIZE 30
static uint8_t sample_count=0;
static float temp_in_array[AVG_TEMP_ARRAY_SIZE];
static float temp_out_array[AVG_TEMP_ARRAY_SIZE];
static bool enough_data = false; // only average value when there is enough data
// shift array
for (uint8_t i = AVG_TEMP_ARRAY_SIZE-1; i > 0; i--) {
// Serial.print ("i = ");
// Serial.println(i , DEC);
temp_in_array[i] = temp_in_array[i-1];
temp_out_array[i] = temp_out_array[i-1];
}
temp_in_array[0] = in;
temp_out_array[0] = out;
sample_count++;
if ( sample_count == AVG_TEMP_ARRAY_SIZE ) {
enough_data = true;
} else {
// we don't have enough data so just send current non average value
temp_in_avg = in;
temp_out_avg = out;
}
if (enough_data == true ) { // ready to calc avg value
temp_in_avg = temp_out_avg = 0;
for (uint8_t i = 0; i < AVG_TEMP_ARRAY_SIZE; i++) {
temp_in_avg = temp_in_avg + temp_in_array[i];
temp_out_avg = temp_out_avg + temp_out_array[i];
}
temp_in_avg = temp_in_avg / AVG_TEMP_ARRAY_SIZE;
temp_out_avg = temp_out_avg / AVG_TEMP_ARRAY_SIZE;
}
/* remove comments to test and adjust offset values */
/*
Serial.print(" in =");
Serial.print(in,DEC);
Serial.print(" out =");
Serial.print(out ,DEC);
Serial.print(" delta(raw) =");
Serial.print( floatTempFormater(out-in));
Serial.print(" temp_in_avg =");
Serial.print(temp_in_avg,DEC);
Serial.print(" temp_out_avg =");
Serial.print(temp_out_avg,DEC);
Serial.print(" delta(avg) =");
Serial.println(floatTempFormater(temp_out_avg - temp_in_avg));
*/
}
// function to format float type.
String floatTempFormater(float in) {
char tmpbuf[10];
dtostrf( in, 2, 1, tmpbuf );
String str = String(tmpbuf);
if ( str.length() < 4) {
str = "0"+str;
}
return str;
}
// function to fill buffer with offset values to draw a graph.
void fillGraphicBufferValues() {
uint8_t delta_value = abs(temp_in - temp_out)* 2; //two pixels is a degree.
temp_vdelta_arr_graphic[0] = delta_value;
if ( temp_vdelta_arr_graphic[0] > 22 ) { temp_vdelta_arr_graphic[0]=22; } // cap maximum graph height.
for (uint8_t i=DISPLAY_WIDTH-1; i >0; i-- ) {
temp_vdelta_arr_graphic[i] = temp_vdelta_arr_graphic[i-1];
}
}
// function to show temperature on display.
void readTemperatures() {
/* important add or subtract values to make sure both sensors give the right temperature.
* this must be done manualy once! See top of code!
*/
tempSensors.requestTemperatures();
temp_in = tempSensors.getTempCByIndex(0)+ (temp_in_adjustmend); // error offset.
temp_out = tempSensors.getTempCByIndex(1)+ (temp_out_adjustmend); // error offset.
}
// function to print a device address
void printAddress(DeviceAddress deviceAddress)
{
for (uint8_t i = 0; i < 8; i++)
{
if (deviceAddress[i] < 16) Serial.print("0");
Serial.print(deviceAddress[i], HEX);
}
}
// function to set temp device precision.
int setTempPrecision() {
numberOfDevices = tempSensors.getDeviceCount();
if ( numberOfDevices < 1 ) {
Serial.println("Fout geen devices gevonden!");
return 0;
}
// Loop through each device, print out address
for(int i=0;i<numberOfDevices; i++)
{
// Search the wire for address
if(tempSensors.getAddress(tempDeviceAddress, i)) {
Serial.print("device gevonden ");
Serial.print(i, DEC);
Serial.print(" met adres: ");
printAddress(tempDeviceAddress);
Serial.println();
Serial.print("Resolutie gezet op ");
Serial.print(TEMPERATURE_PRECISION, DEC);
Serial.println(" bits.");
// set the resolution to TEMPERATURE_PRECISION bit (Each Dallas/Maxim device is capable of several different resolutions)
tempSensors.setResolution(tempDeviceAddress, TEMPERATURE_PRECISION);
Serial.print("Daadwerkelijke resolutie die gezet is : ");
Serial.print(tempSensors.getResolution(tempDeviceAddress), DEC);
Serial.println();
}else{
Serial.print("Spook device op adres ");
Serial.print(i, DEC);
Serial.print(" adress niet gevonden, controleer voeding en bekabeling.");
}
}
return numberOfDevices;
}
Afstellen van de temperatuur sensors.
De sensors die ik gebruikt heb voor het testen wijken soms wel 1 graad Celsius of iets meer af van de werkelijkheid. Als je het belangrijk vindt dat de juiste temperatuur wordt aangeven dan kun je de sensors ijken. Dit kan via de UTP berichten of via de seriële logging van de Arduino IDE. Als je de seriële logging wil gebruiken dan moet je de commentaar karakters verwijderen.
Code: Selecteer alles
/* remove comments to test and adjust offset values */
/*
Serial.print(" in =");
Serial.print(in,DEC);
Serial.print(" out =");
Serial.print(out ,DEC);
Serial.print(" delta(raw) =");
Serial.print( floatTempFormater(out-in));
Serial.print(" temp_in_avg =");
Serial.print(temp_in_avg,DEC);
Serial.print(" temp_out_avg =");
Serial.print(temp_out_avg,DEC);
Serial.print(" delta(avg) =");
Serial.println(floatTempFormater(temp_out_avg - temp_in_avg));
*/
Code: Selecteer alles
const float temp_in_adjustmend = 0.36;
const float temp_out_adjustmend = 1.4;
UDP berichten
De UDP berichten zijn in JSON formaat.
Code: Selecteer alles
{
"id":"ztatz_dt",
"version":1.0,
"t_unit":"C",
"t_in_avg":24.1,
"t_out_avg":22.0,
"t_in":24.298,
"t_out":22.150,
"seq":24464
}
version: versie van het bericht, in de toekomst kan het bericht uitgebreid worden.
t_unit: C is graden Celsius.
t_in_avg: de gemiddelde temperatuur in over de afgelopen 30 seconden.
t_out_avg: de gemiddelde temperatuur uit over de afgelopen 30 seconden.
t_in: de ruwe temperatuur in, wordt elke seconde gemeten.
t_uit: de ruwe temperatuur uit, wordt elke seconde gemeten.
seq: volgorde nummers van het bericht met een waarde van 1 tot 32768. Als de waarde 32769 wordt bereikt dan is het volgende nummer 1. Bij een reboot wordt altijd gestart met de waarde 1. Het doel is dat ontvangers kunnen vaststellen of ze een waarde al gehad hebben of er een gemist.
Onderstaande code geeft een simpele voorbeeld hoe je met Python de berichten kunt uitlezen.
Code: Selecteer alles
#!/usr/bin/python
import socket
import time
import datetime
# bind all IP
HOST = '0.0.0.0'
# Listen on Port
PORT = 30721
#Size of receive buffer
BUFFER_SIZE = 1024
# Create a TCP/IP socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Bind the socket to the host and port
s.bind((HOST, PORT))
while True:
# Receive BUFFER_SIZE bytes data
# data is a list with 2 elements
# first is data
#second is client address
data = s.recvfrom(BUFFER_SIZE)
if data:
#print received data
print( datetime.datetime.fromtimestamp(time.time()).strftime('%H:%M:%S') +' ' + str(data[0]) )
# Close connection
s.close()