Compare commits

...

No commits in common. 'esphome' and 'split_files' have entirely different histories.

  1. 18
      .gitignore
  2. 39
      .gitlab-ci.yml
  3. 20
      LICENSE.md
  4. 91
      README.md
  5. 83
      common/base.yaml
  6. 27
      common/battery.yaml
  7. 8
      common/bh1750.yaml
  8. 45
      common/bme280.yaml
  9. 96
      common/bme680.yaml
  10. 238
      common/display-hendrik.yaml
  11. 294
      common/display.yaml
  12. 8
      common/experimental/ble.yaml
  13. 2
      common/experimental/webserver.yaml
  14. 15
      common/sds011.yaml
  15. 4
      common/secrets.yaml
  16. 19
      common/veml6075.yaml
  17. 43
      custom/veml6075_custom_sensor.h
  18. BIN
      fonts/Vera.ttf
  19. BIN
      fonts/VeraMono.ttf
  20. BIN
      fonts/materialdesignicons-webfont.ttf
  21. BIN
      fonts/slkscr.ttf
  22. 37
      fonts/weather_icon_map.h
  23. 15
      hendrik-test.yaml
  24. 61
      platformio.ini
  25. 13
      script/autoversioning.py
  26. 10
      secrets-example.yaml
  27. 11
      sensor-outdoor.yaml
  28. 9
      sensor-springer.yaml
  29. 8
      sensor-sz.yaml
  30. 8
      sensor-wz.yaml
  31. 16
      src/display/Display.cpp
  32. 14
      src/display/Display.h
  33. 57
      src/display/Display_D32_EPD.cpp
  34. 16
      src/display/Display_D32_EPD.h
  35. 20
      src/hardware.h
  36. 116
      src/main.cpp
  37. 115
      src/network/XD0MQTT.cpp
  38. 39
      src/network/XD0MQTT.h
  39. 137
      src/network/XD0OTA.cpp
  40. 84
      src/network/XD0OTA.h
  41. 56
      src/sensors/BME280.cpp
  42. 19
      src/sensors/BME280.h
  43. 34
      src/sensors/SDS011.cpp
  44. 18
      src/sensors/SDS011.h
  45. 11
      test/README

18
.gitignore

@ -0,0 +1,18 @@
.pio
# old platformio
.pioenvs
.piolibdeps
.clang_complete
.gcc-flags.json
# virtualenv
bin/
include/
lib/
local/
share/
# custom
version.txt
build/

39
.gitlab-ci.yml

