Leave your P1 out in the cold and now with P2
This project requires several pieces and a lot of cross knowledge.
First off is the objective: To display environmental data on a web page.
This sounds simple enough, grab some data and place on a page.
Items used for this project:
Propeller 1
Power source 7.4 volts
Adafruits INA260 power sensor
Adafruits TSL2591 light sensor
DS3231 real time clock module
Parallax BME680 environmental sensor
Parallax ESP8266 Wifi module
The web page will be placed on the Wifi module and the P1 will monitor for request to send data to the web page which will intern update the weather data on the page. Easy Peasy
I will be using two languages for this project three if you count HTML. On the P1 side I will be using C and on the web page side I will be using javascript.
Once the request comes to the P1 it most grab the data and then format it so that the javascript can process the data for display. The simplest way to do that is use JSON. No not the guy that killed a bunch of people in horror. This one is used to encode data so that it can be used by other programs.
I wrote a simple JSON library that allows you to specify the data and the data value and encodes it.
This is what the json data looks like being sent to the web page for processing:
{"time":1711195290, "voltage":7423, "current":58, "temperature":2058, "humidity":3602, "pressure":2958, "light":34}
This is the web page that I will place on the Wifi module.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>Weather</title> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <script type="text/javascript"> function testing() { console.log("This is a test"); } function gettemp() { const req = new XMLHttpRequest(); req.addEventListener("error", err()); req.open("GET", "/weather", true); req.send(); req.responseType = "text"; req.onload = function() { if (req.readyState === req.DONE) { if (req.status === 200) { console.log(req.response); const data = JSON.parse(req.response); document.getElementById('temperature').innerHTML = data.temperature/100; document.getElementById('humidity').innerHTML = data.humidity/100; document.getElementById('pressure').innerHTML = data.pressure/100; document.getElementById('light').innerHTML = data.light; document.getElementById('voltage').innerHTML = data.voltage/1000; document.getElementById('current').innerHTML = data.current; document.getElementById('dt').innerHTML = convertTimestamp(data.time); } } } } function err() { document.getElementById('dt').innerHTML = "No Response"; } function xupdate() { document.getElementById('temperature').innerHTML = 'N/A'; document.getElementById('humidity').innerHTML = 'N/A'; document.getElementById('pressure').innerHTML = 'N/A'; document.getElementById('light').innerHTML = 'N/A'; document.getElementById('voltage').innerHTML = 'N/A'; document.getElementById('current').innerHTML = 'N/A'; document.getElementById('dt').innerHTML = 'N/A'; } function convertTimestamp(timestamp) { var d = new Date(timestamp * 1000); // Convert the passed timestamp to milliseconds var yyyy = d.getFullYear(); var mm = ('0' + (d.getMonth() + 1)).slice(-2); // Months are zero-based. Add leading 0. var dd = ('0' + d.getDate()).slice(-2); // Add leading 0. var hh = d.getHours(); var h = hh; var min = ('0' + d.getMinutes()).slice(-2); // Add leading 0. var ampm = 'AM'; if (hh > 12) { h = hh - 12; ampm = 'PM'; } else if (hh === 12) { h = 12; ampm = 'PM'; } else if (hh === 0) { h = 12; } // Format the date and time var formattedTime = yyyy + '-' + mm + '-' + dd + ', ' + h + ':' + min + ' ' + ampm; return formattedTime; } </script> </head> <body onload="xupdate()"> <h1>Weather Data</h1> <p>Date:</p><p id="dt"></p> <table border="0" cellpadding="0" cellspacing="1"> <tbody> <tr> <td width="96">Temperature</td> <td id="temperature" width="32"></td> <td width="32">C</td> </tr> <tr> <td width="96">Humidity</td> <td id="humidity" width="32"></td> <td width="32">%</td> </tr> <tr> <td width="96">Pressure</td> <td id="pressure" width="32"></td> <td width="32"></td> </tr> <tr> <td width="96">Light</td> <td id="light" width="32"></td> <td width="32"></td> </tr> <tr> <td width="96">Voltage</td> <td id="voltage" width="32"></td> <td width="32">V</td> </tr> <tr> <td width="96">Current</td> <td id="current" width="32"></td> <td width="32">ma</td> </tr> <tr> <td width="96"></td> <td width="32"></td> <td width="32"></td> </tr> <tr> <td width="96"><input type="button" name="doIt" onclick="gettemp();" value="Update"></td> <td width="32"></td> <td width="32"></td> </tr> </tbody> </table> </body> </html>
This is the C program that uses several libraries that just fit on the P1.
#include "esp8266.h" #include "simpletools.h" #include <sys/time.h> #include "bme68x.h" #include "ina260.h" #include "DS3231.h" #include "tsl2591.h" #include "JSON.h" #define LED1 0 #define LED2 1 #define INACLK 15 #define INADTA 14 #define WRx 31 #define WTx 30 #define BMECLK 18 #define BMEDTA 17 #define DSCLK 7 #define DSDTA 6 #define TSLCLK 7 #define TSLDTA 6 uint8_t BME68xRead(uint8_t, uint8_t *, uint16_t len, void *); uint8_t BME68xWrite(uint8_t, uint8_t *, uint16_t len, void *); void BME68xWait(uint32_t, void *); void StartBME(void); void StartMonitor(void); void StartClock(void); void StartLight(void); int GetEnvironment(float *, float *, float *); fdserial *esp; int i; char *x; int handle; char Buffer[1024]; char Convert[10]; char fmt[] = "%4.2f"; time_t tm; i2c *Bme; char dev_addr; struct bme68x_dev gas_sensor; struct bme68x_conf conf; struct bme68x_heatr_conf heatr_conf; struct bme68x_data data[3]; int main() { float t, h, p; int T, H, P; int v, c, l, I; memset(Buffer, 0, sizeof(Buffer)); StartMonitor(); StartBME(); StartClock(); StartLight(); esp = esp8266_open(WRx,WTx); x = esp8266_check("station-ipaddr"); dprint(esp, "IP: %s\n", x); x = esp8266_check("cmd-events"); dprint(esp, "events: %s\n", x); handle = esp8266_listen(HTTP, "/weather"); dprint(esp, "Handle: %d\n", handle); GetEnvironment(&t, &h, &p); while(1) { i = esp8266_poll(0x1F); if (i > 0) { dprint(esp, "Request\n"); i = esp8266_results(); GetEnvironment(&t, &h, &p); v = INA260_getVoltage(); c = INA260_getCurrent(); tm = DS3231_SetDateTime(); TSL2591_Read(&l, &I); T = t * 100; H = h * 100; P = p * 100; Buffer[0] = 0; json_init(Buffer); sprinti(Convert, "%d", tm); json_putDec("time", Convert); sprinti(Convert, "%d", v); json_putDec("voltage", Convert); sprinti(Convert, "%d", c); json_putDec("current", Convert); sprinti(Convert, "%d", T); json_putDec("temperature", Convert); sprinti(Convert, "%d", H); json_putDec("humidity", Convert); sprinti(Convert, "%d", P); json_putDec("pressure", Convert); sprinti(Convert, "%d", l); json_putDec("light", Convert); esp8266_reply(i, Buffer); } pause(1000); } } void StartMonitor() { int i; i = INA260_open(INACLK, INADTA); if (i =! 0x5449) { #ifdef DEBUG printi("Monitor not found\n"); #endif while (1) pause(1000); } INA260_configAveraging(7); INA260_configCurrent(7); INA260_configVoltage(7); } void StartBME() { int8_t rslt = BME68X_OK; float t, h, p; Bme = i2c_newbus(BMECLK, BMEDTA, 0); dev_addr = BME68X_I2C_ADDR_HIGH; gas_sensor.variant_id = BME68X_VARIANT_GAS_LOW; gas_sensor.intf = BME68X_I2C_INTF; gas_sensor.read = BME68xRead; gas_sensor.write = BME68xWrite; gas_sensor.delay_us = BME68xWait; gas_sensor.amb_temp = 25; gas_sensor.intf_ptr = &dev_addr; rslt = bme68x_init(&gas_sensor); if (rslt != BME68X_OK) { #ifdef DEBUG printi("BME Sensor not found\n"); #endif while (1) pause(1000); } /* Set the temperature, pressure and humidity settings */ conf.os_hum = BME68X_OS_16X; conf.os_pres = BME68X_OS_1X; conf.os_temp = BME68X_OS_2X; conf.filter = BME68X_FILTER_OFF; conf.odr = BME68X_ODR_NONE; rslt = bme68x_set_conf(&conf, &gas_sensor); if (rslt != BME68X_OK) { high(LED1); while (1) pause(1000); } /* Set the remaining gas sensor settings and link the heating profile */ heatr_conf.enable = BME68X_ENABLE; /* Create a ramp heat waveform in 3 steps */ heatr_conf.heatr_temp = 300; /* degree Celsius */ heatr_conf.heatr_dur = 100; /* milliseconds */ heatr_conf.heatr_temp_prof = NULL; heatr_conf.heatr_dur_prof = NULL; heatr_conf.profile_len = 0; rslt = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &heatr_conf, &gas_sensor); /* kick off a reading */ GetEnvironment(&t, &h, &p); } void StartLight() { int i; i = TSL2591_Init(TSLCLK, TSLDTA); if (i != 0) { #ifdef DEBUG printi("Light Not Found\n"); #endif while (1) pause(1000); } i = TSL2591_GetStatus(); } int GetEnvironment(float *temp, float *humidity, float *pressure) { int rslt; int delay; int f; rslt = bme68x_set_op_mode(BME68X_FORCED_MODE, &gas_sensor); delay = bme68x_get_meas_dur(BME68X_FORCED_MODE, &conf, &gas_sensor) + (heatr_conf.heatr_dur * 1000); gas_sensor.delay_us(delay, gas_sensor.intf_ptr); rslt = bme68x_get_data(BME68X_FORCED_MODE, &data, &f, &gas_sensor); *temp = data[0].temperature; *humidity = data[0].humidity; *pressure = data[0].pressure/3386.4; } void StartClock() { DS3231_Open(DSCLK, DSDTA); DS3231_SetDateTime(); } uint8_t BME68xRead(uint8_t reg_addr, uint8_t *data, uint16_t len, void *intf_ptr) { int i; uint8_t dev_addr = *(uint8_t*)intf_ptr; i = i2c_in(Bme, dev_addr, reg_addr, 1, data, len); if (i > 0) return 0; else return -1; } uint8_t BME68xWrite(uint8_t reg_addr, uint8_t *data, uint16_t len, void *intf_ptr) { int i; uint8_t dev_addr = *(uint8_t*)intf_ptr; i = i2c_out(Bme, dev_addr, reg_addr, 1, data, len); if (i > 0) return 0; else return -1; } void BME68xWait(uint32_t period, void *intf_ptr) { usleep(period); }
This is a picture of the hardware setup
This is a picture of the web page with returned data
By using json data the javascript was able to process the data natively.
By placing the P1 in a plastic bag I am able to see the weather outside from the inside.
Mike
Comments
Very nice. - And thankfully not too cold yet either
Wait, This project is not done!
I need to collect this data on an hourly bases and display the information.
For this I used another language, Python, and a Raspberry Pi Zero:
They are about the same size but one uses a lot more power.
First I need a python script to get the data off the P1 sitting in the cold:
Wow, that was easy. grab the JSON data off the P1 and save it in a file.
Then using crontab have it update every hour.
Now how to display all that data:
Right, let's use some dynamic graphing tool and let it rip.
On Raspberry Pi I installed Apache2 and configured it to run CGI Python scripts to grab the last 24 records from the Weather file and display the results.
To do that the Python code reads the JSON records and converts them to an array of values which intern is convert back to a JSON object to be used by the charting Javascript.
On the fly it also normalized the data for charting.
This is a sample of the JSON object:
Ugly looking isn't it.
I didn't use the time values, but they are included.
To see it for yourself I put my Zero on the web for a little while anyway: Zero Live
Mike
you can get nice and easy graphs by putting the data into an influxdb and showing it with grafana both of which can run on the zero ( I had this up and running for my home automation, admittedly on on rpi3, but it should work on a zero aswell)
Back again with another update.
I thought it was not fair to have the P1 out in the cold so I decided that the P2 should also be there.
The setup was almost identical except I made some hardware changes.
I also had to write a driver for the P2 RTC board which too a little time.
The weather program procedure wise is about the same.
I like coding in Visual Studio as it looks things up for me so I can tell if I'm making a mistake.
As it turns out the P2 running at lower speed, 20Mhz, uses less power than the P1.
These two projects help me squash some bugs in my libraries that went unchecked.
Mike