Compare commits
No commits in common. 'esphome' and 'ci-test' have entirely different histories.
38 changed files with 559 additions and 1088 deletions
@ -0,0 +1,14 @@ |
|||
.pio |
|||
|
|||
# old platformio |
|||
.pioenvs |
|||
.piolibdeps |
|||
.clang_complete |
|||
.gcc-flags.json |
|||
|
|||
# virtualenv |
|||
bin/ |
|||
include/ |
|||
lib/ |
|||
local/ |
|||
share/ |
@ -0,0 +1,41 @@ |
|||
stages: |
|||
- 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 |
|||
|
|||
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}" |
|||
dependencies: |
|||
- build |
|||
when: manual |
|||
only: |
|||
- master |
|||
|
|||
include: |
|||
- template: Code-Quality.gitlab-ci.yml |
@ -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. |
@ -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 |
@ -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 |
@ -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 |
@ -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' |
@ -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 |
@ -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 |
@ -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 |
@ -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" |
@ -1,2 +0,0 @@ |
|||
web_server: |
|||
port: 80 |
@ -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 |
@ -1,4 +0,0 @@ |
|||
<<: !include ../secrets.yaml |
|||
|
|||
# You can also use Home Assistant secrets.yaml: |
|||
# <<: !include ../../secrets.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 |
File diff suppressed because one or more lines are too long
@ -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); |
|||
} |
|||
}; |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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"}, |
|||
}; |
@ -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 |
@ -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 |
@ -0,0 +1,94 @@ |
|||
#!/usr/bin/env python |
|||
|
|||
import codecs |
|||
import json |
|||
import os.path |
|||
import re |
|||
import subprocess |
|||
import sys |
|||
|
|||
root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, '..', '..'))) |
|||
basepath = os.path.join(root_path, 'esphome') |
|||
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 root esphome 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 = gcc_flags['gccDefaultCppFlags'].split(' ') |
|||
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() |
@ -0,0 +1,170 @@ |
|||
#!/usr/bin/env python |
|||
|
|||
from __future__ import print_function |
|||
|
|||
import multiprocessing |
|||
import os |
|||
import re |
|||
|
|||
import pexpect |
|||
import shutil |
|||
import subprocess |
|||
import sys |
|||
import tempfile |
|||
|
|||
import argparse |
|||
import click |
|||
import threading |
|||
|
|||
sys.path.append(os.path.dirname(__file__)) |
|||
from helpers import basepath, shlex_quote, get_output, build_compile_commands, \ |
|||
build_all_include, temp_header_file, git_ls_files, filter_changed |
|||
|
|||
is_py2 = sys.version[0] == '2' |
|||
|
|||
if is_py2: |
|||
import Queue as queue |
|||
else: |
|||
import queue as queue |
|||
|
|||
|
|||
def run_tidy(args, tmpdir, queue, lock, failed_files): |
|||
print("tidy") |
|||
while True: |
|||
path = queue.get() |
|||
invocation = ['clang-tidy-7', '-header-filter=^{}/.*'.format(re.escape(basepath))] |
|||
if tmpdir is not None: |
|||
invocation.append('-export-fixes') |
|||
# Get a temporary file. We immediately close the handle so clang-tidy can |
|||
# overwrite it. |
|||
(handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) |
|||
os.close(handle) |
|||
invocation.append(name) |
|||
invocation.append('-p=.') |
|||
if args.quiet: |
|||
invocation.append('-quiet') |
|||
for arg in ['-Wfor-loop-analysis', '-Wshadow-field', '-Wshadow-field-in-constructor']: |
|||
invocation.append('-extra-arg={}'.format(arg)) |
|||
invocation.append(os.path.abspath(path)) |
|||
invocation_s = ' '.join(shlex_quote(x) for x in invocation) |
|||
|
|||
# Use pexpect for a pseudy-TTY with colored output |
|||
output, rc = pexpect.run(invocation_s, withexitstatus=True, encoding='utf-8', |
|||
timeout=15*60) |
|||
with lock: |
|||
if rc != 0: |
|||
print() |
|||
print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path)) |
|||
print(invocation_s) |
|||
print(output) |
|||
print() |
|||
failed_files.append(path) |
|||
queue.task_done() |
|||
|
|||
|
|||
def progress_bar_show(value): |
|||
if value is None: |
|||
return '' |
|||
|
|||
|
|||
def main(): |
|||
parser = argparse.ArgumentParser() |
|||
parser.add_argument('-j', '--jobs', type=int, |
|||
default=multiprocessing.cpu_count(), |
|||
help='number of tidy instances to be run in parallel.') |
|||
parser.add_argument('files', nargs='*', default=[], |
|||
help='files to be processed (regex on path)') |
|||
parser.add_argument('--fix', action='store_true', help='apply fix-its') |
|||
parser.add_argument('-q', '--quiet', action='store_false', |
|||
help='Run clang-tidy in quiet mode') |
|||
parser.add_argument('-c', '--changed', action='store_true', |
|||
help='Only run on changed files') |
|||
parser.add_argument('--all-headers', action='store_true', |
|||
help='Create a dummy file that checks all headers') |
|||
args = parser.parse_args() |
|||
|
|||
try: |
|||
get_output('clang-tidy-7', '-version') |
|||
except: |
|||
print(""" |
|||
Oops. It looks like clang-tidy is not installed. |
|||
|
|||
Please check you can run "clang-tidy-7 -version" in your terminal and install |
|||
clang-tidy (v7) if necessary. |
|||
|
|||
Note you can also upload your code as a pull request on GitHub and see the CI check |
|||
output to apply clang-tidy. |
|||
""") |
|||
return 1 |
|||
|
|||
build_all_include() |
|||
build_compile_commands() |
|||
|
|||
print("test") |
|||
|
|||
files = [] |
|||
for path in git_ls_files(): |
|||
filetypes = ('.cpp',) |
|||
ext = os.path.splitext(path)[1] |
|||
if ext in filetypes: |
|||
path = os.path.relpath(path, os.getcwd()) |
|||
files.append(path) |
|||
# Match against re |
|||
file_name_re = re.compile('|'.join(args.files)) |
|||
files = [p for p in files if file_name_re.search(p)] |
|||
|
|||
if args.changed: |
|||
files = filter_changed(files) |
|||
|
|||
files.sort() |
|||
|
|||
if args.all_headers: |
|||
files.insert(0, temp_header_file) |
|||
|
|||
tmpdir = None |
|||
if args.fix: |
|||
tmpdir = tempfile.mkdtemp() |
|||
|
|||
failed_files = [] |
|||
return_code = 0 |
|||
try: |
|||
task_queue = queue.Queue(args.jobs) |
|||
lock = threading.Lock() |
|||
for _ in range(args.jobs): |
|||
t = threading.Thread(target=run_tidy, |
|||
args=(args, tmpdir, task_queue, lock, failed_files)) |
|||
t.daemon = True |
|||
t.start() |
|||
|
|||
# Fill the queue with files. |
|||
with click.progressbar(files, width=30, file=sys.stderr, |
|||
item_show_func=progress_bar_show) as bar: |
|||
for name in bar: |
|||
task_queue.put(name) |
|||
|
|||
# Wait for all threads to be done. |
|||
task_queue.join() |
|||
return_code = len(failed_files) |
|||
|
|||
except KeyboardInterrupt: |
|||
print() |
|||
print('Ctrl-C detected, goodbye.') |
|||
if tmpdir: |
|||
shutil.rmtree(tmpdir) |
|||
os.kill(0, 9) |
|||
|
|||
print("finished") |
|||
if args.fix and failed_files: |
|||
print('Applying fixes ...') |
|||
try: |
|||
subprocess.call(['clang-apply-replacements-7', tmpdir]) |
|||
except: |
|||
print('Error applying fixes.\n', file=sys.stderr) |
|||
raise |
|||
|
|||
sys.exit(return_code) |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
main() |
|||
|
@ -0,0 +1,139 @@ |
|||
import codecs |
|||
import json |
|||
import os.path |
|||
import re |
|||
import subprocess |
|||
import sys |
|||
|
|||
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 root esphome 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 = ['-isystem{}'.format(p) for p in include_paths] |
|||
cpp_flags = gcc_flags['gccDefaultCppFlags'].split(' ') |
|||
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) |
|||
|
|||
|
|||
def get_output(*args): |
|||
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
|||
output, err = proc.communicate() |
|||
return output.decode('utf-8') |
|||
|
|||
|
|||
def splitlines_no_ends(string): |
|||
return [s.strip() for s in string.splitlines()] |
|||
|
|||
|
|||
def changed_files(): |
|||
for remote in ('upstream', 'origin'): |
|||
command = ['git', 'merge-base', '{}/dev'.format(remote), 'HEAD'] |
|||
try: |
|||
merge_base = splitlines_no_ends(get_output(*command))[0] |
|||
break |
|||
except: |
|||
pass |
|||
else: |
|||
raise ValueError("Git not configured") |
|||
command = ['git', 'diff', merge_base, '--name-only'] |
|||
changed = splitlines_no_ends(get_output(*command)) |
|||
changed = [os.path.relpath(f, os.getcwd()) for f in changed] |
|||
changed.sort() |
|||
return changed |
|||
|
|||
|
|||
def filter_changed(files): |
|||
changed = changed_files() |
|||
files = [f for f in files if f in changed] |
|||
print("Changed files:") |
|||
if not files: |
|||
print(" No changed files!") |
|||
for c in files: |
|||
print(" {}".format(c)) |
|||
return files |
|||
|
|||
|
|||
def git_ls_files(): |
|||
command = ['git', 'ls-files', '-s'] |
|||
proc = subprocess.Popen(command, stdout=subprocess.PIPE) |
|||
output, err = proc.communicate() |
|||
lines = [x.split() for x in output.decode('utf-8').splitlines()] |
|||
return { |
|||
s[3].strip(): int(s[0]) for s in lines |
|||
} |
|||
|
Binary file not shown.
@ -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" |
@ -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 |
@ -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 |
|||
|
@ -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 |
@ -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 |
@ -0,0 +1,28 @@ |
|||
/*
|
|||
* 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() |
|||
{ |
|||
// 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); |
|||
} |
@ -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…
Reference in new issue