@ -0,0 +1,39 @@
stages:
- build
- deploy
build:
stage: build
image: python:2.7
before_script:
- pip install -U platformio
- platformio update
script:
# - date +%s > build/version.txt
- platformio ci --lib="script" --project-conf platformio.ini --board=lolin_d32_pro --build-dir="build" --keep-build-dir
variables: {PLATFORMIO_CI_SRC: "src"}
artifacts:
paths:
- build/.pio/build/lolin_d32_pro/*.bin
- build/version.txt
deploy:
stage: deploy
image: alpine:latest
before_script:
- apk update && apk add openssh-client bash rsync
# - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- echo "${SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add - > /dev/null
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "${SSH_HOST_KEY}" > ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
script:
- scp build/.pio/build/lolin_d32_pro/firmware.bin "${SSH_USER_HOST}:${DEPLOY_PATH}"
- scp build/version.txt "${SSH_USER_HOST}:${DEPLOY_PATH}".version
dependencies:
- build
when: manual
only:
- master

20
LICENSE.md

@ -0,0 +1,20 @@
Copyright (c) 2019 Hendrik Langer
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

91
README.md

@ -1,12 +1,9 @@
# esp32-weatherstation2
# esp32-weatherstation
Wetterstation v2 with ESP32 and various sensors (with ESPHome!)
[![Build Status](https://drone.xd0.de/api/badges/hendrik/esp32-weatherstation/status.svg)](https://drone.xd0.de/hendrik/esp32-weatherstation)
Wetterstation v2 with ESP32 and various sensors
## Hardware
* ESP32 µC
* Sensors
## Wiring
@ -25,94 +22,44 @@ DC | IO27 |
RST | IO33 |
GND | |
### BME680
I2C
BME680 pin | ESP32 pin | Notes
-----------|-----------|----------
VCC | 3V |
GND | GND |
VCC | |
GND | |
SCL | IO22 |
SDA | IO21 |
SDO | nc | SPI only
CS | nc | SPI only
### BH1750 (GY-302)
I2C
GY-302 pin | ESP32 pin | Notes
-----------|-----------|----------
VCC | 3V |
GND | GND |
SCL | IO22 |
SDA | IO21 |
ADDR | nc | select addr: GND: 0x23 or VCC: 0x5C
### VEML6075
I2C
GY-VEML6075 pin | ESP32 pin | Notes
-----------|-----------|----------
VIN | 3V |
GND | GND |
SCL | IO22 |
SDA | IO21 |
### SDS011
Serial (HardwareSerial2)
SDS011 pin | ESP32 pin | Notes
-----------|-----------|----------
TXD | IO15 | (don't use IO16 together with PSRAM)
RXD | IO2 | (don't use IO17 together with PSRAM)
TXD | IO16 |
RXD | IO17 |
GND | GND |
25µm | nc |
5V | VUSB | 5V
5V | VUSB |
1µm | nc |
NC | nc |
## Build & Install
* Install ESPHome via Home-Assistant Supervisor
* Upload all needed files via the "File editor" into `config/esphome/` and subdirectories.
* rename secrets-example.yaml to secrets.yaml and fill in your credentials
* Choose your device and flash the firmware. Use ESPHome "Install" -> "Plug into this computer". (Subsequent updates can be done wirelessly.)
### Files
```
.
├── common (components, used by the devices)
│   ├── base.yaml
│   ├── battery.yaml
│   ├── bh1750.yaml
│   ├── bme280.yaml
│   ├── bme680.yaml
│   ├── display.yaml
│   ├── sds011.yaml
│   ├── secrets.yaml
│   ├── veml6075.yaml
│   └── experimental (experiments, skip these)
│      ├── ble.yaml
│      └── webserver.yaml
├── custom (used by custom components)
│   └── veml6075_custom_sensor.h
├── fonts (used by display component)
│   ├── materialdesignicons-webfont.ttf
│   ├── slkscr.ttf
│   ├── VeraMono.ttf
│   ├── Vera.ttf
│   └── weather_icon_map.h
├── secrets.yaml (edit example file)
│ (the individial device descriptions)
├── sensor-outdoor.yaml
├── sensor-springer.yaml
├── sensor-sz.yaml
└── sensor-wz.yaml
```bash
git clone https://dev.xd0.de/hendrik/esp32-weatherstation.git
cd esp32-weatherstation
virtualenv .
source bin/activate
pip install -U platformio
pio run
platformio run -t upload && platformio device monitor -b 115200
```
## Authors
@ -125,5 +72,7 @@ This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md
## Acknowledgments
* [ESPHome](https://esphome.io) - ESPHome is a system to control your ESP8266/ESP32 by simple yet powerful configuration files
* [Home Assistant](https://home-assistant.io) - Open source home automation that puts local control and privacy first
* [PlatformIO](https://platformio.org) - Cross-platform IDE
* [GxEPD2](https://github.com/ZinggJM/GxEPD2) - E-Paper display library
* [Adafruit BME680](https://github.com/adafruit/Adafruit_BME680) - BME680 sensor library
* [Nova Fitness SDS dust sensors arduino library](https://github.com/lewapek/sds-dust-sensors-arduino-library.git) - SDS011 Laser dust sensor library

83
common/base.yaml

@ -1,83 +0,0 @@
esphome:
name: ${node_name}
platform: ESP32
board: lolin_d32_pro
project:
name: "xd0.esp32weatherstation2"
version: "0.1.1"
includes:
- fonts/weather_icon_map.h
- custom/veml6075_custom_sensor.h
libraries:
- Wire
- SPI
- "https://github.com/adafruit/Adafruit_BusIO"
- "https://github.com/adafruit/Adafruit_VEML6075"
# platformio_options:
# lib_ldf_mode: chain+
# Enable logging
logger:
# level: VERY_VERBOSE
# Enable Home Assistant API
api:
ota:
password: !secret esphome_ota_password
wifi:
networks:
- ssid: !secret wifi_ssid
password: !secret wifi_passwd
- ssid: !secret wifi_ssid2
password: !secret wifi_passwd2
power_save_mode: light
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: ${node_name}
password: !secret wifi_captive_portal_password
captive_portal:
time:
- platform: sntp
id: sntp_time
timezone: "Europe/Berlin"
mqtt:
broker: home.xd0.de
username: !secret esphome_mqtt_user
password: !secret esphome_mqtt_passwd
discovery: false
discovery_retain: false
topic_prefix: "thomas"
log_topic:
topic: log"thomas/${node_name}/debug"
level: INFO
# bus
i2c:
sda: 21
scl: 22
scan: true
spi:
clk_pin: 18
mosi_pin: 23
miso_pin: 19
status_led:
pin:
number: GPIO5
inverted: true
sensor:
# WiFi signal strength
- platform: wifi_signal
name: "${node_name} RSSI"
update_interval: 60s
# Example hall sensor
# - platform: esp32_hall
# name: "${node_name} ESP32 Hall Sensor"
# update_interval: 60s

27
common/battery.yaml

@ -1,27 +0,0 @@
sensor:
# Battery level
- platform: adc
pin: _VBAT
name: "${node_name} VBAT Voltage"
id: voltage
attenuation: 11db
filters:
- multiply: 1.71
# filters:
# - lambda: return ( x/4096.0 ) * 2 * 3.42;
update_interval: 60s
- platform: template
name: "${node_name} Battery Level"
id: "battery_level"
unit_of_measurement: '%'
icon: "mdi:battery"
device_class: "battery"
state_class: "measurement"
accuracy_decimals: 1
update_interval: 60s
lambda: |-
return ((id(voltage).state -3) /1.2 * 100.00);
#deep_sleep:
# run_duration: 30s
# sleep_duration: 10min

8
common/bh1750.yaml

@ -1,8 +0,0 @@
# BH1750 Ambient Light Sensor
sensor:
- platform: bh1750
name: "${node_name} Illuminance"
id: lux
address: 0x23
measurement_duration: 69
update_interval: 60s

45
common/bme280.yaml

@ -1,45 +0,0 @@
# BME280 Temperature+Pressure+Humidity Sensor
sensor:
- platform: bme280
temperature:
name: "${node_name} Temperature"
id: temperature
pressure:
name: "${node_name} Pressure"
id: pressure
humidity:
name: "${node_name} Humidity"
id: humidity
address: 0x77
update_interval: 60s
- platform: template
name: "${node_name} Equivalent sea level pressure"
id: pressure_sealevel
lambda: |-
const float STANDARD_ALTITUDE = ${altitude}; // in meters, see note
return id(pressure).state / powf(1 - ((0.0065 * STANDARD_ALTITUDE) /
(id(temperature).state + (0.0065 * STANDARD_ALTITUDE) + 273.15)), 5.257); // in hPa
update_interval: 60s
unit_of_measurement: 'hPa'
- platform: template
name: "${node_name} Absolute Humidity"
lambda: |-
const float mw = 18.01534; // molar mass of water g/mol
const float r = 8.31447215; // Universal gas constant J/mol/K
return (6.112 * powf(2.718281828, (17.67 * id(temperature).state) /
(id(temperature).state + 243.5)) * id(humidity).state * mw) /
((273.15 + id(temperature).state) * r); // in grams/m^3
accuracy_decimals: 2
update_interval: 60s
icon: 'mdi:water'
unit_of_measurement: 'g/m³'
- platform: template
name: "${node_name} Dew Point"
lambda: |-
return (243.5*(log(id(humidity).state/100)+((17.67*id(temperature).state)/
(243.5+id(temperature).state)))/(17.67-log(id(humidity).state/100)-
((17.67*id(temperature).state)/(243.5+id(temperature).state))));
accuracy_decimals: 1
update_interval: 60s
unit_of_measurement: °C
icon: 'mdi:thermometer-alert'

96
common/bme680.yaml

@ -1,96 +0,0 @@
# BME680 Temperature+Pressure+Humidity+Gas Sensor
sensor:
- platform: bme680_bsec
temperature:
name: "${node_name} Temperature"
id: temperature
filters:
- median:
window_size: 12
send_every: 12
pressure:
name: "${node_name} Pressure"
id: pressure
filters:
- median:
window_size: 12
send_every: 12
humidity:
name: "${node_name} Humidity"
id: humidity
filters:
- median:
window_size: 12
send_every: 12
gas_resistance:
name: "${node_name} Gas Resistance"
id: voc
filters:
- median:
window_size: 12
send_every: 12
iaq:
name: "${node_name} IAQ"
id: iaq
filters:
- median:
window_size: 12
send_every: 12
iaq_accuracy:
name: "${node_name} Numeric IAQ Accuracy"
id: iaq_accuracy
filters:
- median:
window_size: 12
send_every: 12
co2_equivalent:
name: "${node_name} CO2 Equivalent"
filters:
- median:
window_size: 12
send_every: 12
breath_voc_equivalent:
name: "${node_name} Breath VOC Equivalent"
filters:
- median:
window_size: 12
send_every: 12
- platform: template
name: "${node_name} Equivalent sea level pressure"
id: pressure_sealevel
lambda: |-
const float STANDARD_ALTITUDE = ${altitude}; // in meters, see note
return id(pressure).state / powf(1 - ((0.0065 * STANDARD_ALTITUDE) /
(id(temperature).state + (0.0065 * STANDARD_ALTITUDE) + 273.15)), 5.257); // in hPa
update_interval: 60s
unit_of_measurement: 'hPa'
- platform: template
name: "${node_name} Absolute Humidity"
lambda: |-
const float mw = 18.01534; // molar mass of water g/mol
const float r = 8.31447215; // Universal gas constant J/mol/K
return (6.112 * powf(2.718281828, (17.67 * id(temperature).state) /
(id(temperature).state + 243.5)) * id(humidity).state * mw) /
((273.15 + id(temperature).state) * r); // in grams/m^3
accuracy_decimals: 2
update_interval: 60s
icon: 'mdi:water'
unit_of_measurement: 'g/m³'
- platform: template
name: "${node_name} Dew Point"
lambda: |-
return (243.5*(log(id(humidity).state/100)+((17.67*id(temperature).state)/
(243.5+id(temperature).state)))/(17.67-log(id(humidity).state/100)-
((17.67*id(temperature).state)/(243.5+id(temperature).state))));
accuracy_decimals: 1
update_interval: 60s
unit_of_measurement: °C
icon: 'mdi:thermometer-alert'
text_sensor:
- platform: bme680_bsec
iaq_accuracy:
name: "${node_name} IAQ Accuracy"
bme680_bsec:
address: 0x77

238
common/display-hendrik.yaml

@ -1,238 +0,0 @@
# display #250x122 pixels (250x128)
font:
- file: 'fonts/Vera.ttf'
id: font1
size: 10
glyphs: '!"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz/²³µΩ€[]?'
- file: 'fonts/Vera.ttf'
id: font2
size: 20
# - file: 'fonts/VeraMono.ttf'
# id: font3
# size: 10
- file: 'fonts/materialdesignicons-webfont.ttf'
id: icon_font
size: 48
glyphs:
- "\U000F0590" # weather-cloudy
- "\U000F0F2F" # weather-cloudy-alert
- "\U000F0E6E" # weather-cloudy-arrow-right
- "\U000F0591" # weather-fog
- "\U000F0592" # weather-hail
- "\U000F0F30" # weather-hazy
- "\U000F0898" # weather-hurricane
- "\U000F0593" # weather-lightning
- "\U000F067E" # weather-lightning-rainy
- "\U000F0594" # weather-night
- "\U000F0F31" # weather-night-partly-cloudy
- "\U000F0595" # weather-partly-cloudy
- "\U000F0F32" # weather-partly-lightning
- "\U000F0F33" # weather-partly-rainy
- "\U000F0F34" # weather-partly-snowy
- "\U000F0F35" # weather-partly-snowy-rainy
- "\U000F0596" # weather-pouring
- "\U000F0597" # weather-rainy
- "\U000F0598" # weather-snowy
- "\U000F0F36" # weather-snowy-heavy
- "\U000F067F" # weather-snowy-rainy
- "\U000F0599" # weather-sunny
- "\U000F0F37" # weather-sunny-alert
- "\U000F14E4" # weather-sunny-off
- "\U000F059A" # weather-sunset
- "\U000F059B" # weather-sunset-down
- "\U000F059C" # weather-sunset-up
- "\U000F0F38" # weather-tornado
- "\U000F059D" # weather-windy
- "\U000F059E" # weather-windy-variant
display:
- platform: waveshare_epaper
cs_pin: 14
dc_pin: 27
reset_pin: 33
model: 2.13in-ttgo
rotation: 90
update_interval: 180s
full_update_every: 1
id: my_display
pages:
- id: page1
lambda: |-
it.print(10, 50, id(font2), "esp32weatherstation2");
- id: page2
lambda: |-
// Title
it.filled_rectangle(0,0,250,20, COLOR_ON);
it.print(5, 6, id(font1), COLOR_OFF, "${node_name}");
it.strftime(150, 6, id(font1), COLOR_OFF, "%Y-%m-%d %H:%M", id(sntp_time).now());
// Weather icon
if (id(weather_icon).has_state()) {
it.printf(0, 18, id(icon_font), weather_icon_map[id(weather_icon).state.c_str()].c_str());
}
it.printf(55, 25, id(font1), "T: %.1f°C", id(temperature_outside).state);
it.printf(55, 35, id(font1), "H: %.1f%%", id(humidity_outside).state);
it.printf(55, 45, id(font1), "%.0fmm / %.0f%%", id(precipitation).state, id(precipitation_probability).state);
// own measurements
it.filled_rectangle(0, 65, 59, 33, COLOR_ON);
it.printf(2, 65+4, id(font2), COLOR_OFF, "%.1f", id(temperature).state); it.printf(45, 65+4, id(font1), COLOR_OFF, "°C");
it.filled_rectangle(60, 65, 59, 33, COLOR_ON);
it.printf(62, 65+4, id(font2), COLOR_OFF, "%.1f", id(humidity).state); it.printf(105, 65+4, id(font1), COLOR_OFF, "%%");
it.filled_rectangle(120, 65, 59, 33, COLOR_ON);
it.printf(122, 65+4, id(font2), COLOR_OFF, "%.0f", id(pressure_sealevel).state); it.printf(178, 87, id(font1), COLOR_OFF, TextAlign::TOP_RIGHT, "hPa");
// humidity human readable
if (id(humidity).state < 30) {
it.printf(116, 87, id(font1), COLOR_OFF, TextAlign::TOP_RIGHT, "low");
} else if (id(humidity).state < 60) {
it.printf(116, 87, id(font1), COLOR_OFF, TextAlign::TOP_RIGHT, "comfort");
} else {
it.printf(116, 87, id(font1), COLOR_OFF, TextAlign::TOP_RIGHT, "high");
}
// graphs
it.graph(0, 98, id(temperature_graph));
it.graph(60, 98, id(humidity_graph));
it.graph(120, 98, id(pressure_graph));
// voc
it.circle(130, 42, 20);
it.line(115, 42, 145, 42);
it.filled_rectangle(130-10, 21, 20, 10, COLOR_OFF);
it.print(130, 26, id(font1), TextAlign::CENTER, "VOC");
if (id(iaq_accuracy).state >= 2) {
it.printf(130, 41, id(font1), TextAlign::BASELINE_CENTER, "%.0f", id(iaq).state);
if (id(iaq).state < 50) {
it.printf(130, 43, id(font1), TextAlign::TOP_CENTER, "excellent");
} else if (id(iaq).state < 100) {
it.printf(130, 43, id(font1), TextAlign::TOP_CENTER, "fine");
} else if (id(iaq).state < 150) {
it.printf(130, 43, id(font1), TextAlign::TOP_CENTER, "moderate");
} else if (id(iaq).state < 200) {
it.printf(130, 43, id(font1), TextAlign::TOP_CENTER, "poor");
} else if (id(iaq).state < 300) {
it.printf(130, 43, id(font1), TextAlign::TOP_CENTER, "very poor");
} else if (id(iaq).state < 500) {
it.printf(130, 43, id(font1), TextAlign::TOP_CENTER, "severe");
}
} else {
it.printf(130, 41, id(font1), TextAlign::BASELINE_CENTER, "%.0f \u03A9", id(voc).state);
it.printf(130, 43, id(font1), TextAlign::TOP_CENTER, "VOC");
}
// climate.wz
it.circle(175, 42, 20);
it.line(160, 42, 190, 42);
it.filled_rectangle(175-10, 21, 20, 10, COLOR_OFF);
it.print(175, 26, id(font1), TextAlign::CENTER, "WZ");
it.printf(175, 41, id(font1), TextAlign::BASELINE_CENTER, "%.1f°C", id(temp_set_wz).state);
it.printf(175, 43, id(font1), TextAlign::TOP_CENTER, "%.1f°C", id(temp_wz).state);
// Benzinpreis
it.rectangle(200, 22, 40, 40);
it.line(205, 42, 235, 42);
it.filled_rectangle(220-10, 21, 20, 10, COLOR_OFF);
it.print(220, 26, id(font1), TextAlign::CENTER, "E5");
it.printf(220, 41, id(font1), TextAlign::BASELINE_CENTER, "%.3f€", id(benzinpreis).state);
it.printf(220, 43, id(font1), TextAlign::TOP_CENTER, "%.3f€", id(benzinpreis2).state);
// other values
it.print(184, 75, id(font1), "UVI:"); it.printf(248, 75, id(font1), TextAlign::TOP_RIGHT, "%.2f", id(uvi_outdoor).state);
it.print(184, 85, id(font1), "YNH:"); it.printf(248, 85, id(font1), TextAlign::TOP_RIGHT, "%.2f", id(yunohost_load).state);
// Springer
it.print(184, 105, id(font1), "Bat:");
if (id(battery_level).state >=0 && id(battery_level).state <= 100) {
it.printf(248, 105, id(font1), TextAlign::TOP_RIGHT, "%.0f%%", id(battery_level).state);
} else {
it.printf(248, 105, id(font1), TextAlign::TOP_RIGHT, "nc");
}
interval:
- interval: 30s
then:
- if:
condition:
- display.is_displaying_page: page1
then:
- display.page.show: page2
- component.update: my_display
graph:
# Show bare-minimum auto-ranged graph
- id: temperature_graph
duration: 1h
width: 59
height: 31
traces:
- sensor: temperature
line_type: SOLID
line_thickness: 2
# - sensor: temperature_outside
# line_type: DASHED
# line_thickness: 1
- id: humidity_graph
sensor: humidity
duration: 1h
width: 59
height: 31
- id: pressure_graph
sensor: pressure
duration: 1h
width: 59
height: 31
# Example configuration entry
sensor:
- platform: homeassistant
name: "Temperature Outdoor"
internal: true
entity_id: weather.openweathermap
attribute: temperature
id: temperature_outside
- platform: homeassistant
name: "Humidity Outdoor"
internal: true
entity_id: weather.openweathermap
attribute: humidity
id: humidity_outside
- platform: homeassistant
name: "Precipitation Propability Forecast"
internal: true
entity_id: sensor.openweathermap_forecast_precipitation
id: precipitation
- platform: homeassistant
name: "Precipitation Probability Forecast"
internal: true
entity_id: sensor.openweathermap_forecast_precipitation_probability
id: precipitation_probability
- platform: homeassistant
name: "UVI Outdoor"
internal: true
entity_id: sensor.openweathermap_uv_index
id: uvi_outdoor
- platform: homeassistant
name: "WZ Temp"
internal: true
entity_id: climate.leq1333417
attribute: current_temperature
id: temp_wz
- platform: homeassistant
name: "WZ Temp Set"
internal: true
entity_id: climate.leq1333417
attribute: temperature
id: temp_set_wz
- platform: homeassistant
name: "Benzinpreis"
internal: true
entity_id: sensor.tankerkoenig_sb_essen_frankenstrasse_74_e5
id: benzinpreis
- platform: homeassistant
name: "Benzinpreis2"
internal: true
entity_id: sensor.tankerkoenig_total_essen_e5
id: benzinpreis2
- platform: homeassistant
name: "Yunohost Load"
internal: true
entity_id: sensor.yunohost_load
id: yunohost_load
text_sensor:
- platform: homeassistant
id: weather_icon
internal: true
entity_id: weather.openweathermap

294
common/display.yaml

@ -1,294 +0,0 @@
# display #250x122 pixels (250x128)
font:
- file: 'fonts/Vera.ttf'
id: font1
size: 10
glyphs: '!"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz/²³µΩ€[]?'
- file: 'fonts/Vera.ttf'
id: font2
size: 20
# - file: 'fonts/VeraMono.ttf'
# id: font3
# size: 10
- file: 'fonts/materialdesignicons-webfont.ttf'
id: icon_font
size: 48
glyphs:
- "\U000F0590" # weather-cloudy
- "\U000F0F2F" # weather-cloudy-alert
- "\U000F0E6E" # weather-cloudy-arrow-right
- "\U000F0591" # weather-fog
- "\U000F0592" # weather-hail
- "\U000F0F30" # weather-hazy
- "\U000F0898" # weather-hurricane
- "\U000F0593" # weather-lightning
- "\U000F067E" # weather-lightning-rainy
- "\U000F0594" # weather-night
- "\U000F0F31" # weather-night-partly-cloudy
- "\U000F0595" # weather-partly-cloudy
- "\U000F0F32" # weather-partly-lightning
- "\U000F0F33" # weather-partly-rainy
- "\U000F0F34" # weather-partly-snowy
- "\U000F0F35" # weather-partly-snowy-rainy
- "\U000F0596" # weather-pouring
- "\U000F0597" # weather-rainy
- "\U000F0598" # weather-snowy
- "\U000F0F36" # weather-snowy-heavy
- "\U000F067F" # weather-snowy-rainy
- "\U000F0599" # weather-sunny
- "\U000F0F37" # weather-sunny-alert
- "\U000F14E4" # weather-sunny-off
- "\U000F059A" # weather-sunset
- "\U000F059B" # weather-sunset-down
- "\U000F059C" # weather-sunset-up
- "\U000F0F38" # weather-tornado
- "\U000F059D" # weather-windy
- "\U000F059E" # weather-windy-variant
display:
- platform: waveshare_epaper
cs_pin: 14
dc_pin: 27
reset_pin: 33
model: 2.13in-ttgo
rotation: 90
update_interval: 180s
full_update_every: 1
id: my_display
pages:
- id: page1
lambda: |-
it.print(10, 50, id(font2), "esp32weatherstation2");
- id: page2
lambda: |-
// Title
it.filled_rectangle(0,0,250,20, COLOR_ON);
it.print(5, 6, id(font1), COLOR_OFF, "${node_name}");
it.strftime(150, 6, id(font1), COLOR_OFF, "%Y-%m-%d %H:%M", id(sntp_time).now());
// Weather icon
if (id(weather_icon).has_state()) {
it.printf(0, 18, id(icon_font), weather_icon_map[id(weather_icon).state.c_str()].c_str());
}
it.printf(55, 25, id(font1), "T: %.1f°C", id(temperature_outside).state);
it.printf(55, 35, id(font1), "H: %.1f%%", id(humidity_outside).state);
it.printf(55, 45, id(font1), "%s", id(weather_icon).state.c_str());
// own measurements
it.filled_rectangle(0, 65, 59, 33, COLOR_ON);
it.printf(2, 65+4, id(font2), COLOR_OFF, "%.1f", id(temperature).state); it.printf(45, 65+4, id(font1), COLOR_OFF, "°C");
it.filled_rectangle(60, 65, 59, 33, COLOR_ON);
it.printf(62, 65+4, id(font2), COLOR_OFF, "%.1f", id(humidity).state); it.printf(105, 65+4, id(font1), COLOR_OFF, "%%");
it.filled_rectangle(120, 65, 59, 33, COLOR_ON);
it.printf(122, 65+4, id(font2), COLOR_OFF, "%.0f", id(pressure_sealevel).state); it.printf(178, 87, id(font1), COLOR_OFF, TextAlign::TOP_RIGHT, "hPa");
// humidity human readable
if (id(humidity).state < 30) {
it.printf(116, 87, id(font1), COLOR_OFF, TextAlign::TOP_RIGHT, "low");
} else if (id(humidity).state < 60) {
it.printf(116, 87, id(font1), COLOR_OFF, TextAlign::TOP_RIGHT, "comfort");
} else {
it.printf(116, 87, id(font1), COLOR_OFF, TextAlign::TOP_RIGHT, "high");
}
// graphs
it.graph(0, 98, id(temperature_graph));
it.graph(60, 98, id(humidity_graph));
it.graph(120, 98, id(pressure_graph));
// voc
it.circle(130, 42, 20);
it.line(115, 42, 145, 42);
it.filled_rectangle(130-10, 21, 20, 10, COLOR_OFF);
it.print(130, 26, id(font1), TextAlign::CENTER, "WZ");
if (id(iaq_accuracy_wz).state >= 2) {
it.printf(130, 41, id(font1), TextAlign::BASELINE_CENTER, "%.0f", id(iaq_wz).state);
if (id(iaq_wz).state < 50) {
it.printf(130, 43, id(font1), TextAlign::TOP_CENTER, "excellent");
} else if (id(iaq_wz).state < 100) {
it.printf(130, 43, id(font1), TextAlign::TOP_CENTER, "fine");
} else if (id(iaq_wz).state < 150) {
it.printf(130, 43, id(font1), TextAlign::TOP_CENTER, "moderate");
} else if (id(iaq_wz).state < 200) {
it.printf(130, 43, id(font1), TextAlign::TOP_CENTER, "poor");
} else if (id(iaq_wz).state < 300) {
it.printf(130, 43, id(font1), TextAlign::TOP_CENTER, "very poor");
} else if (id(iaq_wz).state < 500) {
it.printf(130, 43, id(font1), TextAlign::TOP_CENTER, "severe");
}
} else {
it.printf(130, 41, id(font1), TextAlign::BASELINE_CENTER, "%.0f \u03A9", id(voc_wz).state);
it.printf(130, 43, id(font1), TextAlign::TOP_CENTER, "VOC");
}
// voc2
it.circle(175, 42, 20);
it.line(160, 42, 190, 42);
it.filled_rectangle(175-10, 21, 20, 10, COLOR_OFF);
it.print(175, 26, id(font1), TextAlign::CENTER, "SZ");
if (id(iaq_accuracy_sz).state >= 2) {
it.printf(175, 41, id(font1), TextAlign::BASELINE_CENTER, "%.0f", id(iaq_sz).state);
if (id(iaq_sz).state < 50) {
it.printf(175, 43, id(font1), TextAlign::TOP_CENTER, "excellent");
} else if (id(iaq_sz).state < 100) {
it.printf(175, 43, id(font1), TextAlign::TOP_CENTER, "fine");
} else if (id(iaq_sz).state < 150) {
it.printf(175, 43, id(font1), TextAlign::TOP_CENTER, "moderate");
} else if (id(iaq_sz).state < 200) {
it.printf(175, 43, id(font1), TextAlign::TOP_CENTER, "poor");
} else if (id(iaq_sz).state < 300) {
it.printf(175, 43, id(font1), TextAlign::TOP_CENTER, "very poor");
} else if (id(iaq_sz).state < 500) {
it.printf(175, 43, id(font1), TextAlign::TOP_CENTER, "severe");
}
} else {
it.printf(175, 41, id(font1), TextAlign::BASELINE_CENTER, "%.0f \u03A9", id(voc_sz).state);
it.printf(175, 43, id(font1), TextAlign::TOP_CENTER, "VOC");
}
// Particulate Matter Sensor
it.rectangle(200, 22, 40, 40); // it.circle(220, 42, 20);
it.line(205, 42, 235, 42);
it.filled_rectangle(220-10, 21, 20, 10, COLOR_OFF);
it.print(220, 26, id(font1), TextAlign::CENTER, "PM");
it.printf(220, 41, id(font1), TextAlign::BASELINE_CENTER, "%.0f", id(pm2_outdoor).state);
it.printf(220, 43, id(font1), TextAlign::TOP_CENTER, "%.0f", id(pm10_outdoor).state);
// other values
it.print(184, 65, id(font1), "Lux:"); it.printf(248, 65, id(font1), TextAlign::TOP_RIGHT, "%.0f lx", id(lux_outdoor).state);
it.print(184, 75, id(font1), "UVI:"); it.printf(248, 75, id(font1), TextAlign::TOP_RIGHT, "%.0f", id(uvi_outdoor).state);
it.print(184, 85, id(font1), "UVA:"); it.printf(248, 85, id(font1), TextAlign::TOP_RIGHT, "%.0f", id(uva_outdoor).state);
it.print(184, 95, id(font1), "UVB:"); it.printf(248, 95, id(font1), TextAlign::TOP_RIGHT, "%.0f", id(uvb_outdoor).state);
// Springer
it.print(184, 105, id(font1), "Springer");
if (id(battery_springer).state >=0 && id(battery_springer).state <= 100) {
it.printf(248, 105, id(font1), TextAlign::TOP_RIGHT, "%.0f%%", id(battery_springer).state);
} else {
it.printf(248, 105, id(font1), TextAlign::TOP_RIGHT, "nc");
}
it.printf(184,115, id(font1), "%.1f°C, %.1f%%H, %.0fhPa", id(temperature_springer).state, id(humidity_springer).state, id(pressure_springer).state);
interval:
- interval: 30s
then:
- if:
condition:
- display.is_displaying_page: page1
then:
- display.page.show: page2
- component.update: my_display
graph:
# Show bare-minimum auto-ranged graph
- id: temperature_graph
duration: 1h
width: 59
height: 31
traces:
- sensor: temperature
line_type: SOLID
line_thickness: 2
# - sensor: temperature_outside
# line_type: DASHED
# line_thickness: 1
- id: humidity_graph
sensor: humidity
duration: 1h
width: 59
height: 31
- id: pressure_graph
sensor: pressure
duration: 1h
width: 59
height: 31
# Example configuration entry
sensor:
- platform: homeassistant
name: "Temperature Outdoor"
internal: true
entity_id: sensor.sensor_outdoor_temperature
id: temperature_outside
- platform: homeassistant
name: "Humidity Outdoor"
internal: true
entity_id: sensor.sensor_outdoor_humidity
id: humidity_outside
- platform: homeassistant
name: "WZ VOC"
internal: true
entity_id: sensor.sensor_wz_gas_resistance
id: voc_wz
- platform: homeassistant
name: "SZ VOC"
internal: true
entity_id: sensor.sensor_sz_gas_resistance
id: voc_sz
- platform: homeassistant
name: "WZ IAQ"
internal: true
entity_id: sensor.sensor_wz_iaq
id: iaq_wz
- platform: homeassistant
name: "SZ IAQ"
internal: true
entity_id: sensor.sensor_sz_iaq
id: iaq_sz
- platform: homeassistant
name: "WZ IAQ Accuracy"
internal: true
entity_id: sensor.sensor_wz_iaq_accuracy
id: iaq_accuracy_wz
- platform: homeassistant
name: "SZ IAQ Accuracy"
internal: true
entity_id: sensor.sensor_sz_iaq_accuracy
id: iaq_accuracy_sz
- platform: homeassistant
name: "PM2.5 Outdoor"
internal: true
entity_id: sensor.sensor_outdoor_particulate_matter_2_5um_concentration
id: pm2_outdoor
- platform: homeassistant
name: "PM10 Outdoor"
internal: true
entity_id: sensor.sensor_outdoor_particulate_matter_10_0um_concentration
id: pm10_outdoor
- platform: homeassistant
name: "UVI Outdoor"
internal: true
entity_id: sensor.sensor_outdoor_uvi
id: uvi_outdoor
- platform: homeassistant
name: "UVA Outdoor"
internal: true
entity_id: sensor.sensor_outdoor_uva
id: uva_outdoor
- platform: homeassistant
name: "UVB Outdoor"
internal: true
entity_id: sensor.sensor_outdoor_uvb
id: uvb_outdoor
- platform: homeassistant
name: "Illuminance Outdoor"
internal: true
entity_id: sensor.sensor_outdoor_illuminance
id: lux_outdoor
- platform: homeassistant
name: "Temperature Springer"
internal: true
entity_id: sensor.sensor_springer_temperature
id: temperature_springer
- platform: homeassistant
name: "Humidity Springer"
internal: true
entity_id: sensor.sensor_springer_humidity
id: humidity_springer
- platform: homeassistant
name: "Pressure Springer"
internal: true
entity_id: sensor.sensor_springer_equivalent_sea_level_pressure
id: pressure_springer
- platform: homeassistant
name: "Battery Springer"
internal: true
entity_id: sensor.sensor_springer_battery_level
id: battery_springer
text_sensor:
- platform: homeassistant
id: weather_icon
internal: true
entity_id: weather.openweathermap

8
common/experimental/ble.yaml

@ -1,8 +0,0 @@
# Bluetooth Low-Energy
# Example configuration entry
esp32_ble_tracker:
sensor:
- platform: ble_rssi
mac_address: FA:4E:84:FF:4D:16
name: "${node_name} MiBand Hendrik RSSI value"

2
common/experimental/webserver.yaml

@ -1,2 +0,0 @@
web_server:
port: 80

15
common/sds011.yaml

@ -1,15 +0,0 @@
uart:
rx_pin: 15
tx_pin: 2
baud_rate: 9600
# SDS 011 Particulate Matter Sensor
sensor:
- platform: sds011
pm_2_5:
name: "${node_name} Particulate Matter <2.5µm Concentration"
id: pm2
pm_10_0:
name: "${node_name} Particulate Matter <10.0µm Concentration"
id: pm10
update_interval: 10min

4
common/secrets.yaml

@ -1,4 +0,0 @@
<<: !include ../secrets.yaml
# You can also use Home Assistant secrets.yaml:
# <<: !include ../../secrets.yaml

19
common/veml6075.yaml

@ -1,19 +0,0 @@
# VEML6075 UV sensor
sensor:
- platform: custom
lambda: |-
auto veml6075 = new VEML6075CustomSensor();
App.register_component(veml6075);
return {veml6075->uva_sensor, veml6075->uvb_sensor, veml6075->uvi_sensor};
sensors:
- name: "${node_name} UVA"
id: uva
unit_of_measurement: "mW/cm²"
accuracy_decimals: 0
- name: "${node_name} UVB"
id: uvb
unit_of_measurement: "mW/cm²"
accuracy_decimals: 0
- name: "${node_name} UVI"
id: uvi
accuracy_decimals: 0

43
custom/veml6075_custom_sensor.h

@ -1,43 +0,0 @@
#include "esphome.h"
#include <Wire.h>
#include "Adafruit_VEML6075.h"
class VEML6075CustomSensor : public PollingComponent {
public:
Adafruit_VEML6075 uv = Adafruit_VEML6075();
Sensor *uva_sensor = new Sensor();
Sensor *uvb_sensor = new Sensor();
Sensor *uvi_sensor = new Sensor();
VEML6075CustomSensor() : PollingComponent(15000) {}
float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; }
void setup() override {
//Wire.begin(21, 22);
if (!uv.begin()) {
ESP_LOGE("custom", "VEML6075 init failed");
this->mark_failed();
return;
}
uv.setIntegrationTime(VEML6075_100MS);
//uv.setHighDynamic(true);
// Set the calibration coefficients
uv.setCoefficients(2.22, 1.33, // UVA_A and UVA_B coefficients
2.95, 1.74, // UVB_C and UVB_D coefficients
0.001461, 0.002591); // UVA and UVB responses
}
void update() override {
float uva = uv.readUVA();
float uvb = uv.readUVB();
float uvi = uv.readUVI();
ESP_LOGD("custom", "The value of sensor uva is: %.0f", uva);
ESP_LOGD("custom", "The value of sensor uvb is: %.0f", uvb);
uva_sensor->publish_state(uva);
uvb_sensor->publish_state(uvb);
uvi_sensor->publish_state(uvi);
}
};

BIN
fonts/Vera.ttf

Binary file not shown.

BIN
fonts/VeraMono.ttf

Binary file not shown.

BIN
fonts/materialdesignicons-webfont.ttf

Binary file not shown.

BIN
fonts/slkscr.ttf

Binary file not shown.

37
fonts/weather_icon_map.h

@ -1,37 +0,0 @@
#include <map>
std::map<std::string, std::string> weather_icon_map
{
{"cloudy", "\U000F0590"},
{"cloudy-alert", "\U000F0F2F"},
{"cloudy-arrow-right", "\U000F0E6E"},
{"fog", "\U000F0591"},
{"hail", "\U000F0592"},
{"hazy", "\U000F0F30"},
{"hurricane", "\U000F0898"},
{"lightning", "\U000F0593"},
{"lightning-rainy", "\U000F067E"},
{"night", "\U000F0594"},
{"clear-night", "\U000F0594"},
{"night-partly-cloudy", "\U000F0F31"},
{"partly-cloudy", "\U000F0595"},
{"partlycloudy", "\U000F0595"},
{"partly-lightning", "\U000F0F32"},
{"partly-rainy", "\U000F0F33"},
{"partly-snowy", "\U000F0F34"},
{"partly-snowy-rainy", "\U000F0F35"},
{"pouring", "\U000F0596"},
{"rainy", "\U000F0597"},
{"snowy", "\U000F0598"},
{"snowy-heavy", "\U000F0F36"},
{"snowy-rainy", "\U000F067F"},
{"sunny", "\U000F0599"},
{"sunny-alert", "\U000F0F37"},
{"sunny-off", "\U000F14E4"},
{"sunset", "\U000F059A"},
{"sunset-down", "\U000F059B"},
{"sunset-up", "\U000F059C"},
{"tornado", "\U000F0F38"},
{"windy", "\U000F059D"},
{"windy-variant", "\U000F059E"},
{"exceptional", "\U000F0599"},
};

15
hendrik-test.yaml

@ -1,15 +0,0 @@
substitutions:
node_name: hendrik-test
altitude: "111"
packages:
base: !include common/base.yaml
battery: !include common/battery.yaml
display: !include common/display-hendrik.yaml
webserver: !include common/experimental/webserver.yaml
env_sensor: !include common/bme680.yaml
# remote_package:
# url: https://dev.xd0.de/hendrik/esp32-weatherstation
# ref: esphome
# files: [common/base.yaml, common/bme680.yaml]
# refresh: 1d

61
platformio.ini

@ -0,0 +1,61 @@
;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]
platform = espressif32
;platform = https://github.com/platformio/platform-espressif32.git#feature/stage
framework = arduino
build_flags =
-DLOG_DEFAULT_LEVEL=ESP_LOG_VERBOSE
-DLOG_LOCAL_LEVEL=ESP_LOG_VERBOSE
-DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
-DWIFI_SSID="\"${sysenv.WIFI_SSID}\""
-DWIFI_PASSWD="\"${sysenv.WIFI_PASSWD}\""
-DWIFI_SSID2="\"${sysenv.WIFI_SSID}\""
-DWIFI_PASSWD2="\"${sysenv.WIFI_PASSWD}\""
-DMQTT_BROKER_URI="\"${sysenv.MQTT_BROKER_URI}\""
-DMQTT_USERNAME="\"${sysenv.MQTT_USERNAME}\""
-DMQTT_PASSWORD="\"${sysenv.MQTT_PASSWORD}\""
lib_deps =
ArduinoJSON
monitor_speed = 115200
upload_speed = 115200
extra_scripts =
pre:script/autoversioning.py
pre:lib/script/autoversioning.py
[env:lolin_d32_pro]
board = lolin_d32_pro
build_flags =
${env.build_flags}
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
-DARDUINO_SAMD_VARIANT_COMPLIANCE
lib_deps =
${env.lib_deps}
GxEPD2
; https://github.com/wemos/LOLIN_EPD_Library
Adafruit GFX Library
Adafruit BME280 Library
Adafruit Unified Sensor
; SDS011 sensor Library
; Nova Fitness Sds dust sensors library
https://github.com/lewapek/sds-dust-sensors-arduino-library.git
; 246f28d1a080 e paper
; a4cf1211c3e4 d32
; 246f28d1fa5c d32_pro 2

13
script/autoversioning.py

@ -0,0 +1,13 @@
Import("env")
import time
ver = time.time()
f = open("version.txt", "w")
f.write(str(ver))
f.close()
env.Append(CPPDEFINES=[
("FW_VERSION", ver)
])

10
secrets-example.yaml

@ -1,10 +0,0 @@
# esphome secrets
wifi_ssid: ssid1
wifi_passwd: password1
wifi_ssid2: ssid2
wifi_passwd2: password2
esphome_ota_password: "1234567890abcdef1234567890abcdef"
wifi_captive_portal_password: "123456789abc"
esphome_mqtt_user: "mqtt-user"
esphome_mqtt_passwd: "mqtt-password"

11
sensor-outdoor.yaml

@ -1,11 +0,0 @@
substitutions:
node_name: sensor-outdoor
altitude: "30"
packages:
base: !include common/base.yaml
battery: !include common/battery.yaml
env_sensor: !include common/bme280.yaml
particulate: !include common/sds011.yaml
light: !include common/bh1750.yaml
uv: !include common/veml6075.yaml

9
sensor-springer.yaml

@ -1,9 +0,0 @@
substitutions:
node_name: sensor-springer
altitude: "30"
packages:
base: !include common/base.yaml
battery: !include common/battery.yaml
env_sensor: !include common/bme280.yaml

8
sensor-sz.yaml

@ -1,8 +0,0 @@
substitutions:
node_name: sensor-sz
altitude: "30"
packages:
base: !include common/base.yaml
env_sensor: !include common/bme680.yaml
display: !include common/display.yaml

8
sensor-wz.yaml

@ -1,8 +0,0 @@
substitutions:
node_name: sensor-wz
altitude: "30"
packages:
base: !include common/base.yaml
env_sensor: !include common/bme680.yaml
display: !include common/display.yaml

16
src/display/Display.cpp

@ -0,0 +1,16 @@
#include "Display.h"
#include "esp_log.h"
static const char *TAG = "Display";
Display::Display() {}
bool Display::begin(void) {
}
void Display::helloWorld(void) {
}

14
src/display/Display.h

@ -0,0 +1,14 @@
#ifndef _DISPLAY_H
#define _DISPLAY_H
#include <Arduino.h>
class Display {
public:
Display(void);
virtual bool begin(void);
virtual void helloWorld(void);
private:
};
#endif /* _DISPLAY_H */

