Compare commits

...

No commits in common. 'esphome' and 'quality-test' have entirely different histories.

  1. 14
      .gitignore
  2. 93
      .gitlab-ci.yml
  3. 20
      LICENSE.md
  4. 120
      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. 23
      platformio.ini
  25. 97
      script/build_compile_commands.py
  26. 119
      script/clang-tidy-to-junit.py
  27. 10
      secrets-example.yaml
  28. 11
      sensor-outdoor.yaml
  29. 9
      sensor-springer.yaml
  30. 8
      sensor-sz.yaml
  31. 8
      sensor-wz.yaml
  32. 30
      src/main.cpp
  33. 11
      test/README

14
.gitignore

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

93
.gitlab-ci.yml

@ -0,0 +1,93 @@
stages:
- test
- build
- deploy
build:
stage: build
image: python:2.7
before_script:
- pip install -U platformio
- platformio update
script:
- platformio ci --project-conf platformio.ini --board=lolin_d32_pro --build-dir build --keep-build-dir
# - cp ~/.platformio/packages/framework-arduinoespressif32/tools/sdk/bin/bootloader_dio_40m.bin build/.pio/build/lolin_d32_pro/
# - cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/default.bin build/.pio/build/lolin_d32_pro/
variables: {PLATFORMIO_CI_SRC: "src"}
artifacts:
paths:
- build/.pio/build/lolin_d32_pro/*.bin
#codequality:
# stage: test
# image: docker:stable
# variables:
# DOCKER_DRIVER: overlay2
# allow_failure: true
# services:
# - docker:stable-dind
# script:
# - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
# - docker run
# --env SOURCE_CODE="$PWD"
# --volume "$PWD":/code
# --volume /var/run/docker.sock:/var/run/docker.sock
# "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
# artifacts:
# reports:
# codequality: gl-code-quality-report.json
static_analysis:
stage: test
image: debian:testing
allow_failure: true
before_script:
- apt -q update
- apt install -qy build-essential
- apt install -qy cppcheck
- apt install -qy clang-format clang-tidy
- apt install -qy python-pip
- pip install -U platformio
- pio init --ide atom
- pip install cppcheck-junit
- mkdir -p build
- pip install junit2html
script:
- script/build_compile_commands.py
- cppcheck --error-exitcode=1 --enable=all --check-config --project=compile_commands.json src 2>&1 | tee build/cppcheck-err.txt
- cppcheck --xml --enable=all --check-config --project=compile_commands.json src 2> build/cppcheck-result.xml
- run-clang-tidy -fix -export-fixes=build/clang-tidy-fixes.yml src
after_script:
- cppcheck_junit build/cppcheck-result.xml build/cppcheck-junit.xml
- cat "build/clang-tidy-fixes.yml" | script/clang-tidy-to-junit.py src >"build/clang-tidy-junit.xml"
- junit2html --summary-matrix build/clang-tidy-junit.xml
- junit2html --summary-matrix build/cppcheck-junit.xml
artifacts:
reports:
junit:
- build/clang-tidy-junit.xml
- build/cppcheck-junit.xml
paths:
- build/clang-tidy-junit.xml.html
- build/cppcheck-junit.xml.html
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/.pio/build/lolin_d32_pro/build.version "${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.

120
README.md

@ -1,118 +1,23 @@
# 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
### Display
Connect to TFT LCD Port with 10P SH1.0 cable
E-Paper pin | ESP32 pin | Notes
------------|-----------|------------
3V3 | |
BUSY | nc |
CS | IO14 |
SCK | IO18 |
MOSI | IO23 | MISO: IO19
DC | IO27 |
RST | IO33 |
GND | |
### BME680
I2C
BME680 pin | ESP32 pin | Notes
-----------|-----------|----------
VCC | 3V |
GND | 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)
GND | GND |
25µm | nc |
5V | VUSB | 5V
1µm | nc |
NC | nc |
tbd
## 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
platformio run -t upload && platformio device monitor -b 115200
pio run
```
## Authors
@ -125,5 +30,4 @@ 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

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

23
platformio.ini

@ -0,0 +1,23 @@
;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:lolin_d32_pro]
platform = espressif32
framework = arduino
board = lolin_d32_pro
build_flags =
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
;lib_deps =
; ArduinoJSON
monitor_speed = 115200

97
script/build_compile_commands.py

@ -0,0 +1,97 @@
#!/usr/bin/env python
# from: https://github.com/esphome/esphome/blob/dev/script/clang-tidy
import codecs
import json
import os.path
import re
import subprocess
import sys
import shlex
root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, '..', '..')))
basepath = os.path.join(root_path, 'src')
temp_header_file = os.path.join(root_path, '.temp-clang-tidy.cpp')
def shlex_quote(s):
if not s:
return u"''"
if re.search(r'[^\w@%+=:,./-]', s) is None:
return s
return u"'" + s.replace(u"'", u"'\"'\"'") + u"'"
def build_all_include():
# Build a cpp file that includes all header files in this repo.
# Otherwise header-only integrations would not be tested by clang-tidy
headers = []
for path in walk_files(basepath):
filetypes = ('.h',)
ext = os.path.splitext(path)[1]
if ext in filetypes:
path = os.path.relpath(path, root_path)
include_p = path.replace(os.path.sep, '/')
headers.append('#include "{}"'.format(include_p))
headers.sort()
headers.append('')
content = '\n'.join(headers)
with codecs.open(temp_header_file, 'w', encoding='utf-8') as f:
f.write(content)
def build_compile_commands():
gcc_flags_json = os.path.join(root_path, '.gcc-flags.json')
if not os.path.isfile(gcc_flags_json):
print("Could not find {} file which is required for clang-tidy.")
print('Please run "pio init --ide atom" in the project folder to generate that file.')
sys.exit(1)
with codecs.open(gcc_flags_json, 'r', encoding='utf-8') as f:
gcc_flags = json.load(f)
exec_path = gcc_flags['execPath']
include_paths = gcc_flags['gccIncludePaths'].split(',')
includes = ['-I{}'.format(p) for p in include_paths]
cpp_flags = shlex.split(gcc_flags['gccDefaultCppFlags'])
defines = [flag for flag in cpp_flags if flag.startswith('-D')]
command = [exec_path]
command.extend(includes)
command.extend(defines)
command.append('-std=gnu++11')
command.append('-Wall')
command.append('-Wno-delete-non-virtual-dtor')
command.append('-Wno-unused-variable')
command.append('-Wunreachable-code')
source_files = []
for path in walk_files(basepath):
filetypes = ('.cpp',)
ext = os.path.splitext(path)[1]
if ext in filetypes:
source_files.append(os.path.abspath(path))
source_files.append(temp_header_file)
source_files.sort()
compile_commands = [{
'directory': root_path,
'command': ' '.join(shlex_quote(x) for x in (command + ['-o', p + '.o', '-c', p])),
'file': p
} for p in source_files]
compile_commands_json = os.path.join(root_path, 'compile_commands.json')
if os.path.isfile(compile_commands_json):
with codecs.open(compile_commands_json, 'r', encoding='utf-8') as f:
try:
if json.load(f) == compile_commands:
return
except:
pass
with codecs.open(compile_commands_json, 'w', encoding='utf-8') as f:
json.dump(compile_commands, f, indent=2)
def walk_files(path):
for root, _, files in os.walk(path):
for name in files:
yield os.path.join(root, name)
if __name__ == "__main__":
build_compile_commands()
build_all_include()

119
script/clang-tidy-to-junit.py

@ -0,0 +1,119 @@
#!/usr/bin/env python3
# https://github.com/PSPDFKit-labs/clang-tidy-to-junit/
import sys
import collections
import re
import logging
import itertools
from xml.sax.saxutils import escape
# Create a `ErrorDescription` tuple with all the information we want to keep.
ErrorDescription = collections.namedtuple(
'ErrorDescription', 'file line column error error_identifier description')
class ClangTidyConverter:
# All the errors encountered.
errors = []
# Parses the error.
# Group 1: file path
# Group 2: line
# Group 3: column
# Group 4: error message
# Group 5: error identifier
error_regex = re.compile(
r"^([\w\/\.\-\ ]+):(\d+):(\d+): (.+) (\[[\w\-,\.]+\])$")
# This identifies the main error line (it has a [the-warning-type] at the end)
# We only create a new error when we encounter one of those.
main_error_identifier = re.compile(r'\[[\w\-,\.]+\]$')
def __init__(self, basename):
self.basename = basename
def print_junit_file(self, output_file):
# Write the header.
output_file.write("""<?xml version="1.0" encoding="UTF-8" ?>
<testsuites id="1" name="Clang-Tidy" tests="{error_count}" errors="{error_count}" failures="0" time="0">""".format(error_count=len(self.errors)))
sorted_errors = sorted(self.errors, key=lambda x: x.file)
# Iterate through the errors, grouped by file.
for file, errorIterator in itertools.groupby(sorted_errors, key=lambda x: x.file):
errors = list(errorIterator)
error_count = len(errors)
# Each file gets a test-suite
output_file.write("""\n <testsuite errors="{error_count}" name="{file}" tests="{error_count}" failures="0" time="0">\n"""
.format(error_count=error_count, file=file))
for error in errors:
# Write each error as a test case.
output_file.write("""
<testcase id="{id}" name="{id}" time="0">
<failure message="{message}">
{htmldata}
</failure>
</testcase>""".format(id="[{}/{}] {}".format(error.line, error.column, error.error_identifier), message=escape(error.error),
htmldata=escape(error.description)))
output_file.write("\n </testsuite>\n")
output_file.write("</testsuites>\n")
def process_error(self, error_array):
if len(error_array) == 0:
return
result = self.error_regex.match(error_array[0])
if result is None:
logging.warning(
'Could not match error_array to regex: %s', error_array)
return
# We remove the `basename` from the `file_path` to make prettier filenames in the JUnit file.
file_path = result.group(1).replace(self.basename, "")
error = ErrorDescription(file_path, int(result.group(2)), int(
result.group(3)), result.group(4), result.group(5), "\n".join(error_array[1:]))
self.errors.append(error)
def convert(self, input_file, output_file):
# Collect all lines related to one error.
current_error = []
for line in input_file:
# If the line starts with a `/`, it is a line about a file.
if line[0] == '/':
# Look if it is the start of a error
if self.main_error_identifier.search(line, re.M):
# If so, process any `current_error` we might have
self.process_error(current_error)
# Initialize `current_error` with the first line of the error.
current_error = [line]
else:
# Otherwise, append the line to the error.
current_error.append(line)
elif len(current_error) > 0:
# If the line didn't start with a `/` and we have a `current_error`, we simply append
# the line as additional information.
current_error.append(line)
else:
pass
# If we still have any current_error after we read all the lines,
# process it.
if len(current_error) > 0:
self.process_error(current_error)
# Print the junit file.
self.print_junit_file(output_file)
if __name__ == "__main__":
if len(sys.argv) < 2:
logging.error("Usage: %s base-filename-path", sys.argv[0])
logging.error(
" base-filename-path: Removed from the filenames to make nicer paths.")
sys.exit(1)
converter = ClangTidyConverter(sys.argv[1])
converter.convert(sys.stdin, sys.stdout)

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

30
src/main.cpp

@ -0,0 +1,30 @@
/*
* Blink
* Turns on an LED on for one second,
* then off for one second, repeatedly.
*/
#include <Arduino.h>
// Set LED_BUILTIN if it is not defined by Arduino framework
// #define LED_BUILTIN 2
void setup()
{
// initialize LED digital pin as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
void loop()
{
int array[10];
array[11] = 0;
// turn the LED on (HIGH is the voltage level)
digitalWrite(LED_BUILTIN, HIGH);
// wait for a second
delay(1000);
// turn the LED off by making the voltage LOW
digitalWrite(LED_BUILTIN, LOW);
// wait for a second
delay(1000);
}

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