47 Commits

Author SHA1 Message Date
Ollo
832e5def65 Show PM2.5 at diagrams, too 2021-12-18 22:37:58 +01:00
Ollo
2efa4a576c Use real data for the diagram 2021-12-18 22:16:47 +01:00
Ollo
ca8da3c608 Always start webserver 2021-12-18 21:53:29 +01:00
Ollo
2c884710c1 In standalone mode, do not allocate the memory, necessary to read the sensors 2021-12-18 21:44:49 +01:00
Ollo
a3be4f19a1 Seperate webpage for the diagram 2021-12-18 13:41:51 +01:00
Ollo
ae6c53d39b Generate the JSON without any library 2021-12-18 13:29:41 +01:00
Ollo
3224224251 Generate dynamic diagram content 2021-12-17 21:43:49 +01:00
Ollo
fc5444f8b3 Multiple parameter in one diagram 2021-12-17 20:59:34 +01:00
Ollo
5955ca1a5c New HTTP endpoints: /header and /datasets 2021-12-16 20:40:44 +01:00
Ollo
2b930a6902 Without MQTT no publish 2021-12-16 20:24:51 +01:00
Ollo
128d5a0b3b Chart example on ESP8266 2021-12-16 20:21:24 +01:00
Ollo
c26ee6342c Prepared chart example 2021-12-15 21:20:27 +01:00
Ollo
32e868b176 Reduce cycle time 2021-12-15 20:22:48 +01:00
Ollo
797a6dbe05 Reduce cycle time 2021-12-15 20:17:09 +01:00
Ollo
fc185b4bca Merged brightness configuration into standalone stream 2021-12-15 17:31:02 +01:00
Ollo
d6e34a8e75 Brightness can be configured 2021-12-15 17:22:10 +01:00
Ollo
db10de101c Reduced brightness to 50% 2021-12-15 17:02:36 +01:00
Ollo
24e6bfd6f9 Merged swapped pins for I2C sensor 2021-12-12 17:59:20 +01:00
Ollo
dadee863b8 GPIO and Arduino-Pins are never the same 2021-12-12 17:45:45 +01:00
Ollo
5ece74b0e6 Activate I2C after powering the sensor 2021-12-12 17:07:20 +01:00
Ollo
6af6a6ac76 Use red led to visualize button presses 2021-12-12 16:47:40 +01:00
Ollo
e3fe70dff2 Removed unnecessary DEFINE 2021-12-12 16:28:53 +01:00
Ollo
fd8277b702 Don't manipulate the green LED during runtime, as it is the powersource for the I2C sensor 2021-12-12 16:00:45 +01:00
Ollo
81e4f9901c Always wait for I2C 2021-12-12 15:40:35 +01:00
Ollo
5e27d3ebf4 specific message for each Bosch sensor 2021-12-12 15:28:24 +01:00
Ollo
b91317fae2 Merged from master 2021-12-12 15:19:10 +01:00
Ollo
3e89b3e040 Tweak offset 2021-12-12 14:48:26 +01:00
Ollo
e52300b792 Update Button via MQTT 2021-12-12 14:45:34 +01:00
Ollo
f3d439cbc3 Button is published on MQTT, too 2021-12-12 14:32:29 +01:00
Ollo
95b6fcef06 both BMx sensors supported 2021-12-06 21:28:12 +01:00
Ollo
1c35094cfa Wait until power is stable for BME680 2021-12-06 20:28:34 +01:00
Ollo
b2db51d9a9 Spelling improved 2021-12-06 20:18:23 +01:00
Ollo
1cf8c781eb used most commented version 2021-12-06 20:14:55 +01:00
Ollo
c39ebc8e54 Serial debugging enabled 2021-12-04 23:24:30 +01:00
Ollo
1e15d85d6f Set LED to RED, if no configuration is available 2021-12-04 23:09:54 +01:00
Ollo
afff1275da Documentation 2021-12-04 23:09:07 +01:00
Ollo
d910d0b945 More documentation 2021-12-04 18:46:17 +01:00
Ollo
41ebed2b27 Start webserver only in standalone-mode 2021-11-28 14:27:14 +01:00
Ollo
81adc9bd61 Automatic refresh; alternating background colors 2021-11-28 14:07:36 +01:00
Ollo
ca811a9abe JSON is shown on website 2021-11-28 13:59:49 +01:00
Ollo
73f637d6ec Sensor values are published as JSON in nonMQTT mode 2021-11-27 17:08:03 +01:00
Ollo
29beff5e82 Webserver is running 2021-11-27 16:19:35 +01:00
Ollo
49312a203d Spelling 2021-11-27 15:22:05 +01:00
Ollo
7567a4ef07 Automatic mode is set; instead of ON 2021-11-27 14:56:19 +01:00
Ollo
595fbbc3da Continue with BME680 2021-11-27 12:32:03 +01:00
Ollo
8e24c171ec Spelling corrected 2021-11-27 12:30:39 +01:00
Ollo
9807aa9818 BME680 and BMP280 code merged 2021-11-27 12:29:27 +01:00
9 changed files with 570 additions and 97 deletions

View File