57
src/display/Display_D32_EPD.cpp

@ -0,0 +1,57 @@
#include "Display.h"
#include "Display_D32_EPD.h"
#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include "bitmaps/Bitmaps128x250.h"
#include <Adafruit_GFX.h>
#include "../hardware.h"
#include "esp_log.h"
static const char *TAG = "Display";
namespace {
GxEPD2_BW<GxEPD2_213_B72, GxEPD2_213_B72::HEIGHT> display(GxEPD2_213_B72(/*CS=SS*/ TFT_CS, /*DC=*/ TFT_DC, /*RST=*/ TFT_RST, /*BUSY=*/ -1)); // GDEH0213B72
}
Display_D32_EPD::Display_D32_EPD(void) {}
bool Display_D32_EPD::begin(void) {
// initialize e-paper display
SPI.begin(18, 19, 23, TFT_CS);
display.init();
Serial.println("display init done"); Serial.flush();
display.clearScreen();
display.refresh();
helloWorld();
display.powerOff();
}
void Display_D32_EPD::helloWorld()
{
const char HelloWorld[] = "Hello World!";
//Serial.println("helloWorld");
display.setRotation(1);
display.setFont(&FreeMonoBold9pt7b);
display.setTextColor(GxEPD_BLACK);
int16_t tbx, tby; uint16_t tbw, tbh;
display.getTextBounds(HelloWorld, 0, 0, &tbx, &tby, &tbw, &tbh);
// center bounding box by transposition of origin:
uint16_t x = ((display.width() - tbw) / 2) - tbx;
uint16_t y = ((display.height() - tbh) / 2) - tby;
display.setFullWindow();
display.firstPage();
do
{
display.fillScreen(GxEPD_WHITE);
display.setCursor(x, y);
display.print(HelloWorld);
}
while (display.nextPage());
//Serial.println("helloWorld done");
}

