Initial version
This commit is contained in:
commit
d918bfe08e
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.pio
|
||||||
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/ipch
|
21
PM1006.code-workspace
Normal file
21
PM1006.code-workspace
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"files.associations": {
|
||||||
|
"*.tcc": "cpp",
|
||||||
|
"bitset": "cpp",
|
||||||
|
"algorithm": "cpp",
|
||||||
|
"istream": "cpp",
|
||||||
|
"limits": "cpp",
|
||||||
|
"streambuf": "cpp",
|
||||||
|
"functional": "cpp",
|
||||||
|
"string": "cpp",
|
||||||
|
"typeinfo": "cpp",
|
||||||
|
"cmath": "cpp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
Readme.md
Normal file
35
Readme.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
## Filesystem
|
||||||
|
### Configuration
|
||||||
|
Use the config-example.json from the host folder and create here a config.json file.
|
||||||
|
### HowTo upload
|
||||||
|
Start Platform.io
|
||||||
|
Open a new Atom-Terminal and generate the filesystem with the following command :
|
||||||
|
```pio run -t buildfs```
|
||||||
|
Upload this new generated filesystem with:
|
||||||
|
```pio run -t uploadfs```
|
||||||
|
|
||||||
|
### Command pio
|
||||||
|
Can be found at ```~/.platformio/penv/bin/pio```
|
||||||
|
|
||||||
|
# Hardware
|
||||||
|
ESP8266 version ESP12 was used.
|
||||||
|
|
||||||
|
The prototype was based on the Witty board
|
||||||
|
```
|
||||||
|
REST | TXD
|
||||||
|
ADC LDR | RXD
|
||||||
|
CH_PD | GPIO05
|
||||||
|
GPIO16 | BTN GPIO04
|
||||||
|
GPIO14 | GPIO00
|
||||||
|
GPIO12 RGB-G | GPIO02
|
||||||
|
GPIO13 RGB-B | RGB-R GPIO15
|
||||||
|
VCC | GND
|
||||||
|
USB
|
||||||
|
```
|
||||||
|
|
||||||
|
The following pins are used:
|
||||||
|
* GPIO4 PM1006 particle sensor
|
||||||
|
* GPIO2 WS2812 stripe out of three LEDs, replacing the orignal LEDs at front
|
||||||
|
|
||||||
|
# Sources
|
||||||
|
* [https://github.com/amkuipers/witty Witty pinout]
|
1
data/homie/.gitignore
vendored
Normal file
1
data/homie/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
config.json
|
BIN
data/homie/ui_bundle.gz
Normal file
BIN
data/homie/ui_bundle.gz
Normal file
Binary file not shown.
63
host/Readme.md
Normal file
63
host/Readme.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# Configuration
|
||||||
|
## File
|
||||||
|
Generate a file as, described in
|
||||||
|
http://homieiot.github.io/homie-esp8266/docs/develop-v3/configuration/json-configuration-file/
|
||||||
|
|
||||||
|
## Upload
|
||||||
|
* Start ESP
|
||||||
|
* Login to Wifi, opened by the ESP
|
||||||
|
* Use the script to upload the configuration file
|
||||||
|
* restart the ESP
|
||||||
|
|
||||||
|
# Remote Upload
|
||||||
|
|
||||||
|
This script will allow you to send an OTA update to your device:
|
||||||
|
***upload-via-mqtt.sh***
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Requirements are:
|
||||||
|
* paho-mqtt
|
||||||
|
|
||||||
|
## Usage of underlying tool
|
||||||
|
|
||||||
|
The python script can be used, manually, too:
|
||||||
|
```text
|
||||||
|
usage: ota_updater.py [-h] -l BROKER_HOST -p BROKER_PORT [-u BROKER_USERNAME]
|
||||||
|
[-d BROKER_PASSWORD] [-t BASE_TOPIC] -i DEVICE_ID
|
||||||
|
firmware
|
||||||
|
|
||||||
|
ota firmware update scirpt for ESP8226 implemenation of the Homie mqtt IoT
|
||||||
|
convention.
|
||||||
|
|
||||||
|
positional arguments:
|
||||||
|
firmware path to the firmware to be sent to the device
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
-l BROKER_HOST, --broker-host BROKER_HOST
|
||||||
|
host name or ip address of the mqtt broker
|
||||||
|
-p BROKER_PORT, --broker-port BROKER_PORT
|
||||||
|
port of the mqtt broker
|
||||||
|
-u BROKER_USERNAME, --broker-username BROKER_USERNAME
|
||||||
|
username used to authenticate with the mqtt broker
|
||||||
|
-d BROKER_PASSWORD, --broker-password BROKER_PASSWORD
|
||||||
|
password used to authenticate with the mqtt broker
|
||||||
|
-t BASE_TOPIC, --base-topic BASE_TOPIC
|
||||||
|
base topic of the homie devices on the broker
|
||||||
|
-i DEVICE_ID, --device-id DEVICE_ID
|
||||||
|
homie device id
|
||||||
|
```
|
||||||
|
|
||||||
|
* `BROKER_HOST` and `BROKER_PORT` defaults to 127.0.0.1 and 1883 respectively if not set.
|
||||||
|
* `BROKER_USERNAME` and `BROKER_PASSWORD` are optional.
|
||||||
|
* `BASE_TOPIC` has to end with a slash, defaults to `homie/` if not set.
|
||||||
|
|
||||||
|
### Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python ota_updater.py -l localhost -u admin -d secure -t "homie/" -i "device-id" /path/to/firmware.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Source
|
||||||
|
https://github.com/homieiot/homie-esp8266/blob/develop/scripts/ota_updater
|
174
host/ota_updater.py
Executable file
174
host/ota_updater.py
Executable file
@ -0,0 +1,174 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import division, print_function
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
import base64, sys, math
|
||||||
|
from hashlib import md5
|
||||||
|
|
||||||
|
# The callback for when the client receives a CONNACK response from the server.
|
||||||
|
def on_connect(client, userdata, flags, rc):
|
||||||
|
if rc != 0:
|
||||||
|
print("Connection Failed with result code {}".format(rc))
|
||||||
|
client.disconnect()
|
||||||
|
else:
|
||||||
|
print("Connected with result code {}".format(rc))
|
||||||
|
|
||||||
|
client.subscribe("{base_topic}{device_id}/$state".format(**userdata)) # v3 / v4 devices
|
||||||
|
client.subscribe("{base_topic}{device_id}/$online".format(**userdata)) # v2 devices
|
||||||
|
|
||||||
|
|
||||||
|
print("Waiting for device to come online...")
|
||||||
|
|
||||||
|
|
||||||
|
# The callback for when a PUBLISH message is received from the server.
|
||||||
|
def on_message(client, userdata, msg):
|
||||||
|
# decode string for python2/3 compatiblity
|
||||||
|
msg.payload = msg.payload.decode()
|
||||||
|
|
||||||
|
if msg.topic.endswith('$implementation/ota/status'):
|
||||||
|
status = int(msg.payload.split()[0])
|
||||||
|
|
||||||
|
if userdata.get("published"):
|
||||||
|
if status == 206: # in progress
|
||||||
|
# state in progress, print progress bar
|
||||||
|
progress, total = [int(x) for x in msg.payload.split()[1].split('/')]
|
||||||
|
bar_width = 30
|
||||||
|
bar = int(bar_width*(progress/total))
|
||||||
|
print("\r[", '+'*bar, ' '*(bar_width-bar), "] ", msg.payload.split()[1], end='', sep='')
|
||||||
|
if (progress == total):
|
||||||
|
print()
|
||||||
|
sys.stdout.flush()
|
||||||
|
elif status == 304: # not modified
|
||||||
|
print("Device firmware already up to date with md5 checksum: {}".format(userdata.get('md5')))
|
||||||
|
client.disconnect()
|
||||||
|
elif status == 403: # forbidden
|
||||||
|
print("Device ota disabled, aborting...")
|
||||||
|
client.disconnect()
|
||||||
|
|
||||||
|
elif msg.topic.endswith('$fw/checksum'):
|
||||||
|
checksum = msg.payload
|
||||||
|
|
||||||
|
if userdata.get("published"):
|
||||||
|
if checksum == userdata.get('md5'):
|
||||||
|
print("Device back online. Update Successful!")
|
||||||
|
else:
|
||||||
|
print("Expecting checksum {}, got {}, update failed!".format(userdata.get('md5'), checksum))
|
||||||
|
client.disconnect()
|
||||||
|
else:
|
||||||
|
if checksum != userdata.get('md5'): # save old md5 for comparison with new firmware
|
||||||
|
userdata.update({'old_md5': checksum})
|
||||||
|
else:
|
||||||
|
print("Device firmware already up to date with md5 checksum: {}".format(checksum))
|
||||||
|
client.disconnect()
|
||||||
|
|
||||||
|
elif msg.topic.endswith('ota/enabled'):
|
||||||
|
if msg.payload == 'true':
|
||||||
|
userdata.update({'ota_enabled': True})
|
||||||
|
else:
|
||||||
|
print("Device ota disabled, aborting...")
|
||||||
|
client.disconnect()
|
||||||
|
|
||||||
|
elif msg.topic.endswith('$state') or msg.topic.endswith('$online'):
|
||||||
|
if (msg.topic.endswith('$state') and msg.payload != 'ready') or (msg.topic.endswith('$online') and msg.payload == 'false'):
|
||||||
|
return
|
||||||
|
|
||||||
|
# calcluate firmware md5
|
||||||
|
firmware_md5 = md5(userdata['firmware']).hexdigest()
|
||||||
|
userdata.update({'md5': firmware_md5})
|
||||||
|
|
||||||
|
# Subscribing in on_connect() means that if we lose the connection and
|
||||||
|
# reconnect then subscriptions will be renewed.
|
||||||
|
client.subscribe("{base_topic}{device_id}/$implementation/ota/status".format(**userdata))
|
||||||
|
client.subscribe("{base_topic}{device_id}/$implementation/ota/enabled".format(**userdata))
|
||||||
|
client.subscribe("{base_topic}{device_id}/$fw/#".format(**userdata))
|
||||||
|
|
||||||
|
# Wait for device info to come in and invoke the on_message callback where update will continue
|
||||||
|
print("Waiting for device info...")
|
||||||
|
|
||||||
|
if ( not userdata.get("published") ) and ( userdata.get('ota_enabled') ) and \
|
||||||
|
( 'old_md5' in userdata.keys() ) and ( userdata.get('md5') != userdata.get('old_md5') ):
|
||||||
|
# push the firmware binary
|
||||||
|
userdata.update({"published": True})
|
||||||
|
topic = "{base_topic}{device_id}/$implementation/ota/firmware/{md5}".format(**userdata)
|
||||||
|
print("Publishing new firmware with checksum {}".format(userdata.get('md5')))
|
||||||
|
client.publish(topic, userdata['firmware'])
|
||||||
|
|
||||||
|
|
||||||
|
def main(broker_host, broker_port, broker_username, broker_password, broker_ca_cert, base_topic, device_id, firmware):
|
||||||
|
# initialise mqtt client and register callbacks
|
||||||
|
client = mqtt.Client()
|
||||||
|
client.on_connect = on_connect
|
||||||
|
client.on_message = on_message
|
||||||
|
|
||||||
|
# set username and password if given
|
||||||
|
if broker_username and broker_password:
|
||||||
|
client.username_pw_set(broker_username, broker_password)
|
||||||
|
|
||||||
|
if broker_ca_cert is not None:
|
||||||
|
client.tls_set(
|
||||||
|
ca_certs=broker_ca_cert
|
||||||
|
)
|
||||||
|
|
||||||
|
# save data to be used in the callbacks
|
||||||
|
client.user_data_set({
|
||||||
|
"base_topic": base_topic,
|
||||||
|
"device_id": device_id,
|
||||||
|
"firmware": firmware
|
||||||
|
})
|
||||||
|
|
||||||
|
# start connection
|
||||||
|
print("Connecting to mqtt broker {} on port {}".format(broker_host, broker_port))
|
||||||
|
client.connect(broker_host, broker_port, 60)
|
||||||
|
|
||||||
|
# Blocking call that processes network traffic, dispatches callbacks and handles reconnecting.
|
||||||
|
client.loop_forever()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='ota firmware update scirpt for ESP8226 implemenation of the Homie mqtt IoT convention.')
|
||||||
|
|
||||||
|
# ensure base topic always ends with a '/'
|
||||||
|
def base_topic_arg(s):
|
||||||
|
s = str(s)
|
||||||
|
if not s.endswith('/'):
|
||||||
|
s = s + '/'
|
||||||
|
return s
|
||||||
|
|
||||||
|
# specify arguments
|
||||||
|
parser.add_argument('-l', '--broker-host', type=str, required=False,
|
||||||
|
help='host name or ip address of the mqtt broker', default="127.0.0.1")
|
||||||
|
parser.add_argument('-p', '--broker-port', type=int, required=False,
|
||||||
|
help='port of the mqtt broker', default=1883)
|
||||||
|
parser.add_argument('-u', '--broker-username', type=str, required=False,
|
||||||
|
help='username used to authenticate with the mqtt broker')
|
||||||
|
parser.add_argument('-d', '--broker-password', type=str, required=False,
|
||||||
|
help='password used to authenticate with the mqtt broker')
|
||||||
|
parser.add_argument('-t', '--base-topic', type=base_topic_arg, required=False,
|
||||||
|
help='base topic of the homie devices on the broker', default="homie/")
|
||||||
|
parser.add_argument('-i', '--device-id', type=str, required=True,
|
||||||
|
help='homie device id')
|
||||||
|
parser.add_argument('firmware', type=argparse.FileType('rb'),
|
||||||
|
help='path to the firmware to be sent to the device')
|
||||||
|
|
||||||
|
parser.add_argument("--broker-tls-cacert", default=None, required=False,
|
||||||
|
help="CA certificate bundle used to validate TLS connections. If set, TLS will be enabled on the broker conncetion"
|
||||||
|
)
|
||||||
|
|
||||||
|
# workaround for http://bugs.python.org/issue9694
|
||||||
|
parser._optionals.title = "arguments"
|
||||||
|
|
||||||
|
# get and validate arguments
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# read the contents of firmware into buffer
|
||||||
|
fw_buffer = args.firmware.read()
|
||||||
|
args.firmware.close()
|
||||||
|
firmware = bytearray()
|
||||||
|
firmware.extend(fw_buffer)
|
||||||
|
|
||||||
|
# Invoke the business logic
|
||||||
|
main(args.broker_host, args.broker_port, args.broker_username,
|
||||||
|
args.broker_password, args.broker_tls_cacert, args.base_topic, args.device_id, firmware)
|
11
host/spelling.sh
Executable file
11
host/spelling.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
whereis -b codespell | grep "/codespell" >> /dev/null
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "codespell needs to be installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
codespell -w ../src/*
|
||||||
|
codespell -w ../include/*
|
||||||
|
codespell ../Readme.md
|
||||||
|
exit 0
|
25
host/upload-via-mqtt.sh
Executable file
25
host/upload-via-mqtt.sh
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
#!//bin/bash
|
||||||
|
|
||||||
|
if [ $# -ne 3 ]; then
|
||||||
|
echo "Homie prefex and device index must be specified:"
|
||||||
|
echo "$0 <mqtt host> <prefix> <device index>"
|
||||||
|
echo "e.g."
|
||||||
|
echo "$0 192.168.0.2 test/ MyDeviceId"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mqttHost=$1
|
||||||
|
mqttPrefix=$2
|
||||||
|
homieId=$3
|
||||||
|
firmwareFile=../.pio/build/nodemcuv2/firmware.bin
|
||||||
|
|
||||||
|
if [ ! -f $firmwareFile ]; then
|
||||||
|
echo "the script $0 must be started in host/ sub directory"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Waiting for $homieId ..."
|
||||||
|
mosquitto_sub -h $mqttHost -t "${mqttPrefix}${homieId}/#" -R -C 1
|
||||||
|
python ota_updater.py -l $mqttHost -t "$mqttPrefix" -i "$homieId" $firmwareFile
|
||||||
|
|
||||||
|
exit 0
|
18
include/HomieSettings.h
Normal file
18
include/HomieSettings.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* @file HomieSettings.h
|
||||||
|
* @author your name (you@domain.com)
|
||||||
|
* @brief Air quality sensor
|
||||||
|
* @version 0.1
|
||||||
|
* @date 2021-11-05
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HOMIE_SETTINGS
|
||||||
|
#define HOMIE_SETTINGS
|
||||||
|
|
||||||
|
#define HOMIE_FIRMWARE_NAME "Vindriktning"
|
||||||
|
#define HOMIE_FIRMWARE_VERSION "1.1.0"
|
||||||
|
|
||||||
|
#endif
|
21
platformio.ini
Normal file
21
platformio.ini
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
;PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:nodemcuv2]
|
||||||
|
platform = espressif8266
|
||||||
|
board = d1_mini
|
||||||
|
framework = arduino
|
||||||
|
build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
|
||||||
|
; the latest development branch (convention V3.0.x)
|
||||||
|
lib_deps = https://github.com/homieiot/homie-esp8266.git#develop
|
||||||
|
EspSoftwareSerial
|
||||||
|
NeoPixel
|
||||||
|
adafruit/Adafruit BMP280 Library @ ^2.4.2
|
||||||
|
|
333
src/main.cpp
Normal file
333
src/main.cpp
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
/**
|
||||||
|
* @file main.cpp
|
||||||
|
* @author Ollo
|
||||||
|
* @brief
|
||||||
|
* @version 0.1
|
||||||
|
* @date 2021-11-05
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* INCLUDES
|
||||||
|
******************************************************************************/
|
||||||
|
#include <Homie.h>
|
||||||
|
#include <SoftwareSerial.h>
|
||||||
|
#include "HomieSettings.h"
|
||||||
|
#include <Adafruit_NeoPixel.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <Adafruit_Sensor.h>
|
||||||
|
#include <Adafruit_BMP280.h>
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* DEFINES
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#define GPIO_WS2812 D4 /**< GPIO2 */
|
||||||
|
#define SENSOR_PM1006_RX D2 /**< GPIO4 */
|
||||||
|
#define SENSOR_PM1006_TX -1 /**< Unused */
|
||||||
|
#define WITTY_RGB_R D8 /**< GPIO15 */
|
||||||
|
#define WITTY_RGB_G D6 /**< GPIO12 */
|
||||||
|
#define WITTY_RGB_B D7 /**< GPIO13 */
|
||||||
|
#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 PIXEL_COUNT 3
|
||||||
|
#define GPIO_BUTTON SENSOR_PM1006_RX /**< Button and software serial share one pin on Witty board */
|
||||||
|
#define SENSOR_I2C_SCK D5 /**< GPIO14 - I2C clock 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 LOG_TOPIC "log\0"
|
||||||
|
#define MQTT_LOG_ERROR 1
|
||||||
|
#define MQTT_LOG_WARNING 10
|
||||||
|
#define MQTT_LOG_INFO 20
|
||||||
|
#define MQTT_LOG_DEBUG 90
|
||||||
|
|
||||||
|
#define getTopic(test, topic) \
|
||||||
|
char *topic = new char[strlen(Homie.getConfiguration().mqtt.baseTopic) + strlen(Homie.getConfiguration().deviceId) + 1 + strlen(test) + 1]; \
|
||||||
|
strcpy(topic, Homie.getConfiguration().mqtt.baseTopic); \
|
||||||
|
strcat(topic, Homie.getConfiguration().deviceId); \
|
||||||
|
strcat(topic, "/"); \
|
||||||
|
strcat(topic, test);
|
||||||
|
|
||||||
|
#define NUMBER_TYPE "Number"
|
||||||
|
#define NODE_PARTICE "particle"
|
||||||
|
#define NODE_TEMPERATUR "temp"
|
||||||
|
#define NODE_PRESSURE "pressure"
|
||||||
|
#define NODE_ALTITUDE "altitude"
|
||||||
|
/******************************************************************************
|
||||||
|
* TYPE DEFS
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* FUNCTION PROTOTYPES
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
void log(int level, String message, int code);
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* LOCAL VARIABLES
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
bool mConfigured = false;
|
||||||
|
bool mConnected = false;
|
||||||
|
|
||||||
|
HomieNode particle(NODE_PARTICE, "particle", "Particle"); /**< Measuret in micro gram per quibik meter air volume */
|
||||||
|
HomieNode temperatureNode(NODE_TEMPERATUR, "Room Temperature", "Room Temperature");
|
||||||
|
HomieNode pressureNode(NODE_PRESSURE, "Pressure", "Room Pressure");
|
||||||
|
HomieNode altitudeNode(NODE_ALTITUDE, "Altitude", "Room altitude");
|
||||||
|
|
||||||
|
HomieSetting<bool> i2cEnable("i2c", "BMP280 sensor present");
|
||||||
|
|
||||||
|
static SoftwareSerial pmSerial(SENSOR_PM1006_RX, SENSOR_PM1006_TX);
|
||||||
|
Adafruit_BMP280 bmp; // connected via I2C
|
||||||
|
|
||||||
|
Adafruit_NeoPixel strip(PIXEL_COUNT, GPIO_WS2812, NEO_GRB + NEO_KHZ800);
|
||||||
|
|
||||||
|
// Variablen
|
||||||
|
uint8_t serialRxBuf[80];
|
||||||
|
uint8_t rxBufIdx = 0;
|
||||||
|
int spm25 = 0;
|
||||||
|
int last = 0;
|
||||||
|
unsigned int mButtonPressed = 0;
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* LOCAL FUNCTIONS
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the Sensor Data from software serial
|
||||||
|
*
|
||||||
|
* @return int PM25 value
|
||||||
|
*/
|
||||||
|
int getSensorData() {
|
||||||
|
uint8_t rxBufIdx = 0;
|
||||||
|
uint8_t checksum = 0;
|
||||||
|
|
||||||
|
// Sensor Serial aushorchen
|
||||||
|
while ((pmSerial.available() && rxBufIdx < 127) || rxBufIdx < 20)
|
||||||
|
{
|
||||||
|
serialRxBuf[rxBufIdx++] = pmSerial.read();
|
||||||
|
delay(15);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate checksum
|
||||||
|
for (uint8_t i = 0; i < 20; i++)
|
||||||
|
{
|
||||||
|
checksum += serialRxBuf[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Debug Print of received bytes */
|
||||||
|
String dbgBuffer = String("PM1006: ");
|
||||||
|
for (uint8_t i = 0; i < 20; i++)
|
||||||
|
{
|
||||||
|
dbgBuffer += String(serialRxBuf[i], 16);
|
||||||
|
}
|
||||||
|
dbgBuffer += String("check: " + String(checksum, 16));
|
||||||
|
log(MQTT_LOG_DEBUG, String(dbgBuffer), 1);
|
||||||
|
|
||||||
|
// Header und Prüfsumme checken
|
||||||
|
if (serialRxBuf[0] == 0x16 && serialRxBuf[1] == 0x11 && serialRxBuf[2] == 0x0B /* && checksum == 0 */)
|
||||||
|
{
|
||||||
|
return (serialRxBuf[5] << 8 | serialRxBuf[6]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle events of the Homie platform
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
void onHomieEvent(const HomieEvent &event)
|
||||||
|
{
|
||||||
|
switch (event.type)
|
||||||
|
{
|
||||||
|
case HomieEventType::MQTT_READY:
|
||||||
|
mConnected=true;
|
||||||
|
digitalWrite(WITTY_RGB_R, LOW);
|
||||||
|
if (!i2cEnable.get()) { /** keep green LED activated to power I2C sensor */
|
||||||
|
digitalWrite(WITTY_RGB_G, LOW);
|
||||||
|
}
|
||||||
|
digitalWrite(WITTY_RGB_B, LOW);
|
||||||
|
strip.fill(strip.Color(0,0,128));
|
||||||
|
strip.show();
|
||||||
|
break;
|
||||||
|
case HomieEventType::READY_TO_SLEEP:
|
||||||
|
break;
|
||||||
|
case HomieEventType::OTA_STARTED:
|
||||||
|
break;
|
||||||
|
case HomieEventType::OTA_SUCCESSFUL:
|
||||||
|
ESP.restart();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bmpPublishValues() {
|
||||||
|
temperatureNode.setProperty(NODE_TEMPERATUR).send(String(bmp.readTemperature()));
|
||||||
|
pressureNode.setProperty(NODE_PRESSURE).send(String(bmp.readPressure() / 100.0F));
|
||||||
|
altitudeNode.setProperty(NODE_ALTITUDE).send(String(bmp.readAltitude(SEALEVELPRESSURE_HPA)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Main loop, triggered by the Homie API
|
||||||
|
* All logic needs to be done here.
|
||||||
|
*
|
||||||
|
* The ranges are defined as followed:
|
||||||
|
* - green: 0-35 (good+low)
|
||||||
|
* - orange: 36-85 (OK+medicore)
|
||||||
|
* - red: 86-... (bad+high)
|
||||||
|
*
|
||||||
|
* source @link{https://github.com/Hypfer/esp8266-vindriktning-particle-sensor/issues/16#issuecomment-903116056}
|
||||||
|
*/
|
||||||
|
void loopHandler()
|
||||||
|
{
|
||||||
|
static long lastRead = 0;
|
||||||
|
if ((millis() - lastRead) > PM1006_MQTT_UPDATE) {
|
||||||
|
int pM25 = getSensorData();
|
||||||
|
if (pM25 >= 0) {
|
||||||
|
particle.setProperty("particle").send(String(pM25));
|
||||||
|
if (pM25 < 35) {
|
||||||
|
strip.fill(strip.Color(0, 255, 0)); /* green */
|
||||||
|
} else if (pM25 < 85) {
|
||||||
|
strip.fill(strip.Color(255, 127, 0)); /* orange */
|
||||||
|
} else {
|
||||||
|
strip.fill(strip.Color(255, 0, 0)); /* red */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strip.show();
|
||||||
|
|
||||||
|
/* Read BOSCH sensor */
|
||||||
|
bmpPublishValues();
|
||||||
|
|
||||||
|
lastRead = millis();
|
||||||
|
}
|
||||||
|
// Feed the dog -> ESP stay alive
|
||||||
|
ESP.wdtFeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* GLOBAL FUNCTIONS
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
SPIFFS.begin();
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.setTimeout(2000);
|
||||||
|
pinMode(WITTY_RGB_R, OUTPUT);
|
||||||
|
pinMode(WITTY_RGB_G, OUTPUT);
|
||||||
|
pinMode(WITTY_RGB_B, OUTPUT);
|
||||||
|
digitalWrite(WITTY_RGB_R, LOW);
|
||||||
|
digitalWrite(WITTY_RGB_G, LOW);
|
||||||
|
digitalWrite(WITTY_RGB_B, LOW);
|
||||||
|
|
||||||
|
Homie_setFirmware(HOMIE_FIRMWARE_NAME, HOMIE_FIRMWARE_VERSION);
|
||||||
|
Homie.setLoopFunction(loopHandler);
|
||||||
|
Homie.onEvent(onHomieEvent);
|
||||||
|
i2cEnable.setDefaultValue(false);
|
||||||
|
memset(serialRxBuf, 0, 80);
|
||||||
|
|
||||||
|
pmSerial.begin(PM1006_BIT_RATE);
|
||||||
|
Homie.setup();
|
||||||
|
|
||||||
|
particle.advertise("particle").setName("Particle").setDatatype(NUMBER_TYPE).setUnit("micro gram per quibik");
|
||||||
|
temperatureNode.advertise(NODE_TEMPERATUR).setName("Degrees")
|
||||||
|
.setDatatype("float")
|
||||||
|
.setUnit("ºC");
|
||||||
|
pressureNode.advertise(NODE_PRESSURE).setName("Pressure")
|
||||||
|
.setDatatype("float")
|
||||||
|
.setUnit("hPa");
|
||||||
|
altitudeNode.advertise(NODE_ALTITUDE).setName("Altitude")
|
||||||
|
.setDatatype("float")
|
||||||
|
.setUnit("m");
|
||||||
|
|
||||||
|
|
||||||
|
strip.begin();
|
||||||
|
/* activate I2C for BOSCH sensor */
|
||||||
|
Wire.begin(SENSOR_I2C_SDI, SENSOR_I2C_SCK);
|
||||||
|
|
||||||
|
mConfigured = Homie.isConfigured();
|
||||||
|
digitalWrite(WITTY_RGB_G, HIGH);
|
||||||
|
if (mConfigured)
|
||||||
|
{
|
||||||
|
if (i2cEnable.get()) {
|
||||||
|
strip.fill(strip.Color(0,128,0));
|
||||||
|
strip.show();
|
||||||
|
/* Extracted from library's example */
|
||||||
|
if (!bmp.begin()) {
|
||||||
|
Serial.println(F("Could not find a valid BMP280 sensor, check wiring or "
|
||||||
|
"try a different address!"));
|
||||||
|
while (1) delay(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Default settings from datasheet. */
|
||||||
|
bmp.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. */
|
||||||
|
}
|
||||||
|
strip.fill(strip.Color(0,0,0));
|
||||||
|
for (int i=0;i < (PIXEL_COUNT / 2); i++) {
|
||||||
|
strip.setPixelColor(0, strip.Color(0,0,128));
|
||||||
|
}
|
||||||
|
strip.show();
|
||||||
|
digitalWrite(WITTY_RGB_B, HIGH);
|
||||||
|
} else {
|
||||||
|
strip.fill(strip.Color(128,0,0));
|
||||||
|
for (int i=0;i < (PIXEL_COUNT / 2); i++) {
|
||||||
|
strip.setPixelColor(0, strip.Color(0,0,128));
|
||||||
|
}
|
||||||
|
strip.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
Homie.loop();
|
||||||
|
/* use the pin, receiving the soft serial additionally as button */
|
||||||
|
if (digitalRead(GPIO_BUTTON) == LOW) {
|
||||||
|
mButtonPressed++;
|
||||||
|
} else {
|
||||||
|
mButtonPressed=0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mButtonPressed > 10000U) {
|
||||||
|
mButtonPressed=0U;
|
||||||
|
if (SPIFFS.exists("/homie/config.json")) {
|
||||||
|
printf("Resetting config\r\n");
|
||||||
|
SPIFFS.remove("/homie/config.json");
|
||||||
|
SPIFFS.end();
|
||||||
|
} else {
|
||||||
|
printf("No config present\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void log(int level, String message, int statusCode)
|
||||||
|
{
|
||||||
|
String buffer;
|
||||||
|
StaticJsonDocument<200> doc;
|
||||||
|
doc["level"] = level;
|
||||||
|
doc["message"] = message;
|
||||||
|
doc["statusCode"] = statusCode;
|
||||||
|
serializeJson(doc, buffer);
|
||||||
|
if (mConnected)
|
||||||
|
{
|
||||||
|
getTopic(LOG_TOPIC, logTopic)
|
||||||
|
|
||||||
|
Homie.getMqttClient().publish(logTopic, 2, false, buffer.c_str());
|
||||||
|
delete logTopic;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user