@@ -3,7 +3,7 @@ located in IKEAs Vindriktning
after this upgrade it will measure: after this upgrade it will measure:
* air quality * air quality
* temperatur * temperature
* pressure * pressure
* altitude * altitude
@@ -39,10 +39,10 @@ VCC | GND
``` ```
The following pins are used: The following pins are used:
* GPIO4 PM1006 particle sensor * GPIO4 PM1006 particle sensor PIN REST on Vindriktning board
* GPIO2 WS2812 stripe out of three LEDs, replacing the orignal LEDs at front * GPIO2 WS2812 stripe out of three LEDs, replacing the original LEDs at front
* GPIO15 Red LED (optional) * GPIO15 Red LED (optional)
* GPIO12 Green LED (optional) * GPIO12 Green LED (optional) Used as 3.3V Supply for the I2C sensor
* GPIO13 Blue LED (optional) * GPIO13 Blue LED (optional)
* GPIO13 VCC of I2C (3.3 V) * GPIO13 VCC of I2C (3.3 V)
* GPIO14 I2C clock * GPIO14 I2C clock
@@ -54,5 +54,15 @@ The following pins are used:
* BMP280 sensor * BMP280 sensor
* some wire * some wire
# Webserver
This version has a webserver activated, if MQTT server is set to **localhost**.
Every 20 seconds a measurement is performed.
This is shown in a diagram via chart.js
# Sources # Sources
* [https://github.com/amkuipers/witty Witty pinout] For the Witty board
* [https://github.com/amkuipers/witty Witty pinout]
* [https://arduino.ua/products_pictures/large_AOC361-5.jpg Schematics]
* [https://www.chartjs.org/docs/latest/getting-started/integration.html]

37
data/chart.htm Normal file
View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>IKEA Home Sensor Diagram</title>
<script src="/chart.min.js"></script>
<script type="text/javascript">
function onBodyLoad(){
const xhttp = new XMLHttpRequest();
xhttp.responseType = 'json';
xhttp.onload = function() {
var jsonResponse = this.response;
console.log("JSON received");
const ctx = document.getElementById('myChart');
const myChart = new Chart(ctx, {
type: 'line',
data: jsonResponse,
options: {
scales: {
y: {
beginAtZero: false
}
}
}
});
console.log("Diagram generated");
}
xhttp.open("GET", "/diagram", true);
xhttp.send();
}
</script>
</head>
<body id="body" onload="onBodyLoad()">
<canvas id="myChart" width="100%" height="100"></canvas>
</body>
</html>

13
data/chart.min.js vendored Normal file

File diff suppressed because one or more lines are too long

96
data/standalone.htm Normal file
View File

@@ -0,0 +1,96 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>IKEA Home Sensor</title>
<style type="text/css">
tr.heading td {
background-color: black; color: white;
}
tr.d0 td {
background-color: #ffffff; color: black;
}
tr.d1 td {
background-color: #b8b8b8; color: black;
}
</style>
<script type="text/javascript">
function mySensorTimer(){
const xhttp = new XMLHttpRequest();
xhttp.responseType = 'json';
xhttp.onload = function() {
var jsonResponse = this.response;
document.getElementById("temp").innerHTML = jsonResponse.temp
document.getElementById("altitude").innerHTML = jsonResponse.altitude;
document.getElementById("pressure").innerHTML = jsonResponse.pressure;
document.getElementById("particle").innerHTML = jsonResponse.particle;
if (jsonResponse.gas) {
document.getElementById("gas").innerHTML = jsonResponse.gas;
} else {
document.getElementById("rowGas").hidden = true;
}
if (jsonResponse.humidity) {
document.getElementById("humidity").innerHTML = jsonResponse.humidity;
} else {
document.getElementById("rowHumidity").hidden = true;
}
console.log(this.responseText);
}
xhttp.open("GET", "/sensors", true);
xhttp.send();
}
function onBodyLoad(){
setInterval(mySensorTimer, 5000);
}
</script>
</head>
<body id="body" onload="onBodyLoad()">
<h1>Room Sensor</h1>
<table>
<thead>
<tr class="heading">
<td>Sensor</td>
<td>Value</td>
<td></td>
</tr>
</thead>
<tbody>
<tr class="d0">
<td>Temperatur</td>
<td id="temp">temp</td>
<td>°C</td>
</tr>
<tr id="rowHumidity" class="d1">
<td>Humidity</td>
<td id="humidity">humidity</td>
<td>%</td>
</tr>
<tr id="rowGas" class="d0">
<td>Gas</td>
<td id="gas">gas</td>
<td> KOhms</td>
</tr>
<tr class="d1">
<td>Altitude</td>
<td id="altitude">altitude</td>
<td>m</td>
</tr>
<tr class="d0">
<td>Pressure</td>
<td id="pressure">pressure</td>
<td>hPa</td>
</tr>
<tr class="d1">
<td>Particle</td>
<td id="particle">particle</td>
<td>micro gram per quibik</td>
</tr>
</tbody>
</table>
<h1>Diagram</h1>
<p>
Can be found on the <a href="chart.htm">next page</a>.
</p>
</body>
</html>

View File

@@ -27,7 +27,7 @@ usage: ota_updater.py [-h] -l BROKER_HOST -p BROKER_PORT [-u BROKER_USERNAME]
[-d BROKER_PASSWORD] [-t BASE_TOPIC] -i DEVICE_ID [-d BROKER_PASSWORD] [-t BASE_TOPIC] -i DEVICE_ID
firmware firmware
ota firmware update scirpt for ESP8226 implemenation of the Homie mqtt IoT ota firmware update script for ESP8226 implementation of the Homie mqtt IoT
convention. convention.
positional arguments: positional arguments:

View File

@@ -7,5 +7,6 @@ if [ $? -ne 0 ]; then
fi fi
codespell -w ../src/* codespell -w ../src/*
codespell -w ../include/* codespell -w ../include/*
codespell ../Readme.md codespell -w ../*.md
codespell -w *.md
exit 0 exit 0

View File

@@ -12,7 +12,7 @@
#ifndef HOMIE_SETTINGS #ifndef HOMIE_SETTINGS
#define HOMIE_SETTINGS #define HOMIE_SETTINGS
#define HOMIE_FIRMWARE_NAME "Vindriktning" #define HOMIE_FIRMWARE_NAME "RoomSensor"
#define HOMIE_FIRMWARE_VERSION "1.2.1" #define HOMIE_FIRMWARE_VERSION "2.2.0"
#endif #endif

View File

@@ -12,11 +12,17 @@
platform = espressif8266 platform = espressif8266
board = d1_mini board = d1_mini
framework = arduino framework = arduino
build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY -DBMP280
; build_flag needs define the Bosch sensor...
; -D BMP280
;or
; -D BME680
; the latest development branch (convention V3.0.x) ; the latest development branch (convention V3.0.x)
lib_deps = https://github.com/homieiot/homie-esp8266.git#develop lib_deps = https://github.com/homieiot/homie-esp8266.git#develop
EspSoftwareSerial EspSoftwareSerial
NeoPixel NeoPixel
adafruit/Adafruit BMP280 Library @ ^2.4.2
adafruit/Adafruit BME680 Library @ ^2.0.1 adafruit/Adafruit BME680 Library @ ^2.0.1
upload_port = /dev/ttyUSB1 upload_port = /dev/ttyUSB1

View File

@@ -18,30 +18,39 @@
#include <Adafruit_NeoPixel.h> #include <Adafruit_NeoPixel.h>
#include <Wire.h> #include <Wire.h>
#include <Adafruit_Sensor.h> #include <Adafruit_Sensor.h>
#ifdef BME680
#include "Adafruit_BME680.h" #include "Adafruit_BME680.h"
#else
#ifdef BMP280
#include "Adafruit_BMP280.h"
#else
#error "Decition, which BMx??? is used missing"
#endif
#endif
/****************************************************************************** /******************************************************************************
* DEFINES * DEFINES
******************************************************************************/ ******************************************************************************/
#define GPIO_WS2812 D4 /**< GPIO2 */ #define GPIO_WS2812 D4 /**< GPIO2 */
#define SENSOR_PM1006_RX D2 /**< GPIO4 */ #define SENSOR_PM1006_RX D2 /**< GPIO4 */
#define SENSOR_PM1006_TX -1 /**< Unused */ #define SENSOR_PM1006_TX -1 /**< Unused */
#define WITTY_RGB_R D8 /**< GPIO15 */ #define WITTY_RGB_R D8 /**< GPIO15 */
#define WITTY_RGB_G D6 /**< GPIO12 */ #define WITTY_RGB_G D6 /**< GPIO12 Used as 3.3V Power supply for the I2C Sensor */
#define WITTY_RGB_B D7 /**< GPIO13 */ #define WITTY_RGB_B D7 /**< GPIO13 */
#define PM1006_BIT_RATE 9600 #define PM1006_BIT_RATE 9600
#define PM1006_MQTT_UPDATE 5000 /**< Check the sensor every 10 seconds; New measurement is done every 20seconds by the sensor */ #define PM1006_MQTT_UPDATE 30000 /**< Check the sensor every 30 seconds; New measurement is done every 20seconds by the PM1006 sensor */
#define PIXEL_COUNT 3 #define PIXEL_COUNT 3
#define GPIO_BUTTON SENSOR_PM1006_RX /**< Button and software serial share one pin on Witty board */ #define GPIO_BUTTON SENSOR_PM1006_RX /**< Button and software serial share one pin on Witty board */
#define SENSOR_I2C_SCK D1 /**< GPIO14 - I2C clock pin */ #define SENSOR_I2C_SCK D5 /**< GPIO14 - I2C clock pin */
#define SENSOR_I2C_SDI D5 /**< GPIO5 - I2C data pin */ #define SENSOR_I2C_SDI D1 /**< GPIO5 - I2C data pin */
#define PM1006_BIT_RATE 9600
#define PM1006_MQTT_UPDATE 5000 /**< Check the sensor every 10 seconds; New measurement is done every 20seconds by the sensor */
#define SEALEVELPRESSURE_HPA (1013.25) #define SEALEVELPRESSURE_HPA (1013.25)
#define BUTTON_MAX_CYCLE 10000U /**< Action: Reset configuration */
#define BUTTON_MIN_ACTION_CYCLE 55U /**< Minimum cycle to react on the button (e.g. 5 second) */
#define BUTTON_CHECK_INTERVALL 100U /**< Check every 100 ms the button state */
#define LOG_TOPIC "log\0" #define LOG_TOPIC "log\0"
#define MQTT_LEVEL_ERROR 1 #define MQTT_LEVEL_ERROR 1
#define MQTT_LEVEL_WARNING 10 #define MQTT_LEVEL_WARNING 10
@@ -62,6 +71,8 @@
strcat(topic, "/"); \ strcat(topic, "/"); \
strcat(topic, test); strcat(topic, test);
#define PERCENT2FACTOR(b, a) ((b * a.get()) / 100)
#define NUMBER_TYPE "Number" #define NUMBER_TYPE "Number"
#define NODE_PARTICLE "particle" #define NODE_PARTICLE "particle"
#define NODE_TEMPERATUR "temp" #define NODE_TEMPERATUR "temp"
@@ -70,10 +81,29 @@
#define NODE_GAS "gas" #define NODE_GAS "gas"
#define NODE_HUMIDITY "humidity" #define NODE_HUMIDITY "humidity"
#define NODE_AMBIENT "ambient" #define NODE_AMBIENT "ambient"
#define NODE_BUTTON "button"
#define PORTNUMBER_HTTP 80 /**< IANA TCP/IP port number, used for HTTP / web traffic */
#define SERIAL_RCEVBUF_MAX 80 /**< Maximum 80 characters can be received from the PM1006 sensor */
#define MEASURE_POINT_MAX 1024 /**< Amount of measure point, that can be stored */
/****************************************************************************** /******************************************************************************
* TYPE DEFS * TYPE DEFS
******************************************************************************/ ******************************************************************************/
typedef struct s_point {
long timestamp;
float temp;
#ifdef BME680
uint32_t gas;
float humidity;
#endif
float pressure;
int pm25;
} sensor_point;
/****************************************************************************** /******************************************************************************
* FUNCTION PROTOTYPES * FUNCTION PROTOTYPES
******************************************************************************/ ******************************************************************************/
@@ -87,34 +117,61 @@ void log(int level, String message, int code);
bool mConfigured = false; bool mConfigured = false;
bool mConnected = false; bool mConnected = false;
bool mFailedI2Cinitialization = false; bool mFailedI2Cinitialization = false;
AsyncWebServer* mHttp = NULL;
long mLastButtonAction = 0;
/******************************* Sensor data **************************/ /******************************* Sensor data **************************/
HomieNode particle(NODE_PARTICLE, "particle", "number"); /**< Measuret in micro gram per quibik meter air volume */ HomieNode particle(NODE_PARTICLE, "particle", "number"); /**< Measuret in micro gram per quibik meter air volume */
HomieNode temperatureNode(NODE_TEMPERATUR, "Room Temperature", "number"); HomieNode temperaturNode(NODE_TEMPERATUR, "Room Temperature", "number");
HomieNode pressureNode(NODE_PRESSURE, "Pressure", "number"); HomieNode pressureNode(NODE_PRESSURE, "Pressure", "number");
HomieNode altitudeNode(NODE_ALTITUDE, "Altitude", "number"); HomieNode altitudeNode(NODE_ALTITUDE, "Altitude", "number");
#ifdef BME680
HomieNode gasNode(NODE_GAS, "Gas", "number"); HomieNode gasNode(NODE_GAS, "Gas", "number");
HomieNode humidityNode(NODE_HUMIDITY, "Humidity", "number"); HomieNode humidityNode(NODE_HUMIDITY, "Humidity", "number");
#endif
HomieNode buttonNode(NODE_BUTTON, "Button", "number");
/****************************** Output control ***********************/ /****************************** Output control ***********************/
HomieNode ledStripNode /* to rule them all */("led", "RGB led", "color"); HomieNode ledStripNode /* to rule them all */("led", "RGB led", "color");
/************************** Settings ******************************/ /************************** Settings ******************************/
HomieSetting<bool> i2cEnable("i2c", "BME280 sensor present"); HomieSetting<bool> i2cEnable("i2c",
HomieSetting<bool> rgbTemp("rgbTemp", "Show temperatur via red (>20 °C) and blue (< 20°C)"); #ifdef BME680
"BME680 sensor present"
#else
#ifdef BMP280
"BMP280 sensor present"
#else
"No I2C sensor specified in the project"
#endif
#endif
);
HomieSetting<bool> rgbTemp("rgbTemp", "Show temperature via red (>20 °C) and blue (< 20°C)");
HomieSetting<long> rgbDim("rgbDim", "Factor (1 to 200%) of the status LEDs");
static SoftwareSerial pmSerial(SENSOR_PM1006_RX, SENSOR_PM1006_TX); static SoftwareSerial pmSerial(SENSOR_PM1006_RX, SENSOR_PM1006_TX);
Adafruit_BME680 bme(&Wire); // connected via I2C #ifdef BME680
Adafruit_BME680 bmx(&Wire); // connected via I2C
#else
#ifdef BMP280
Adafruit_BMP280 bmx; // connected via I2C
#endif
#endif
Adafruit_NeoPixel strip(PIXEL_COUNT, GPIO_WS2812, NEO_GRB + NEO_KHZ800); Adafruit_NeoPixel strip(PIXEL_COUNT, GPIO_WS2812, NEO_GRB + NEO_KHZ800);
// Variablen // Variablen
uint8_t serialRxBuf[80]; uint8_t serialRxBuf[SERIAL_RCEVBUF_MAX];
uint8_t rxBufIdx = 0; uint8_t rxBufIdx = 0;
int spm25 = 0; int mParticle_pM25 = 0;
int last = 0; int last = 0;
unsigned int mButtonPressed = 0; unsigned int mButtonPressed = 0;
bool mSomethingReceived = false; bool mSomethingReceived = false;
bool mConnectedNonMQTT = false;
sensor_point* mMeasureSeries;
uint32_t mMeasureIndex = 0;
/****************************************************************************** /******************************************************************************
* LOCAL FUNCTIONS * LOCAL FUNCTIONS
@@ -170,18 +227,34 @@ void onHomieEvent(const HomieEvent &event)
{ {
switch (event.type) switch (event.type)
{ {
case HomieEventType::MQTT_READY: case HomieEventType::WIFI_CONNECTED:
mConnected=true;
digitalWrite(WITTY_RGB_R, LOW); digitalWrite(WITTY_RGB_R, LOW);
if (!i2cEnable.get()) { /** keep green LED activated to power I2C sensor */ if (!i2cEnable.get()) { /** keep green LED activated to power I2C sensor */
digitalWrite(WITTY_RGB_G, LOW); digitalWrite(WITTY_RGB_G, LOW);
log(MQTT_LEVEL_INFO, F("I2C powersupply deactivated"), MQTT_LOG_I2CINIT);
} }
digitalWrite(WITTY_RGB_B, LOW); digitalWrite(WITTY_RGB_B, LOW);
strip.fill(strip.Color(0,0,128)); strip.fill(strip.Color(0,0,128));
strip.show(); strip.show();
if (mHttp != NULL) {
strip.fill(strip.Color(0,64,0));
mConnectedNonMQTT = true;
}
break;
case HomieEventType::MQTT_READY:
mConnected=true;
if (mFailedI2Cinitialization) { if (mFailedI2Cinitialization) {
log(MQTT_LEVEL_DEBUG, F("Could not find a valid BME680 sensor, check wiring or " log(MQTT_LEVEL_DEBUG,
"try a different address!"), MQTT_LOG_I2CINIT); #ifdef BME680
"Could not find a valid BME680 sensor, check wiring or try a different address!"
#else
#ifdef BMP280
"Could not find a valid BMP280 sensor, check wiring or try a different address!"
#else
"no I2C sensor defined"
#endif
#endif
, MQTT_LOG_I2CINIT);
} else { } else {
log(MQTT_LEVEL_INFO, F("BME680 sensor found"), MQTT_LOG_I2CINIT); log(MQTT_LEVEL_INFO, F("BME680 sensor found"), MQTT_LOG_I2CINIT);
} }
@@ -198,30 +271,175 @@ void onHomieEvent(const HomieEvent &event)
} }
} }
void bmpPublishValues() { void updateLEDs() {
#ifdef BME680
// Tell BME680 to begin measurement. // Tell BME680 to begin measurement.
unsigned long endTime = bme.beginReading(); unsigned long endTime = bmx.beginReading();
if (endTime == 0) { if (endTime == 0) {
log(MQTT_LEVEL_ERROR, F("BME680 not accessable"), MQTT_LOG_I2READ); log(MQTT_LEVEL_ERROR, "BME680 not accessible", MQTT_LOG_I2READ);
return; return;
} }
temperatureNode.setProperty(NODE_TEMPERATUR).send(String(bme.readTemperature())); #endif
pressureNode.setProperty(NODE_PRESSURE).send(String(bme.readPressure() / 100.0F));
altitudeNode.setProperty(NODE_ALTITUDE).send(String(bme.readAltitude(SEALEVELPRESSURE_HPA)));
gasNode.setProperty(NODE_GAS).send(String((bme.gas_resistance / 1000.0)));
humidityNode.setProperty(NODE_HUMIDITY).send(String(bme.humidity));
if ( (rgbTemp.get()) && (!mSomethingReceived) ) { if ( (rgbTemp.get()) && (!mSomethingReceived) ) {
if (bme.readTemperature() < TEMPBORDER) { if (bmx.readTemperature() < TEMPBORDER) {
strip.setPixelColor(0, strip.Color(0,0,255)); strip.setPixelColor(0, strip.Color(0,0,255));
} else { } else {
strip.setPixelColor(0, strip.Color(255,0,0)); strip.setPixelColor(0, strip.Color(255,0,0));
} }
strip.show(); strip.show();
} else {
#ifdef BME680
bmx.performReading();
#endif
} }
} }
void bmpPublishValues() {
// Publish the values
temperaturNode.setProperty(NODE_TEMPERATUR).send(String(bmx.readTemperature()));
pressureNode.setProperty(NODE_PRESSURE).send(String(bmx.readPressure() / 100.0F));
altitudeNode.setProperty(NODE_ALTITUDE).send(String(bmx.readAltitude(SEALEVELPRESSURE_HPA)));
#ifdef BME680
gasNode.setProperty(NODE_GAS).send(String((bmx.gas_resistance / 1000.0)));
humidityNode.setProperty(NODE_HUMIDITY).send(String(bmx.humidity));
#endif
log(MQTT_LEVEL_DEBUG, String("Temp" + String(bmx.readTemperature()) + "\tPressure:" +
String(bmx.readPressure() / 100.0F) + "\t Altitude:"+
String(bmx.readAltitude(SEALEVELPRESSURE_HPA))), MQTT_LOG_I2READ);
if ( (rgbTemp.get()) && (!mSomethingReceived) ) {
if (bmx.readTemperature() < TEMPBORDER) {
strip.setPixelColor(0, strip.Color(0,0,PERCENT2FACTOR(127, rgbDim)));
} else {
strip.setPixelColor(0, strip.Color(PERCENT2FACTOR(127, rgbDim),0,0));
}
strip.show();
}
}
/**
* @brief Generate JSON with last measured values
* For the update interval, please check @see PM1006_MQTT_UPDATE
* @return String with JSON
*/
String sensorAsJSON(void) {
String buffer;
StaticJsonDocument<500> doc;
if (mMeasureIndex > 0) {
int lastIdx = mMeasureIndex - 1;
doc["temp"] = String(mMeasureSeries[lastIdx].temp);
#ifdef BME680
doc["gas"] = String(mMeasureSeries[lastIdx].gas);
doc["humidity"] = String(mMeasureSeries[lastIdx].humidity);
#endif
float atmospheric = mMeasureSeries[lastIdx].pressure;
float altitude = 44330.0 * (1.0 - pow(atmospheric / SEALEVELPRESSURE_HPA, 0.1903));
doc["altitude"] = String(altitude);
doc["pressure"] = String(atmospheric);
doc["particle"] = String(mMeasureSeries[lastIdx].pm25);
}
doc["cycle"] = String(mMeasureIndex);
serializeJson(doc, buffer);
return buffer;
}
/**
* @brief Generate JSON with all available sensors
*
* @return String
*/
String sensorHeader(void) {
String buffer;
StaticJsonDocument<500> doc;
doc.add("milli");
doc.add("temp");
#ifdef BME680
doc.add("gas");
doc.add("humidity");
#endif
doc.add("altitude");
doc.add("pressure");
doc.add("particle");
serializeJson(doc, buffer);
return buffer;
}
/**
* @brief Generate JSON with all sensor values
* Array of JSON with all measured elements to show in the web browser.
* Example:
* <code>
* dynamicData = {
* labels: [ "0", "-30", "-60", "-90", "-120", "-160" ],
* datasets: [{
* label: 'temp',
* data: [24, 22, 23, 25, 22, 23],
* borderColor: "rgba(255,0,0,1)"
* }, {
* label: 'pressure',
* data: [1000, 1002, 1003, 999, 996, 1000],
* borderColor: "rgba(0,0,255,1)"
* }
* ]
* }
* </code>
*
* @return String
*/
String diagramJson(void) {
int i;
String buffer;
String bufferLabels = "\"\"";
String bufferDataTemp = "\"\"";
String bufferDataPressure = "\"\"";
String bufferDataPM = "\"\"";
String bufferDatasets;
String bufferData;
if (mMeasureSeries == NULL) {
buffer = "{ \"error\": \"Malloc failed\" }";
return buffer;
}
long now = millis();
if (mMeasureIndex > 0) {
bufferLabels = "[ \"" + String((now - mMeasureSeries[0].timestamp) / 1000) + "s";
bufferDataTemp = "[ \"" + String(mMeasureSeries[0].temp);
bufferDataPressure = "[ \"" + String(mMeasureSeries[0].pressure);
bufferDataPM = "[ \"" + String(mMeasureSeries[0].pm25);
}
for(i=1; i < mMeasureIndex; i++) {
bufferLabels += "\", \"" + String((now - mMeasureSeries[i].timestamp) / 1000) + "s";
bufferDataTemp += "\", \"" + String(mMeasureSeries[i].temp);
bufferDataPressure += "\", \"" + String(mMeasureSeries[i].pressure);
bufferDataPM += "\", \"" + String(mMeasureSeries[i].pm25);
}
if (mMeasureIndex > 0) {
bufferLabels += "\" ]";
bufferDataTemp += "\" ]";
bufferDataPressure += "\" ]";
bufferDataPM += "\" ]";
}
/* Generate label */
buffer = "{ \"labels\" : " + bufferLabels + ",\n";
buffer += "\"datasets\" : [";
/* generate first block for Temperature */
buffer += "{\n \"label\" : \"Temp\",\n \"data\" : " + bufferDataTemp + ",\n \"borderColor\" : \"rgba(255,0,0,1)\" \n}";
/* generate second block for Pressure */
buffer += ", ";
buffer += "{\n \"label\" : \"Pressure\",\n \"data\" : " + bufferDataPressure + ",\n \"borderColor\" : \"rgba(0,0,255,1)\" \n}";
/* generate third block for PM2.5 values */
buffer += ", ";
buffer += "{\n \"label\" : \"PM 2.5\",\n \"data\" : " + bufferDataPM + ",\n \"borderColor\" : \"rgba(0,255,0,1)\" \n}";
/* TODO, next ones ... */
buffer += "]\n }";
return buffer;
}
/** /**
* @brief Main loop, triggered by the Homie API * @brief Main loop, triggered by the Homie API
* All logic needs to be done here. * All logic needs to be done here.
@@ -237,16 +455,18 @@ void loopHandler()
{ {
static long lastRead = 0; static long lastRead = 0;
if ((millis() - lastRead) > PM1006_MQTT_UPDATE) { if ((millis() - lastRead) > PM1006_MQTT_UPDATE) {
int pM25 = getSensorData(); mParticle_pM25 = getSensorData();
if (pM25 >= 0) { if (mParticle_pM25 >= 0) {
particle.setProperty(NODE_PARTICLE).send(String(pM25)); if (!mConnectedNonMQTT) {
particle.setProperty(NODE_PARTICLE).send(String(mParticle_pM25));
}
if (!mSomethingReceived) { if (!mSomethingReceived) {
if (pM25 < 35) { if (mParticle_pM25 < 35) {
strip.fill(strip.Color(0, 255, 0)); /* green */ strip.fill(strip.Color(0, PERCENT2FACTOR(127, rgbDim), 0)); /* green */
} else if (pM25 < 85) { } else if (mParticle_pM25 < 85) {
strip.fill(strip.Color(255, 127, 0)); /* orange */ strip.fill(strip.Color(PERCENT2FACTOR(127, rgbDim), PERCENT2FACTOR(64, rgbDim), 0)); /* orange */
} else { } else {
strip.fill(strip.Color(255, 0, 0)); /* red */ strip.fill(strip.Color(PERCENT2FACTOR(127, rgbDim), 0, 0)); /* red */
} }
strip.show(); strip.show();
} }
@@ -254,11 +474,38 @@ void loopHandler()
/* Read BOSCH sensor */ /* Read BOSCH sensor */
if (i2cEnable.get() && (!mFailedI2Cinitialization)) { if (i2cEnable.get() && (!mFailedI2Cinitialization)) {
bmpPublishValues(); updateLEDs();
if (!mConnectedNonMQTT) {
bmpPublishValues();
}
}
// FIXME: add the measured data into the big list
if (mMeasureSeries != NULL) {
mMeasureSeries[mMeasureIndex].timestamp = millis();
mMeasureSeries[mMeasureIndex].temp = bmx.readTemperature();
#ifdef BME680
mMeasureSeries[mMeasureIndex].gas = (bmx.gas_resistance / 1000.0);
mMeasureSeries[mMeasureIndex].humidity = bmx.humidity;
#endif
float atmospheric = bmx.readPressure() / 100.0F;
mMeasureSeries[mMeasureIndex].pressure = atmospheric;
mMeasureSeries[mMeasureIndex].pm25 = mParticle_pM25;
mMeasureIndex++;
}
/* Clean cycles buttons */
if ((!mConnectedNonMQTT) && (mButtonPressed <= BUTTON_MIN_ACTION_CYCLE)) {
buttonNode.setProperty(NODE_BUTTON).send("0");
} }
lastRead = millis(); lastRead = millis();
} }
/* if the user sees something via the LEDs, inform MQTT, too */
if ((!mConnectedNonMQTT) && (mButtonPressed > BUTTON_MIN_ACTION_CYCLE)) {
buttonNode.setProperty(NODE_BUTTON).send(String(mButtonPressed));
}
// Feed the dog -> ESP stay alive // Feed the dog -> ESP stay alive
ESP.wdtFeed(); ESP.wdtFeed();
} }
@@ -268,35 +515,40 @@ void loopHandler()
bool ledHandler(const HomieRange& range, const String& value) { bool ledHandler(const HomieRange& range, const String& value) {
if (range.isRange) return false; // only one switch is present if (range.isRange) return false; // only one switch is present
mSomethingReceived = true; // Stop animation Homie.getLogger() << "Received: " << (value) << endl;
if (value.equals("250,250,250")) {
int sep1 = value.indexOf(','); mSomethingReceived = false; // enable animation again
int sep2 = value.indexOf(',', sep1 + 1); ledStripNode.setProperty(NODE_AMBIENT).send(value);
if ((sep1 > 0) && (sep2 > 0)) {
int red = value.substring(0,sep1).toInt(); /* OpenHAB hue (0-360°) */
int green = value.substring(sep1 + 1, sep2).toInt(); /* OpenHAB saturation (0-100%) */
int blue = value.substring(sep2 + 1, value.length()).toInt(); /* brightness (0-100%) */
uint8_t r = (red * 255) / 250;
uint8_t g = (green *255) / 250;
uint8_t b = (blue *255) / 250;
uint32_t c = strip.Color(r,g,b);
strip.fill(c);
strip.show();
ledStripNode.setProperty(NODE_AMBIENT).send(String(r) + "," + String(g) + "," + String(b));
return true; return true;
} else {
mSomethingReceived = true; // Stop animation
int sep1 = value.indexOf(',');
int sep2 = value.indexOf(',', sep1 + 1);
if ((sep1 > 0) && (sep2 > 0)) {
int red = value.substring(0,sep1).toInt();
int green = value.substring(sep1 + 1, sep2).toInt();
int blue = value.substring(sep2 + 1, value.length()).toInt();
uint8_t r = (red * 255) / 250;
uint8_t g = (green *255) / 250;
uint8_t b = (blue *255) / 250;
uint32_t c = strip.Color(r,g,b);
strip.fill(c);
strip.show();
ledStripNode.setProperty(NODE_AMBIENT).send(value);
return true;
}
} }
return false; return false;
} }
/****************************************************************************** /******************************************************************************
* GLOBAL FUNCTIONS * GLOBAL FUNCTIONS
*****************************************************************************/ *****************************************************************************/
void setup() void setup()
{ {
SPIFFS.begin(); SPIFFS.begin();
Serial.begin(115200); Serial.begin(115200);
Serial.setTimeout(2000); Serial.setTimeout(2000);
@@ -310,15 +562,18 @@ void setup()
Homie_setFirmware(HOMIE_FIRMWARE_NAME, HOMIE_FIRMWARE_VERSION); Homie_setFirmware(HOMIE_FIRMWARE_NAME, HOMIE_FIRMWARE_VERSION);
Homie.setLoopFunction(loopHandler); Homie.setLoopFunction(loopHandler);
Homie.onEvent(onHomieEvent); Homie.onEvent(onHomieEvent);
i2cEnable.setDefaultValue(false); i2cEnable.setDefaultValue(true);
rgbTemp.setDefaultValue(false); rgbTemp.setDefaultValue(false);
memset(serialRxBuf, 0, 80); rgbDim.setDefaultValue(100).setValidator([] (long candidate) {
return (candidate > 1) && (candidate <= 200);
});
memset(serialRxBuf, 0, SERIAL_RCEVBUF_MAX);
pmSerial.begin(PM1006_BIT_RATE); pmSerial.begin(PM1006_BIT_RATE);
Homie.setup(); Homie.setup();
particle.advertise(NODE_PARTICLE).setName("Particle").setDatatype(NUMBER_TYPE).setUnit("micro gram per quibik"); particle.advertise(NODE_PARTICLE).setName("Particle").setDatatype(NUMBER_TYPE).setUnit("micro gram per quibik");
temperatureNode.advertise(NODE_TEMPERATUR).setName("Degrees") temperaturNode.advertise(NODE_TEMPERATUR).setName("Degrees")
.setDatatype("float") .setDatatype("float")
.setUnit("ºC"); .setUnit("ºC");
pressureNode.advertise(NODE_PRESSURE).setName("Pressure") pressureNode.advertise(NODE_PRESSURE).setName("Pressure")
@@ -327,73 +582,127 @@ void setup()
altitudeNode.advertise(NODE_ALTITUDE).setName("Altitude") altitudeNode.advertise(NODE_ALTITUDE).setName("Altitude")
.setDatatype("float") .setDatatype("float")
.setUnit("m"); .setUnit("m");
#ifdef BME680
gasNode.advertise(NODE_GAS).setName("Gas") gasNode.advertise(NODE_GAS).setName("Gas")
.setDatatype("float") .setDatatype("float")
.setUnit(" KOhms"); .setUnit(" KOhms");
humidityNode.advertise(NODE_HUMIDITY).setName("Humidity") humidityNode.advertise(NODE_HUMIDITY).setName("Humidity")
.setDatatype("float") .setDatatype("float")
.setUnit("%"); .setUnit("%");
#endif
ledStripNode.advertise(NODE_AMBIENT).setName("All Leds") ledStripNode.advertise(NODE_AMBIENT).setName("All Leds")
.setDatatype("color").setFormat("rgb") .setDatatype("color").setFormat("rgb")
.settable(ledHandler); .settable(ledHandler);
buttonNode.advertise(NODE_BUTTON).setName("Button pressed")
.setDatatype("integer");
strip.begin(); strip.begin();
/* activate I2C for BOSCH sensor */
Wire.begin(SENSOR_I2C_SDI, SENSOR_I2C_SCK);
mConfigured = Homie.isConfigured(); mConfigured = Homie.isConfigured();
digitalWrite(WITTY_RGB_G, HIGH); digitalWrite(WITTY_RGB_G, HIGH);
if (mConfigured) if (mConfigured)
{ {
mMeasureSeries = (sensor_point *) malloc(sizeof(sensor_point) * MEASURE_POINT_MAX);
memset(mMeasureSeries, 0, sizeof(sensor_point) * MEASURE_POINT_MAX);
if (i2cEnable.get()) { if (i2cEnable.get()) {
strip.fill(strip.Color(0,128,0)); #ifdef BME680
strip.show(); printf("Wait 1 second...\r\n");
delay(1000);
#endif
/* activate I2C for BOSCH sensor */
Wire.begin(SENSOR_I2C_SDI, SENSOR_I2C_SCK);
printf("Wait 50 milliseconds...\r\n");
delay(50);
/* Extracted from library's example */ /* Extracted from library's example */
mFailedI2Cinitialization = !bme.begin(); mFailedI2Cinitialization = !bmx.begin();
if (!mFailedI2Cinitialization) { if (!mFailedI2Cinitialization) {
bme.setTemperatureOversampling(BME680_OS_8X); strip.fill(strip.Color(0,PERCENT2FACTOR(64, rgbDim),0));
bme.setHumidityOversampling(BME680_OS_2X); strip.show();
bme.setPressureOversampling(BME680_OS_4X); #ifdef BME680
bme.setIIRFilterSize(BME680_FILTER_SIZE_3); bmx.setTemperatureOversampling(BME680_OS_8X);
bme.setGasHeater(320, 150); // 320*C for 150 ms bmx.setHumidityOversampling(BME680_OS_2X);
bmx.setPressureOversampling(BME680_OS_4X);
bmx.setIIRFilterSize(BME680_FILTER_SIZE_3);
bmx.setGasHeater(320, 150); // 320*C for 150 ms
#endif
#ifdef BMP280
/* Default settings from datasheet. */
bmx.setSampling(Adafruit_BMP280::MODE_NORMAL, /* Operating Mode. */
Adafruit_BMP280::SAMPLING_X2, /* Temp. oversampling */
Adafruit_BMP280::SAMPLING_X16, /* Pressure oversampling */
Adafruit_BMP280::FILTER_X16, /* Filtering. */
Adafruit_BMP280::STANDBY_MS_500); /* Standby time. */
#endif
printf("Sensor found on I2C bus\r\n");
} else { } else {
printf("Faild to initialize I2C bus\r\n"); printf("Failed to initialize I2C bus\r\n");
} }
} }
strip.fill(strip.Color(0,0,0)); strip.fill(strip.Color(0,0,0));
for (int i=0;i < (PIXEL_COUNT / 2); i++) {
strip.setPixelColor(0, strip.Color(0,0,128)); for (int i=0;i < (PIXEL_COUNT / 2); i++) {
} strip.setPixelColor(0, strip.Color(0,128,0));
strip.show(); }
digitalWrite(WITTY_RGB_B, HIGH); mHttp = new AsyncWebServer(PORTNUMBER_HTTP);
} else { mHttp->on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){
strip.fill(strip.Color(128,0,0)); request->send(200, "text/plain", String(ESP.getFreeHeap()));
for (int i=0;i < (PIXEL_COUNT / 2); i++) { });
strip.setPixelColor(0, strip.Color(0,0,128)); mHttp->on("/sensors", HTTP_GET, [](AsyncWebServerRequest *request){
} request->send(200, "application/json", sensorAsJSON());
});
mHttp->on("/header", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "application/json", sensorHeader());
});
mHttp->on("/diagram", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "application/json", diagramJson());
});
mHttp->serveStatic("/", SPIFFS, "/").setDefaultFile("standalone.htm");
mHttp->begin();
Homie.getLogger() << "Webserver started" << endl;
strip.show(); strip.show();
} }
} }
void loop() void loop()
{ {
Homie.loop(); if (!mConnectedNonMQTT) {
Homie.loop();
} else {
/* call the custom loop directly, as Homie is deactivated */
loopHandler();
}
/* use the pin, receiving the soft serial additionally as button */ /* use the pin, receiving the soft serial additionally as button */
if (digitalRead(GPIO_BUTTON) == LOW) { if (digitalRead(GPIO_BUTTON) == LOW) {
mButtonPressed++; if ((millis() - mLastButtonAction) > BUTTON_CHECK_INTERVALL) {
mButtonPressed++;
}
if (mButtonPressed > BUTTON_MIN_ACTION_CYCLE) {
digitalWrite(WITTY_RGB_R, HIGH);
digitalWrite(WITTY_RGB_B, LOW);
strip.fill(strip.Color(0,0,0));
strip.setPixelColor(0, strip.Color((mButtonPressed % 100),0,0));
strip.setPixelColor(1, strip.Color((mButtonPressed / 100),0,0));
strip.setPixelColor(2, strip.Color((mButtonPressed / 100),0,0));
strip.show();
}
} else { } else {
mButtonPressed=0U; mButtonPressed=0U;
digitalWrite(WITTY_RGB_R, LOW);
} }
if (mButtonPressed > 10000U) { if (mButtonPressed > BUTTON_MAX_CYCLE) {
mButtonPressed=0U;
if (SPIFFS.exists("/homie/config.json")) { if (SPIFFS.exists("/homie/config.json")) {
strip.fill(strip.Color(0,PERCENT2FACTOR(127, rgbDim),0));
strip.show();
printf("Resetting config\r\n"); printf("Resetting config\r\n");
SPIFFS.remove("/homie/config.json"); SPIFFS.remove("/homie/config.json");
SPIFFS.end(); SPIFFS.end();
delay(50);
Homie.reboot();
} else { } else {
printf("No config present\r\n"); printf("No config present\r\n");
strip.fill(strip.Color(0,0,128));
strip.show();
} }
} }
} }
@@ -414,4 +723,5 @@ void log(int level, String message, int statusCode)
Homie.getMqttClient().publish(logTopic, 2, false, buffer.c_str()); Homie.getMqttClient().publish(logTopic, 2, false, buffer.c_str());
delete logTopic; delete logTopic;
} }
Homie.getLogger() << (level) << "@" << (statusCode) << " " << (message) << endl;
} }