16
src/display/Display_D32_EPD.h

@ -0,0 +1,16 @@
#ifndef _DISPLAY_D32_EPD_H
#define _DISPLAY_D32_EPD_H
#include <Arduino.h>
#include "Display.h"
class Display_D32_EPD : public Display {
public:
Display_D32_EPD();
bool begin(void);
void helloWorld(void);
private:
};
#endif /* _DISPLAY_D32_EPD_H */

20
src/hardware.h

@ -0,0 +1,20 @@
#ifndef _HARDWARE_H
#define _HARDWARE_H
#include <Arduino.h>
static constexpr uint8_t BME_SDA = 21;
static constexpr uint8_t BME_SCL = 22;
/* defined in arduino-esp32/variants/d32_pro/pins_arduino.h */
//static const uint8_t LED_BUILTIN = 5;
//static const uint8_t _VBAT = 35; // battery voltage
//#define TF_CS 4 // TF (Micro SD Card) CS pin
//#define TS_CS 12 // Touch Screen CS pin
//#define TFT_CS 14 // TFT CS pin
//#define TFT_LED 32 // TFT backlight control pin
//#define TFT_RST 33 // TFT reset pin
//#define TFT_DC 27 // TFT DC pin
//#define SS TF_CS
#endif /* _HARDWARE_H */

