feature/InfluxDB-Connection-Update #1
10 changed files with 119 additions and 379 deletions
|
@ -1,2 +0,0 @@
|
|||
|
||||
sudo docker-compose exec esphome esphome run /config/weatherstation.yml
|
|
@ -1,179 +0,0 @@
|
|||
esphome:
|
||||
name: weatherstation
|
||||
platform: ESP8266
|
||||
board: d1_mini
|
||||
|
||||
i2c:
|
||||
- id: bus_a
|
||||
sda: 4 # D2
|
||||
scl: 5 # D1
|
||||
scan: true
|
||||
|
||||
apds9960:
|
||||
address: 0x39
|
||||
update_interval: 300s
|
||||
|
||||
sensor:
|
||||
#
|
||||
#################### wind sensor
|
||||
- platform: pulse_counter
|
||||
pin:
|
||||
# pin D8
|
||||
number: GPIO15
|
||||
mode: INPUT # TODO check if required
|
||||
unit_of_measurement: 'm/s' ##change to m/s if metric
|
||||
name: 'wind_speed'
|
||||
icon: 'mdi:weather-windy'
|
||||
id: my_wind
|
||||
count_mode:
|
||||
rising_edge: DISABLE
|
||||
falling_edge: INCREMENT
|
||||
internal_filter: 50us
|
||||
update_interval: 60s
|
||||
#rotations_per_sec = pulses/2/60
|
||||
#circ_m=0.09*2*3.14 = 0.5652
|
||||
#mps = 1.18*circ_m*rotations_per_sec
|
||||
#mps = 1.18*0.5652/2/60 =0,0055578
|
||||
filters:
|
||||
- multiply: 0.0055578 #use for m/s
|
||||
# - multiply: 2.237 #m/s to mph
|
||||
# - sliding_window_moving_average:
|
||||
# window_size: 4
|
||||
# send_every: 1
|
||||
#- multiply: 0.04973 #1.492mph switch to close 1/sec per spec, pulse/sec (/60/2)*1.492
|
||||
#- multiply: 0.0124327986 #m/s * mph conversion
|
||||
|
||||
#################### apds9960 sensor i2c
|
||||
- platform: apds9960
|
||||
type: CLEAR
|
||||
name: "APDS9960_clear_channel"
|
||||
- platform: apds9960
|
||||
type: RED
|
||||
name: "APDS9960_red_channel"
|
||||
id: apds9960_red
|
||||
- platform: apds9960
|
||||
type: GREEN
|
||||
name: "APDS9960_green_channel"
|
||||
id: apds9960_green
|
||||
- platform: apds9960
|
||||
type: BLUE
|
||||
name: "APDS9960_blue_channel"
|
||||
id: apds9960_blue
|
||||
# TODO fix calculation to get lux instead of a percentual value
|
||||
#
|
||||
# Code from Adafruit_APDS9960 lib:
|
||||
# illuminance = (-0.32466F * r) + (1.57837F * g) + (-0.73191F * b);
|
||||
#
|
||||
# Code from: https://esphome.io/api/apds9960_8cpp_source.html
|
||||
# 180: float red_perc = (uint_red / float(UINT16_MAX)) * 100.0f;
|
||||
# 181: float green_perc = (uint_green / float(UINT16_MAX)) * 100.0f;
|
||||
# 182: float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f;
|
||||
- platform: template
|
||||
name: "light"
|
||||
lambda: |-
|
||||
const float perc_to_raw = 65535.0 / 100.0;
|
||||
const float red_to_lux = -0.32466;
|
||||
const float green_to_lux = 1.57837;
|
||||
const float blue_to_lux = -0.73191;
|
||||
return ((red_to_lux * id(apds9960_red).raw_state) + (green_to_lux * id(apds9960_green).raw_state) + (blue_to_lux * id(apds9960_blue).raw_state)) * perc_to_raw;
|
||||
update_interval: 300s
|
||||
accuracy_decimals: 2
|
||||
unit_of_measurement: 'lux'
|
||||
|
||||
#################### bme280 sensor i2c
|
||||
- platform: bme280
|
||||
temperature:
|
||||
name: "BME280_temperature"
|
||||
id: bme280_temperature
|
||||
pressure:
|
||||
name: "BME280_ressure"
|
||||
id: bme280_pressure
|
||||
humidity:
|
||||
name: "BME280 Relative Humidity"
|
||||
id: bme280_humidity
|
||||
address: 0x76
|
||||
update_interval: 300s
|
||||
|
||||
- platform: template
|
||||
name: "altitude"
|
||||
lambda: |-
|
||||
const float STANDARD_SEA_LEVEL_PRESSURE = 1013.25; //in hPa, see note
|
||||
return ((id(bme280_temperature).state + 273.15) / 0.0065) *
|
||||
(powf((STANDARD_SEA_LEVEL_PRESSURE / id(bme280_pressure).state), 0.190234) - 1); // in meter
|
||||
update_interval: 300s
|
||||
icon: 'mdi:signal'
|
||||
unit_of_measurement: 'm'
|
||||
|
||||
- platform: template
|
||||
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(bme280_temperature).state) /
|
||||
(id(bme280_temperature).state + 243.5)) * id(bme280_humidity).state * mw) /
|
||||
((273.15 + id(bme280_temperature).state) * r); // in grams/m^3
|
||||
accuracy_decimals: 2
|
||||
update_interval: 300s
|
||||
icon: 'mdi:water'
|
||||
unit_of_measurement: 'g/m³'
|
||||
|
||||
- platform: template
|
||||
name: "dew_point"
|
||||
lambda: |-
|
||||
return (243.5*(log(id(bme280_humidity).state/100)+((17.67*id(bme280_temperature).state)/
|
||||
(243.5+id(bme280_temperature).state)))/(17.67-log(id(bme280_humidity).state/100)-
|
||||
((17.67*id(bme280_temperature).state)/(243.5+id(bme280_temperature).state))));
|
||||
unit_of_measurement: °C
|
||||
update_interval: 300s
|
||||
icon: 'mdi:thermometer-alert'
|
||||
|
||||
# battery power adc
|
||||
- platform: adc
|
||||
pin: A0
|
||||
name: "battery_power"
|
||||
update_interval: 300s
|
||||
|
||||
binary_sensor:
|
||||
# battery charged pin
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: 12 # D6
|
||||
mode:
|
||||
input: true
|
||||
pullup: false
|
||||
name: "battery_charged"
|
||||
|
||||
# battery charging pin
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: 14 # D5
|
||||
mode:
|
||||
input: true
|
||||
pullup: false
|
||||
name: "battery_charging"
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: my_time
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
|
||||
ota:
|
||||
password: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
wifi:
|
||||
ssid: UPC4556888
|
||||
password: emmydhvZ4faw
|
||||
manual_ip:
|
||||
# Set this to the IP of the ESP
|
||||
static_ip: 192.168.0.91
|
||||
# Set this to the IP address of the router. Often ends with .1
|
||||
gateway: 192.168.0.1
|
||||
# The subnet of the network. 255.255.255.0 works for most home networks.
|
||||
subnet: 255.255.255.0
|
||||
|
||||
captive_portal:
|
|
@ -1,146 +0,0 @@
|
|||
esphome:
|
||||
name: weatherstation
|
||||
platform: ESP8266
|
||||
board: d1_mini
|
||||
|
||||
i2c:
|
||||
- id: bus_a
|
||||
sda: 4 # D2
|
||||
scl: 5 # D1
|
||||
scan: true
|
||||
|
||||
apds9960:
|
||||
address: 0x39
|
||||
update_interval: 300s
|
||||
|
||||
sensor:
|
||||
#
|
||||
#################### wind sensor
|
||||
- platform: pulse_counter
|
||||
pin:
|
||||
# pin D8
|
||||
number: GPIO15
|
||||
mode: INPUT_PULLUP # TODO check if required
|
||||
unit_of_measurement: 'm/s' ##change to m/s if metric
|
||||
name: 'wind_speed'
|
||||
icon: 'mdi:weather-windy'
|
||||
id: my_wind
|
||||
count_mode:
|
||||
rising_edge: DISABLE
|
||||
falling_edge: INCREMENT
|
||||
internal_filter: 50us
|
||||
update_interval: 300s
|
||||
#rotations_per_sec = pulses/2/60
|
||||
#circ_m=0.09*2*3.14 = 0.5652
|
||||
#mps = 1.18*circ_m*rotations_per_sec
|
||||
#mps = 1.18*0.5652/2/60 =0,0055578
|
||||
filters:
|
||||
- multiply: 0.0055578 #use for m/s
|
||||
# - multiply: 2.237 #m/s to mph
|
||||
# - sliding_window_moving_average:
|
||||
# window_size: 4
|
||||
# send_every: 1
|
||||
#- multiply: 0.04973 #1.492mph switch to close 1/sec per spec, pulse/sec (/60/2)*1.492
|
||||
#- multiply: 0.0124327986 #m/s * mph conversion
|
||||
|
||||
#################### apds9960 sensor i2c
|
||||
- platform: apds9960
|
||||
type: CLEAR
|
||||
name: "APDS9960_clear_channel"
|
||||
|
||||
- platform: bme280
|
||||
temperature:
|
||||
name: "BME280_temperature"
|
||||
id: bme280_temperature
|
||||
pressure:
|
||||
name: "BME280_ressure"
|
||||
id: bme280_pressure
|
||||
humidity:
|
||||
name: "BME280 Relative Humidity"
|
||||
id: bme280_humidity
|
||||
address: 0x76
|
||||
update_interval: 300s
|
||||
|
||||
- platform: template
|
||||
name: "altitude"
|
||||
lambda: |-
|
||||
const float STANDARD_SEA_LEVEL_PRESSURE = 1013.25; //in hPa, see note
|
||||
return ((id(bme280_temperature).state + 273.15) / 0.0065) *
|
||||
(powf((STANDARD_SEA_LEVEL_PRESSURE / id(bme280_pressure).state), 0.190234) - 1); // in meter
|
||||
update_interval: 300s
|
||||
icon: 'mdi:signal'
|
||||
unit_of_measurement: 'm'
|
||||
|
||||
- platform: template
|
||||
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(bme280_temperature).state) /
|
||||
(id(bme280_temperature).state + 243.5)) * id(bme280_humidity).state * mw) /
|
||||
((273.15 + id(bme280_temperature).state) * r); // in grams/m^3
|
||||
accuracy_decimals: 2
|
||||
update_interval: 300s
|
||||
icon: 'mdi:water'
|
||||
unit_of_measurement: 'g/m³'
|
||||
|
||||
- platform: template
|
||||
name: "dew_point"
|
||||
lambda: |-
|
||||
return (243.5*(log(id(bme280_humidity).state/100)+((17.67*id(bme280_temperature).state)/
|
||||
(243.5+id(bme280_temperature).state)))/(17.67-log(id(bme280_humidity).state/100)-
|
||||
((17.67*id(bme280_temperature).state)/(243.5+id(bme280_temperature).state))));
|
||||
unit_of_measurement: °C
|
||||
update_interval: 300s
|
||||
icon: 'mdi:thermometer-alert'
|
||||
|
||||
# battery power adc
|
||||
- platform: adc
|
||||
pin: A0
|
||||
name: "battery_power"
|
||||
update_interval: 300s
|
||||
|
||||
binary_sensor:
|
||||
# battery charged pin
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: 12 # D6
|
||||
mode:
|
||||
input: true
|
||||
pullup: false
|
||||
name: "battery_charged"
|
||||
|
||||
# battery charging pin
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: 14 # D5
|
||||
mode:
|
||||
input: true
|
||||
pullup: false
|
||||
name: "battery_charging"
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: my_time
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
|
||||
ota:
|
||||
password: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
wifi:
|
||||
ssid: UPC4556888
|
||||
password: emmydhvZ4faw
|
||||
manual_ip:
|
||||
# Set this to the IP of the ESP
|
||||
static_ip: 192.168.0.91
|
||||
# Set this to the IP address of the router. Often ends with .1
|
||||
gateway: 192.168.0.1
|
||||
# The subnet of the network. 255.255.255.0 works for most home networks.
|
||||
subnet: 255.255.255.0
|
||||
|
||||
captive_portal:
|
|
@ -16,9 +16,9 @@
|
|||
#define ENERGY_SAVE_MODE_ENABLED 1.0
|
||||
#define ENERGY_SAVE_MODE_DISABLED 0.0
|
||||
|
||||
#define WIFI_AUTOCONNECT_TIMEOUT_S 120
|
||||
#define WIFI_CONFIG_PORTAL_TIMEOUT_S 120
|
||||
#define UPDATE_SENSOR_INTERVAL_S 10
|
||||
#define WIFI_AUTOCONNECT_TIMEOUT_S 120
|
||||
#define WIFI_CONFIG_PORTAL_TIMEOUT_S 120
|
||||
#define UPDATE_SENSOR_INTERVAL_S 300
|
||||
#define UPDATE_WEBSERVER_INTVERVAL_S 1
|
||||
#define DELAY_LOOP_MS 50
|
||||
#define POWERSAVING_SLEEP_S 600
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Standard ESP8266 libs
|
||||
// Standard ESP8266 libs from project folder
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <ESP8266HTTPUpdateServer.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
|
@ -6,7 +6,7 @@
|
|||
#include <ESP8266WebServer.h>
|
||||
|
||||
#include <WiFiClient.h> // WiFiClient
|
||||
#include <WiFiManager.h> // WiFiManager
|
||||
#include <WiFiManager.h> // WiFiManager from bib manager
|
||||
|
||||
// Project includes
|
||||
#include "config.h"
|
||||
|
@ -35,7 +35,7 @@ void debug(String x) {
|
|||
|
||||
void setup() {
|
||||
|
||||
// Erase WiFi Credentials (maybe this will work ...)
|
||||
// Erase WiFi Credentials, enable, compile, flash, disable and reflash.
|
||||
//WiFi.disconnect(true);
|
||||
//delay(2000);
|
||||
//ESP.reset();
|
||||
|
@ -118,6 +118,12 @@ void setup() {
|
|||
#ifndef BATTERY_POWERED
|
||||
setupWebUpdater(DEVICE_NAME, WiFi.localIP().toString());
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HOMEBRIDGE_WEBSTAT
|
||||
#ifndef BATTERY_POWERED
|
||||
hb_webstat_server_setup();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//It's magic! leave in
|
||||
|
@ -192,7 +198,6 @@ void _loop() {
|
|||
}
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef defined(BATTERY_POWERED) && defined(SENSOR_WIND)
|
||||
if (energySavingMode() == 1) {
|
||||
// Disable expensive tasks
|
||||
|
@ -225,6 +230,11 @@ void _loop() {
|
|||
#ifdef WEBUPDATER_FEATURE
|
||||
setSensorData(currentSensorData);
|
||||
#endif
|
||||
|
||||
#ifdef HOMEBRIDGE_WEBSTAT
|
||||
hb_webstat_loop(currentSensorData);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void logToSerial(float sensorValues[]) {
|
||||
|
|
30
firmware/hb_webstat_srv.ino
Executable file
30
firmware/hb_webstat_srv.ino
Executable file
|
@ -0,0 +1,30 @@
|
|||
|
||||
// Port des Web Servers auf 80 setzen
|
||||
|
||||
#ifdef HOMEBRIDGE_WEBSTAT
|
||||
|
||||
WiFiServer hb_webstat_server(80);
|
||||
|
||||
const char *hb_ws_msg_start = "{\"temperature\": ";
|
||||
const char *hb_ws_msg_mid = "\"humidity\": ";
|
||||
const char *hb_ws_msg_end = "}";
|
||||
|
||||
void hb_webstat_setup()
|
||||
{
|
||||
hb_webstat_server.begin()
|
||||
}
|
||||
|
||||
void hb_webstat_send(floar currentSensorDfloar currentSensorData[]ata[])
|
||||
{
|
||||
WiFiClient client = server.available();
|
||||
|
||||
if (client.available()) {
|
||||
client.print(hb_ws_msg_start);
|
||||
client.print(String(currentSensorData[0],2));
|
||||
client.print(hb_ws_msg_mid);
|
||||
client.print(hb_ws_msg_end);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
113
firmware/influxdb.ino
Normal file → Executable file
113
firmware/influxdb.ino
Normal file → Executable file
|
@ -1,76 +1,103 @@
|
|||
#include <ESP8266Influxdb.h> // https://github.com/hwwong/ESP8266Influxdb
|
||||
#include <InfluxDbClient.h> // from bib manager
|
||||
|
||||
Influxdb _influxdb(INFLUXDB_HOST, INFLUXDB_PORT);
|
||||
// Data point
|
||||
Point sensor("weatherstation");
|
||||
|
||||
// Init variables to influxdb config - doesn't talk to database
|
||||
InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN);
|
||||
|
||||
void influxdb_begin() {
|
||||
// Init variables to influxdb config - doesn't talk to database
|
||||
_influxdb.opendb(INFLUXDB_DB, INFLUXDB_USER, INFLUXDB_PASS);
|
||||
|
||||
// Check server connection
|
||||
if (client.validateConnection()) {
|
||||
// success
|
||||
#ifdef DEBUG
|
||||
Serial.print("InfluxDB connect success\n");
|
||||
#endif
|
||||
} else {
|
||||
// fail
|
||||
#ifdef DEBUG
|
||||
Serial.print("InfluxDB connect failed\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void pushToInfluxDB(String device, float sensorValues[]) {
|
||||
uint8_t tries = 0;
|
||||
boolean addComma = false;
|
||||
|
||||
String msg = "weather,device=" + device + " ";
|
||||
if (!(isnan(sensorValues[SENSOR_TEMPERATURE])))
|
||||
{
|
||||
msg += "temperature=" + String(sensorValues[SENSOR_TEMPERATURE]);
|
||||
addComma = true;
|
||||
sensor.clearFields();
|
||||
sensor.addField("temperature", sensorValues[SENSOR_TEMPERATURE]);
|
||||
_writePoint();
|
||||
}
|
||||
if (!(isnan(sensorValues[SENSOR_HUMIDITY])))
|
||||
{
|
||||
if (true == addComma)
|
||||
msg += ",";
|
||||
msg += "humidity=" + String(sensorValues[SENSOR_HUMIDITY]);
|
||||
addComma = true;
|
||||
sensor.clearFields();
|
||||
sensor.addField("humidity", sensorValues[SENSOR_HUMIDITY]);
|
||||
_writePoint();
|
||||
}
|
||||
if (!(isnan(sensorValues[SENSOR_LIGHT])))
|
||||
{
|
||||
if (true == addComma)
|
||||
msg += ",";
|
||||
msg += "light=" + String(sensorValues[SENSOR_LIGHT]);
|
||||
addComma = true;
|
||||
sensor.clearFields();
|
||||
sensor.addField("light", sensorValues[SENSOR_LIGHT]);
|
||||
_writePoint();
|
||||
}
|
||||
if (!(isnan(sensorValues[SENSOR_WINDSPEED])))
|
||||
{
|
||||
if (true == addComma)
|
||||
msg += ",";
|
||||
msg += "windspeed=" + String(sensorValues[SENSOR_WINDSPEED]);
|
||||
addComma = true;
|
||||
sensor.clearFields();
|
||||
sensor.addField("windspeed", sensorValues[SENSOR_WINDSPEED]);
|
||||
_writePoint();
|
||||
}
|
||||
if (!(isnan(sensorValues[SENSOR_PRESSURE])))
|
||||
{
|
||||
if (true == addComma)
|
||||
msg += ",";
|
||||
msg += "pressure=" + String(sensorValues[SENSOR_PRESSURE]);
|
||||
addComma = true;
|
||||
sensor.clearFields();
|
||||
sensor.addField("pressure", sensorValues[SENSOR_PRESSURE]);
|
||||
_writePoint();
|
||||
}
|
||||
if (!(isnan(sensorValues[SENSOR_BAT_VOLTAGE])))
|
||||
{
|
||||
if (true == addComma)
|
||||
msg += ",";
|
||||
msg += "batvoltage=" + String(sensorValues[SENSOR_BAT_VOLTAGE]);
|
||||
addComma = true;
|
||||
sensor.clearFields();
|
||||
sensor.addField("batvoltage", sensorValues[SENSOR_BAT_VOLTAGE]);
|
||||
_writePoint();
|
||||
}
|
||||
if (!(isnan(sensorValues[SENSOR_ESAVEMODE])))
|
||||
{
|
||||
if (true == addComma)
|
||||
msg += ",";
|
||||
msg += "esavemode=" + String(sensorValues[SENSOR_ESAVEMODE]);
|
||||
addComma = true;
|
||||
sensor.clearFields();
|
||||
sensor.addField("esavemode", sensorValues[SENSOR_ESAVEMODE]);
|
||||
_writePoint();
|
||||
}
|
||||
if (!(isnan(sensorValues[SENSOR_BATCHARGESTATE])))
|
||||
{
|
||||
if (true == addComma)
|
||||
msg += ",";
|
||||
msg += "batchargestate=" + String(sensorValues[SENSOR_BATCHARGESTATE]);
|
||||
addComma = true;
|
||||
sensor.clearFields();
|
||||
sensor.addField("batchargestate", sensorValues[SENSOR_BATCHARGESTATE]);
|
||||
_writePoint();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void _writePoint() {
|
||||
|
||||
// wait unitl ready
|
||||
do {
|
||||
#ifdef DEBUG
|
||||
Serial.print("InfluxDB: waiting for write ready\n");
|
||||
#endif
|
||||
} while (client.canSendRequest() == false);
|
||||
|
||||
// Write point
|
||||
if (!client.writePoint(sensor)) {
|
||||
#ifdef DEBUG
|
||||
Serial.print("InfluxDB write failed: ");
|
||||
Serial.println(client.getLastErrorMessage());
|
||||
Serial.print("\nErrorcode: ");
|
||||
Serial.println(client.getLastStatusCode());
|
||||
Serial.print("\n");
|
||||
#endif
|
||||
} else {
|
||||
#ifdef DEBUG
|
||||
Serial.print("InfluxDB write done\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
debug(msg);
|
||||
|
||||
do {
|
||||
tries++;
|
||||
_influxdb.write(msg);
|
||||
} while (_influxdb.response() != DB_SUCCESS and tries < 5);
|
||||
}
|
||||
|
|
0
firmware/sensor_apds9960.ino
Normal file → Executable file
0
firmware/sensor_apds9960.ino
Normal file → Executable file
0
firmware/sensor_wind.ino
Normal file → Executable file
0
firmware/sensor_wind.ino
Normal file → Executable file
0
firmware/webUpdater.ino
Normal file → Executable file
0
firmware/webUpdater.ino
Normal file → Executable file
Loading…
Reference in a new issue