116
src/main.cpp

@ -0,0 +1,116 @@
/*
* Blink
* Turns on an LED on for one second,
* then off for one second, repeatedly.
*/
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <Wire.h>
#include <SPI.h>
#include "sensors/SDS011.h"
#include "sensors/BME280.h"
#include "hardware.h"
#include "network/XD0OTA.h"
#include "network/XD0MQTT.h"
#include "display/Display_D32_EPD.h"
extern "C" {
int rom_phy_get_vdd33();
uint8_t temprature_sens_read();
//uint32_t hall_sens_read();
}
static const char* TAG = "MAIN";
WiFiMulti wifiMulti;
XD0MQTT mqtt;
Display *display = new Display_D32_EPD();
BME280 bme;
SDS011 sds;
struct __attribute__((packed)) Measurements {
char timeStr[20];
float temperature;
float humidity;
float pressure;
int voltage;
int32_t rssi;
};
Measurements measurements;
void setup()
{
Serial.begin(115200);
// initialize LED digital pin as an output.
pinMode(LED_BUILTIN, OUTPUT);
Serial.println("setup");
bme.begin();
sds.begin();
display->begin();
display->helloWorld();
WiFi.setHostname("esp32-weatherstation");
wifiMulti.addAP(WIFI_SSID, WIFI_PASSWD);
wifiMulti.addAP(WIFI_SSID2, WIFI_PASSWD2);
Serial.println("Connecting Wifi...");
if(wifiMulti.run() == WL_CONNECTED) {
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
XD0OTA ota("esp32-weatherstation");
ota.update();
mqtt.begin();
}
void loop()
{
Serial.println("loop");
/*
unsigned long endTime = bme.beginReading();
if (! bme.performReading()) {
ESP_LOGE(TAG, "BME680: Failed to perform reading :(");
return;
}
Serial.print("Temperature = "); Serial.print(bme.temperature); Serial.println(" *C");
display.print("Temperature: "); display.print(bme.temperature); display.println(" *C");
Serial.print("Pressure = "); Serial.print(bme.pressure / 100.0); Serial.println(" hPa");
display.print("Pressure: "); display.print(bme.pressure / 100); display.println(" hPa");
Serial.print("Humidity = "); Serial.print(bme.humidity); Serial.println(" %");
display.print("Humidity: "); display.print(bme.humidity); display.println(" %");
Serial.print("Gas = "); Serial.print(bme.gas_resistance / 1000.0); Serial.println(" KOhms");
display.print("Gas: "); display.print(bme.gas_resistance / 1000.0); display.println(" KOhms");
*/
sds.readPm();
bme.read();
if(wifiMulti.run() != WL_CONNECTED) {
Serial.println("WiFi not connected!");
delay(1000);
}
delay(2000);
}

115
src/network/XD0MQTT.cpp

@ -0,0 +1,115 @@
#include "XD0MQTT.h"
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "tcpip_adapter.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"
#include "esp_log.h"
#include "mqtt_client.h"
#include "esp_tls.h"
static const char *TAG = "MQTT";
XD0MQTT::XD0MQTT() {}
static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
{
esp_mqtt_client_handle_t client = event->client;
int msg_id;
//int mbedtls_err; esp_err_t err;
// your_context_t *context = event->context;
switch (event->event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0);
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1);
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1");
ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
printf("DATA=%.*s\r\n", event->data_len, event->data);
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
//mbedtls_err = 0;
//err = esp_tls_get_and_clear_last_error(event->error_handle, &mbedtls_err, NULL);
//ESP_LOGI(TAG, "Last esp error code: 0x%x", err);
//ESP_LOGI(TAG, "Last mbedtls failure: 0x%x", mbedtls_err);
break;
default:
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
break;
}
return ESP_OK;
}
//static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
static esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event) {
esp_mqtt_client_handle_t client = event->client;
mqtt_event_handler_cb(event);
//ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
//mqtt_event_handler_cb(event_data);
}
bool XD0MQTT::begin(void) {
static esp_mqtt_client_config_t mqtt_cfg;
mqtt_cfg.uri = MQTT_BROKER_URI;
// mqtt_cfg.host = "home.xd0.de";
// mqtt_cfg.port = 8883;
mqtt_cfg.event_handle = mqtt_event_handler;
mqtt_cfg.cert_pem = (const char *)rootCACertificate;
mqtt_cfg.username = MQTT_USERNAME;
mqtt_cfg.password = MQTT_PASSWORD;
mqtt_cfg.user_context = (void*)this;
ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
Serial.printf("BROKER URI: %s\n", MQTT_BROKER_URI);
Serial.printf("MQTT USERNAME: %s\n", MQTT_USERNAME);
Serial.printf("MQTT PASSWORD: %s\n", MQTT_PASSWORD);
client = esp_mqtt_client_init(&mqtt_cfg);
//esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);
esp_mqtt_client_start(client);
return true;
}
bool XD0MQTT::publish(const char* topic, const char* data, int len, int qos=1, int retain=0) {
int msg_id = esp_mqtt_client_publish(client, topic, data, len, qos, retain);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
}

39
src/network/XD0MQTT.h

@ -0,0 +1,39 @@
#ifndef _XD0MQTT_H
#define _XD0MQTT_H
#include <Arduino.h>
#include "mqtt_client.h"
class XD0MQTT {
public:
XD0MQTT(void);
bool begin(void);
bool publish(const char* topic, const char* data, int len, int qos, int retain);
private:
// openssl s_client -showcerts -connect home.xd0.de:8883 </dev/null 2>/dev/null|openssl x509 -outform PEM >mqtt_xd0.de.pem
const char* rootCACertificate = \
"-----BEGIN CERTIFICATE-----\n" \
"MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\n" \
"MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \
"DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\n" \
"PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\n" \
"Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" \
"AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\n" \
"rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\n" \
"OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\n" \
"xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n" \
"7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\n" \
"aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\n" \
"HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\n" \
"SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\n" \
"ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\n" \
"AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\n" \
"R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\n" \
"JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\n" \
"Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n" \
"-----END CERTIFICATE-----\n";
esp_mqtt_client_handle_t client;
};
#endif /* _XD0MQTT_H */

137
src/network/XD0OTA.cpp

@ -0,0 +1,137 @@
#include "XD0OTA.h"
#include <WiFi.h>
#include <WiFiClient.h>
#include <HTTPClient.h>
#include <HTTPUpdate.h>
#include <time.h>
#include "hardware.h"
static const char* TAG = "OTA";
XD0OTA::XD0OTA(String deviceName) : deviceName{deviceName} {}
// Set time via NTP, as required for x.509 validation
void XD0OTA::setClock() {
configTime(0, 0, "pool.ntp.org", "time.nist.gov"); // UTC
Serial.print(F("Waiting for NTP time sync: "));
time_t now = time(nullptr);
int tries = 0;
while (now < 8 * 3600 * 2) {
yield();
delay(500);
Serial.print(F("."));
now = time(nullptr);
tries++;
if (tries>15) return;
}
Serial.println(F(""));
struct tm timeinfo;
gmtime_r(&now, &timeinfo);
Serial.print(F("Current time: "));
Serial.print(asctime(&timeinfo));
}
String XD0OTA::getMAC() {
uint8_t mac[6];
char result[14];
WiFi.macAddress( mac );
snprintf( result, sizeof( result ), "%02x%02x%02x%02x%02x%02x", mac[ 0 ], mac[ 1 ], mac[ 2 ], mac[ 3 ], mac[ 4 ], mac[ 5 ] );
return String( result );
}
String XD0OTA::getUpdateURL(String file, String extension) {
String updateURL = String(fwUrlBase);
updateURL.concat(file);
updateURL.concat(extension);
return updateURL;
}
void XD0OTA::update(void) {
setClock();
// try device specific image first
String fwURL = getUpdateURL(getMAC(), ".bin");
int newVersion = checkForUpdates(fwURL + ".version");
if (newVersion < 1) {
ESP_LOGW(TAG, "[update] no device specific update found..\n");
fwURL = getUpdateURL(deviceName, ".bin");
newVersion = checkForUpdates(fwURL + ".version");
// try project specific image
if (newVersion < 1) {
ESP_LOGW(TAG, "[update] Error while looking for updates..\n");
return;
}
}
if( newVersion > FW_VERSION ) {
ESP_LOGW(TAG, "Preparing to update.\n" );
ESP_LOGW(TAG, "Firmware image URL: " );
ESP_LOGW(TAG, "%s\n", fwURL.c_str() );
WiFiClientSecure client;
client.setCACert(rootCACertificate);
client.setTimeout(12); // seconds
httpUpdate.setLedPin(LED_BUILTIN, HIGH);
t_httpUpdate_return ret = httpUpdate.update( client, fwURL );
switch(ret) {
case HTTP_UPDATE_FAILED:
ESP_LOGW(TAG, "HTTP_UPDATE_FAILED Error (%d): %s\n", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str());
break;
case HTTP_UPDATE_NO_UPDATES:
ESP_LOGW(TAG, "HTTP_UPDATE_NO_UPDATES\n");
break;
case HTTP_UPDATE_OK:
ESP_LOGW(TAG, "[update] Update ok.\n"); // may not called we reboot the ESP
break;
}
} else {
ESP_LOGW(TAG, "[update] Already on latest version\n" );
}
}
int XD0OTA::checkForUpdates(String url) {
int newVersion = -1;
ESP_LOGW(TAG, "Checking for firmware updates.\n" );
ESP_LOGW(TAG, "Firmware version URL: " );
ESP_LOGW(TAG, "%s\n", url.c_str() );
WiFiClientSecure client;
client.setCACert(rootCACertificate);
client.setTimeout(5); // seconds
HTTPClient httpClient;
httpClient.begin( client, url );
int httpCode = httpClient.GET();
if( httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY ) {
String newFWVersion = httpClient.getString();
ESP_LOGW(TAG, "Current firmware version: " );
ESP_LOGW(TAG, "%s\n", String(FW_VERSION).c_str() );
ESP_LOGW(TAG, "Available firmware version: " );
ESP_LOGW(TAG, "%s\n", newFWVersion.c_str() );
newVersion = newFWVersion.toInt();
} else {
ESP_LOGW(TAG, "Firmware version check failed, got HTTP response code " );
ESP_LOGW(TAG, "%d\n", httpCode );
newVersion = -1;
}
httpClient.end();
return newVersion;
}

84
src/network/XD0OTA.h

@ -0,0 +1,84 @@
#ifndef _XD0OTA_H
#define _XD0OTA_H
#include <Arduino.h>
class XD0OTA {
public:
XD0OTA(String deviceName);
void update(void);
int checkForUpdates(String url);
private:
String deviceName;
const char* fwUrlBase = "https://fwupdate.xd0.de:444/fota/";
const char* httpsFingerprint = "37 42 61 B9 E6 EE 22 36 D1 59 67 7D 55 53 6E A4 C7 AA 60 26";
const char* rootCACertificate = \
"-----BEGIN CERTIFICATE-----\n" \
"MIID8DCCAtigAwIBAgIJAPSONy8RRejRMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD\n" \
"VQQGEwJERTEeMBwGA1UECAwVTm9ydGhyaGluZS1XZXN0cGhhbGlhMQ4wDAYDVQQH\n" \
"DAVFc3NlbjEPMA0GA1UECgwGeGQwLmRlMRgwFgYDVQQDDA9md3VwZGF0ZS54ZDAu\n" \
"ZGUxITAfBgkqhkiG9w0BCQEWEmhlbmRyaWsrZGV2QHhkMC5kZTAgFw0xODA2MTYx\n" \
"MTAzMTVaGA8yMDY4MDYxNjExMDMxNVowgYsxCzAJBgNVBAYTAkRFMR4wHAYDVQQI\n" \
"DBVOb3J0aHJoaW5lLVdlc3RwaGFsaWExDjAMBgNVBAcMBUVzc2VuMQ8wDQYDVQQK\n" \
"DAZ4ZDAuZGUxGDAWBgNVBAMMD2Z3dXBkYXRlLnhkMC5kZTEhMB8GCSqGSIb3DQEJ\n" \
"ARYSaGVuZHJpaytkZXZAeGQwLmRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" \
"CgKCAQEAx37S1YgaG74IhTysaaZQMFI50TDeGkxxdpNoIBX0UBeVtKI/3u3MAqBz\n" \
"kKTDHFu4IQDj0PvBdlqPFGdSinFgrIvr49uAr+alNKUtkuSTT7nXI0fzqAxv1taj\n" \
"0mNhigVvYikX2BUU/rNLnQclyBdPNVsOf9cv0t5+UcOHRt6oEwk5nFtG7s7k4+wu\n" \
"wRdGlLy2LwLihYFon4LHAs05JW3qs0IQI4etc8E2JWjF2YwBg3+ooyzUFFIGjPSl\n" \
"Lpi7WvAAR19HITbt5FJXQkFZnFxnfbQv/5f7n8vWfFmzYsEgvldwMZv+Eg6wPb2h\n" \
"rgH7T6RSb55JrZE/JUY5C6pKvTJ3AwIDAQABo1MwUTAdBgNVHQ4EFgQUCnZywNj+\n" \
"djz6n0sIARPx8dp+7bQwHwYDVR0jBBgwFoAUCnZywNj+djz6n0sIARPx8dp+7bQw\n" \
"DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAto7IGXpNYTiPUgnA\n" \
"DE6osdgSV1yVYJj75v+Y8aUGgQ93Ipl/0+PQL99wbGgjDhfxGADLtljwoEAz/fep\n" \
"RqCh8swjL34XV9XjMzfhEMDCybSO6mK7ZmCKwhz9yBaK/Qjdj0YUoLhJ9Huzb70m\n" \
"lGzbOhY4JJKFVaA5AcZhYHqjCmzHCVJ/H0zeuPGyKutbvSx23a24LmebfY4q2D62\n" \
"U85ox3Ojek6Mc8J4V+RjORygDGAO4gZClEhAza4koAg7lCO/kSSk5PrXdlz2dqtA\n" \
"D5Npv9M5363apnO1VlVR+OuO1NEJusRK1aWk9RLZsTPxzwOWwdkifXxUEJ+f8mGn\n" \
"o+6SCw==\n" \
"-----END CERTIFICATE-----\n";
String getMAC(void);
String getUpdateURL(String file, String extension);
void setClock(void);
};
#endif /* _XD0OTA_H */
/*
MAKE SURE TO RESET THE ESP8266 ONCE AFTER NORMAL FLASHING! OTHERWISE OTA WON'T WORK!
openssl req -x509 -nodes -days 18263 -newkey rsa:2048 -keyout /etc/ssl/private/xd0-fwupdate-selfsigned.key -out /etc/ssl/certs/xd0-fwupdate-selfsigned.crt
openssl x509 -noout -fingerprint -sha1 -inform pem -in /etc/ssl/certs/xd0-fwupdate-selfsigned.crt
scp .pioenvs/nodemcuv2/firmware.bin user@webserver:/var/www/fwupdate/fota/macaddress.bin
and change macaddress.version
server {
listen 444 ssl;
listen [::]:444 ssl;
server_name fwupdate.xd0.de;
# SSL configuration
ssl_certificate /etc/ssl/certs/xd0-fwupdate-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/xd0-fwupdate-selfsigned.key;
ssl_buffer_size 4k;
root /var/www/fwupdate;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
access_log /var/log/nginx/fwupdate_access.log;
error_log /var/log/nginx/fwupdate_error.log;
}
*/

56
src/sensors/BME280.cpp

@ -0,0 +1,56 @@
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME280.h"
#include "BME280.h"
#include "../hardware.h"
#include "esp_log.h"
static const char *TAG = "BME280";
BME280::BME280() {}
bool BME280::begin(void) {
bme = new Adafruit_BME280(); // I2C (also available: hardware SPI
// #define BME_SDA 21
// #define BME_SCL 22
Wire.begin(BME_SDA, BME_SCL);
if (!bme->begin()) {
ESP_LOGE(TAG, "Could not find a valid BME280 sensor, check wiring!");
}
/*
// Set up oversampling and filter initialization
bme->setTemperatureOversampling(BME680_OS_8X);
bme->setHumidityOversampling(BME680_OS_2X);
bme->setPressureOversampling(BME680_OS_4X);
bme->setIIRFilterSize(BME680_FILTER_SIZE_3);
bme->setGasHeater(320, 150); // 320*C for 150 ms
*/
}
void BME280::read() {
#define SEALEVELPRESSURE_HPA (1013.25)
Serial.print("Temperature = ");
Serial.print(bme->readTemperature());
Serial.println(" *C");
Serial.print("Pressure = ");
Serial.print(bme->readPressure() / 100.0F);
Serial.println(" hPa");
Serial.print("Approx. Altitude = ");
Serial.print(bme->readAltitude(SEALEVELPRESSURE_HPA));
Serial.println(" m");
Serial.print("Humidity = ");
Serial.print(bme->readHumidity());
Serial.println(" %");
Serial.println();
}

19
src/sensors/BME280.h

@ -0,0 +1,19 @@
#ifndef _BME280_H
#define _BME280_H
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME280.h"
class BME280 {
public:
BME280(void);
bool begin(void);
void read(void);
private:
Adafruit_BME280 *bme;
};
#endif /* _BME280_H */

34
src/sensors/SDS011.cpp

@ -0,0 +1,34 @@
#include <Arduino.h>
#define ARDUINO_SAMD_VARIANT_COMPLIANCE
#include "SdsDustSensor.h"
#include "SDS011.h"
#include "esp_log.h"
static const char *TAG = "SDS011";
SDS011::SDS011() {}
bool SDS011::begin(void) {
//HardwareSerial Serial2(2);
sds = new SdsDustSensor(Serial2);
sds->begin();
}
void SDS011::readPm() {
PmResult pm = sds->readPm();
if (pm.isOk()) {
Serial.print("PM2.5 = ");
Serial.print(pm.pm25);
Serial.print(", PM10 = ");
Serial.println(pm.pm10);
// if you want to just print the measured values, you can use toString() method as well
Serial.println(pm.toString());
} else {
Serial.print("Could not read values from sensor, reason: ");
Serial.println(pm.statusToString());
}
}

18
src/sensors/SDS011.h

@ -0,0 +1,18 @@
#ifndef _SDS011_H
#define _SDS011_H
#include <Arduino.h>
#define ARDUINO_SAMD_VARIANT_COMPLIANCE
#include "SdsDustSensor.h"
class SDS011 {
public:
SDS011(void);
bool begin(void);
void readPm(void);
private:
SdsDustSensor *sds;
};
#endif /* _SDS011_H */

11
test/README

@ -0,0 +1,11 @@
This directory is intended for PIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html
Loading…
Cancel
Save