Compare commits
58 Commits
9d22ee1830
...
main
Author | SHA1 | Date | |
---|---|---|---|
8198d8f62e | |||
3d06338782
|
|||
![]() |
3b2414c3c4 | ||
![]() |
db2cb2a556 | ||
051c6f32ce | |||
![]() |
47335abc89 | ||
6fd235c2f9
|
|||
8c9c9f403c
|
|||
a9bc9d15cd
|
|||
a28af51304
|
|||
e1a7d166e5
|
|||
1d0c3fd07b
|
|||
8b11af147f
|
|||
f3d7f4183a
|
|||
30db0802a0
|
|||
f0be663618
|
|||
82d3a7458a
|
|||
53831f23f3
|
|||
cfd7c86235
|
|||
d21229fa73
|
|||
1bd3b7eb90
|
|||
4b7195a037
|
|||
fd6d54697c
|
|||
cb6a250d29
|
|||
d38b9b38d7
|
|||
1e35bff570
|
|||
12f9dea01b
|
|||
01548813dc
|
|||
3d7e80154a
|
|||
a96531dda9
|
|||
d6c8d35385
|
|||
056990076e
|
|||
9fac30e714
|
|||
cdcd297d68
|
|||
42f9224c05
|
|||
264bcb66f6
|
|||
8ed0efd32c
|
|||
5e89b866b4
|
|||
bdb3ec110f
|
|||
a837b8494a
|
|||
69851b34ff
|
|||
6b99942647
|
|||
26341e075a
|
|||
ef7144ebc1
|
|||
9719833b47
|
|||
ac345026e7
|
|||
fcb8838fb6
|
|||
713faae604
|
|||
089a0fe069 | |||
00ccfbfcc0
|
|||
48e1bbaeef
|
|||
ebb9d7f65b
|
|||
774016bbfb
|
|||
05a93e2aa5
|
|||
87ba336d88
|
|||
5eb3ab6bc9
|
|||
41b9d2d175
|
|||
0eb0b179fe
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@
|
||||
**/src/
|
||||
**/platformio.ini
|
||||
secrets.yaml
|
||||
external_components/*/__pycache__/
|
90
README.md
90
README.md
@@ -1,7 +1,91 @@
|
||||
# The NBS espHome config file collection
|
||||
This is a collection of all espHome config files!
|
||||
|
||||
# A3Pool
|
||||
This is a standard config for the a3pool. Nothing special by far with 2 temp sensors and 2 switches.
|
||||
# Some general things:
|
||||
## Why tho?
|
||||
Why? You ask me why? Because I want everyone in my family to have access to those files.
|
||||
|
||||
Better documentation comes later.
|
||||
## How about changes?
|
||||
The general rule about changes is that you can intruduce them yourself and put in a pull request. However, withhin the family I will try to continue and improve stuff as it is so should you have suggestions, I'd like to hear them
|
||||
|
||||
## How about passwords and keys?
|
||||
Passwords and keys are listed in (secrets.yaml.temple)[this template]. We do not have a way to share the actual secrets.yaml internally within the family but I hope we find a solution to that problem. For now, ask me and I may share it with you.
|
||||
|
||||
# A3Pool
|
||||
This is a standard config for the a3pool. Nothing special by far with 2 temp sensors and 1 switch.
|
||||
|
||||
The temperature sensors have the `pool_temperature` and `solar_temperature` and will show report the pool temperature and solar temperature respectively every 10 seconds.
|
||||
|
||||
There is also one switch that is designed to turn the pump on and off. This works by giving an 500ms long impulse that should turn the pump on/off and sensing on a GPIO ping whenever the pump is currently on/off. This switch carries the name of `pump`. I know HOW CREATIVE...
|
||||
|
||||
It also has the possibility of having it's own clock.
|
||||
Simply adjust the time using the `start_h`, `start_m` as well as `end_h`, `end_m` and enable the `control` switch to ensure the automatic control in on. The button `sync_to_clock`is used to sync the system back to the clock without having to know/care about on/off times, simply press the button and it should switch to on/off if it should be on/off respectively.
|
||||
|
||||
**HOWEVER: If the pump is set to off or to the physical clock with physical switch in the garage this will not work!** It will still report it's state correclty however.
|
||||
|
||||
# Table Tennis Count!
|
||||
## Table Tennis Footswitch
|
||||
The files go by the name of (pingpongfuss.yaml)[pingpongfuss1.yaml].
|
||||
While the 1 may be interpreted as just the first of the two sides, it's actually just one nodemcu for both sides. So keep that in mind.
|
||||
|
||||
And other disclaimer: **This does not use the native API, or an encryption key, and uses MQTT only. However, that might change in the future**
|
||||
|
||||
The topics are as follows:
|
||||
|
||||
### Scoring points
|
||||
Topic `tabletenniscounter/control/score/count` will be send with the value of either `0` or `1` when ever red or blue should get a point respectively.
|
||||
|
||||
### Undoing last points (Up to 10 points)
|
||||
Topic `tabletenniscounter/control/score/undo` will be sent with no payload to worry about. The counting part on the respberry pi is supposed to keep a list of the last 10 points.
|
||||
|
||||
## Table tennis LED display
|
||||
### Overall
|
||||
This is probably the more exciting bit about this. It's the part that actually displays the numbers on the display. The part, that tries to displays all the numbers in a way people can use it to read their current score.
|
||||
|
||||
We have a LOT of stuff of stuff to discuss here.
|
||||
|
||||
First of all: It does take advantage of custom components. CustomMQTTDevices to be precise.
|
||||
|
||||
Second of all: It uses the NeoPixelBus Library to communicate with the ledstrip.
|
||||
|
||||
Third of all: It does also use the esphome native api.
|
||||
|
||||
And a disclaimer as well: The native api part was the latest thing added and is by no means ready to be used. In fact it is only used right now to publish the current score to home assistent but nothing else as it cannot display the numbers sent to the esphome over the native api. This feature is planned but not yet implemented tho.
|
||||
|
||||
For the MQTT part we have only one topic to worry about.
|
||||
|
||||
### MQTT JSON message
|
||||
MQTT topic `tabletenniscounter/display/number/command` is used to send the score that should be display encoded as a JSON message. So let's go over the different key/values attributes.
|
||||
|
||||
- state => "ON"/"OFF": This must always be included and must be ON if any other fuctionallity is intended to be used as OFF is going to turn the ledstrip OFF no matter what
|
||||
- player1 => Number 0 - 99: This tells the display the score of player red (which is also the left number). This MUST be used together with player2 and player1Start/displayGreen.
|
||||
- player2 => Number 0 - 99: This tells the display the score of player blue (which is also the right number). This MUST be used together with player1 and player1Start/displayGreen.
|
||||
- player1Start => true/false: This if this is used to display which player is supposed to start. If the value is true, the colon between the two numbers is displayed as red, if it's false, the colon is blue. This attribute can be replaced with displayGreen.
|
||||
- displayGreen: The value for this does not matter. If it's there, the colon is green.
|
||||
- player1Winner => true/false (OPTIONAL): Set this to true, and player 1 (red) will be displayed as the winner by displaying the red number as yellow. If it's false, player 2 (blue) will be displayed as the winner. Omit this attribute if no one should be displayed at the winner.
|
||||
|
||||
Should state ON be sent without any other attribute, the nbs logo is displayed with a little minus at the end for no particular reason.
|
||||
|
||||
# Sonoff Stecker 1 (Sonoff Plug 1)
|
||||
Is the base implementation of the Sonoff Power switch. Nothings special. Still here just in case someone wants it.
|
||||
|
||||
# Ledstrip Simple
|
||||
Ledstrip Simple is a basic implementation of a ledstrip using no custom code and works solely on the addressable_ledstrip implementation of espHome.
|
||||
|
||||
There are a few light effects configured so please play around with them for a little. Have fun.
|
||||
|
||||
There is one thing I should mention tho:
|
||||
At line 34 in the ledstrip_simple.yaml there is a number called num_leds which dictates how many leds there are on the ledstrip. Please change that according to your ledstrip.
|
||||
|
||||
# garage1.yaml (Hörmann Garage Doors)
|
||||
This is the config for the Hörmann Garage Doors and it is absolutely snuffing stupid.
|
||||
|
||||
First of all: It uses a ~~custom component~~... They are called external components now.
|
||||
Which starts the trouble.
|
||||
|
||||
Second of all, if you want to change the door, just change the `garageSide` under `substitutions`. It's either `sy` or `wo`.
|
||||
Then let the custom components do their job I suppose. They are under [external_components/hoermann_door](external_components/hoermann_door). The [__init__.py](external_components/hoermann_door/__init__.py) needs to be there while the [cover.py](external_components/hoermann_door/cover.py) and [output.py](external_components/hoermann_door/output.py) make the platforms in `cover` and `output` available.
|
||||
|
||||
Otherwise. they works but there is still room for improvements
|
||||
|
||||
Edit: Improvements have been made! (Lights now update their state on the front end).
|
104
a3pool.yaml
104
a3pool.yaml
@@ -1,13 +1,14 @@
|
||||
esphome:
|
||||
name: a3pool
|
||||
|
||||
esp8266:
|
||||
board: nodemcuv2
|
||||
platform: ESP8266
|
||||
|
||||
ota:
|
||||
password: !secret a3poolpasswd
|
||||
- platform: esphome
|
||||
password: !secret a3poolpasswd
|
||||
|
||||
api:
|
||||
password: !secret a3poolpasswd
|
||||
encryption:
|
||||
key: !secret a3poolkey
|
||||
|
||||
@@ -17,10 +18,12 @@ wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: true
|
||||
ap:
|
||||
ssid: a3pool_rescue
|
||||
password: !secret rescue_ap_password
|
||||
|
||||
captive_portal:
|
||||
|
||||
mqtt:
|
||||
broker: !secret mqtt_broker_2
|
||||
client_id: "A3 Pool"
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
@@ -32,6 +35,55 @@ binary_sensor:
|
||||
pullup: true
|
||||
id: pump_state
|
||||
|
||||
datetime:
|
||||
- platform: template
|
||||
name: Pool Pump Start Time
|
||||
id: pool_start
|
||||
type: time
|
||||
optimistic: true
|
||||
restore_value: true
|
||||
initial_value: "08:00:00"
|
||||
- platform: template
|
||||
name: Pool Pump End Time
|
||||
id: pool_end
|
||||
type: time
|
||||
optimistic: true
|
||||
restore_value: true
|
||||
initial_value: "20:00:00"
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: "Sync To Clock"
|
||||
on_press:
|
||||
then:
|
||||
- lambda: |-
|
||||
int startSec = (int) (id(pool_start).hour * 3600) + (id(pool_start).minute * 60) + id(pool_start).second;
|
||||
int endSec = (int) (id(pool_end).hour * 3600) + (id(pool_end).minute * 60) + id(pool_end).second;
|
||||
int nowSec = (id(a3poolTime).now().hour * 3600) + (id(a3poolTime).now().minute * 60) + id(a3poolTime).now().second;
|
||||
|
||||
if(!id(control).state){
|
||||
// Make sure wierd error or whatever
|
||||
return;
|
||||
}
|
||||
|
||||
if(startSec < endSec){
|
||||
if(startSec < nowSec && nowSec < endSec){
|
||||
id(pump).turn_on();
|
||||
}
|
||||
else{
|
||||
id(pump).turn_off();
|
||||
}
|
||||
}
|
||||
else if(startSec > endSec){
|
||||
if(endSec < nowSec && nowSec < startSec){
|
||||
id(pump).turn_off();
|
||||
}
|
||||
else{
|
||||
id(pump).turn_on();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
pin: 5
|
||||
@@ -41,12 +93,13 @@ switch:
|
||||
- switch.turn_off: relay
|
||||
- platform: template
|
||||
name: "Automate Control"
|
||||
id: control
|
||||
optimistic: true
|
||||
restore_state: true
|
||||
|
||||
restore_mode: RESTORE_DEFAULT_OFF
|
||||
- platform: template
|
||||
optimistic: false
|
||||
name: pump
|
||||
id: pump
|
||||
lambda: |-
|
||||
if (id(pump_state).state) {
|
||||
return true;
|
||||
@@ -70,14 +123,39 @@ switch:
|
||||
- delay: 500ms
|
||||
- switch.turn_off: relay
|
||||
|
||||
dallas:
|
||||
- pin: D4
|
||||
update_interval: 10s
|
||||
time:
|
||||
timezone: "Europe/Vienna"
|
||||
id: a3poolTime
|
||||
platform: sntp
|
||||
on_time:
|
||||
- seconds: 0
|
||||
months: 4-10
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(a3poolTime).now().hour == id(pool_start).hour && id(a3poolTime).now().minute == id(pool_start).minute/* && id(a3poolTime).now().second == id(pool_start).second*/ && id(control).state;'
|
||||
then:
|
||||
- switch.turn_on: pump
|
||||
- seconds: 0
|
||||
months: 4-10
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(a3poolTime).now().hour == id(pool_end).hour && id(a3poolTime).now().minute == id(pool_end).minute/* && id(a3poolTime).now().second == id(pool_end).second*/ && id(control).state;'
|
||||
then:
|
||||
- switch.turn_off: pump
|
||||
|
||||
one_wire:
|
||||
- platform: gpio
|
||||
pin: D4
|
||||
|
||||
sensor:
|
||||
- platform: dallas
|
||||
- platform: dallas_temp
|
||||
address: 0xd1000007494b3628
|
||||
name: "Pool Temperature"
|
||||
- platform: dallas
|
||||
update_interval: 10s
|
||||
- platform: dallas_temp
|
||||
address: 0x440000073de2e728
|
||||
name: "Solar Temperature"
|
||||
update_interval: 10s
|
||||
|
||||
|
205
chlorPump.yaml
Normal file
205
chlorPump.yaml
Normal file
@@ -0,0 +1,205 @@
|
||||
esphome:
|
||||
name: chlorine-pump
|
||||
project:
|
||||
name: nbsgamesat.chlorine-pump
|
||||
version: "0.7"
|
||||
on_boot:
|
||||
priority: 600
|
||||
then:
|
||||
- output.turn_on: wifi_status_led
|
||||
- chlorine_pump.set_target:
|
||||
target: !lambda return id(chlorine_target).state;
|
||||
|
||||
|
||||
esp8266:
|
||||
board: nodemcuv2
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret cp_password
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: external_components/
|
||||
components: [ chlorine_pump, ezo_orp_i2c]
|
||||
|
||||
globals:
|
||||
- id: last_pump_state
|
||||
type: bool
|
||||
restore_value: no
|
||||
initial_value: "false"
|
||||
- id: last_cycle_info
|
||||
type: std::string
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !secret cp_key
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: true
|
||||
on_connect:
|
||||
output.turn_off: wifi_status_led
|
||||
on_disconnect:
|
||||
output.turn_on: wifi_status_led
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
pin: D6
|
||||
id: pump_switch
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: D0
|
||||
inverted: true
|
||||
id: wifi_status_led
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
id: pool_pump
|
||||
pin:
|
||||
number: D5
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
on_state:
|
||||
- script.execute:
|
||||
id: manage_power
|
||||
switch_state: !lambda return id(power).state;
|
||||
- platform: gpio
|
||||
id: calibrater
|
||||
pin:
|
||||
number: GPIO0
|
||||
inverted: true
|
||||
on_press:
|
||||
- sensor.ezo_orp_i2c.print_device_info:
|
||||
id: chlorine_sensor
|
||||
on_click:
|
||||
min_length: 5s
|
||||
max_length: 10s
|
||||
then:
|
||||
- sensor.ezo_orp_i2c.calibrate:
|
||||
id: chlorine_sensor
|
||||
calibrate_target: 475
|
||||
- output.turn_on: wifi_status_led
|
||||
- delay: 2s
|
||||
- output.turn_off: wifi_status_led
|
||||
- platform: template
|
||||
id: pump_state
|
||||
name: Pump State
|
||||
lambda: !lambda return id(last_pump_state);
|
||||
|
||||
script:
|
||||
- id: manage_power
|
||||
parameters:
|
||||
switch_state: bool
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: |-
|
||||
return switch_state && id(pool_pump).state;
|
||||
then:
|
||||
- chlorine_pump.start: {}
|
||||
- text_sensor.template.publish:
|
||||
id: cycle_text_info
|
||||
state: !lambda 'return "ON... ";'
|
||||
else:
|
||||
- chlorine_pump.stop: {}
|
||||
- text_sensor.template.publish:
|
||||
id: cycle_text_info
|
||||
state: !lambda 'return "PUMP: OFF";'
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
name: Chlorine Pump Power
|
||||
id: power
|
||||
optimistic: true
|
||||
inverted: off
|
||||
restore_mode: RESTORE_DEFAULT_ON
|
||||
turn_on_action:
|
||||
- script.execute:
|
||||
id: manage_power
|
||||
switch_state: true
|
||||
turn_off_action:
|
||||
- script.execute:
|
||||
id: manage_power
|
||||
switch_state: false
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: Prime
|
||||
id: prime
|
||||
on_press:
|
||||
- chlorine_pump.prime: {}
|
||||
- text_sensor.template.publish:
|
||||
id: cycle_text_info
|
||||
state: !lambda 'return "PRIMING??";'
|
||||
|
||||
number:
|
||||
- platform: template
|
||||
name: Chlorine Target
|
||||
id: chlorine_target
|
||||
initial_value: 700
|
||||
restore_value: true
|
||||
min_value: 300
|
||||
max_value: 1400
|
||||
optimistic: true
|
||||
step: 1.0
|
||||
set_action:
|
||||
then:
|
||||
- chlorine_pump.set_target:
|
||||
target: !lambda return x;
|
||||
|
||||
text_sensor:
|
||||
- platform: template
|
||||
name: Cycle Info
|
||||
id: cycle_text_info
|
||||
|
||||
i2c:
|
||||
|
||||
sensor:
|
||||
- platform: ezo_orp_i2c
|
||||
id: chlorine_sensor
|
||||
name: Chlorine
|
||||
update_interval: 5s
|
||||
- platform: hx711
|
||||
name: "Chlorine Canister Levels"
|
||||
dout_pin: D7
|
||||
clk_pin: D8
|
||||
gain: 128
|
||||
update_interval: 20s
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- calibrate_linear:
|
||||
- 47608 -> 0
|
||||
- 590566 -> 100
|
||||
unit_of_measurement: "%"
|
||||
|
||||
chlorine_pump:
|
||||
pump: pump_switch
|
||||
sensor: chlorine_sensor
|
||||
id: chlorine_pump_component
|
||||
disable_clock: false
|
||||
proportional_band: 200
|
||||
cycle_time: 300
|
||||
target: 700
|
||||
# get_target: !lambda return id(chlorine_target).state;
|
||||
cycle_modifiers:
|
||||
min_on_time: 30
|
||||
max_on_time: 240
|
||||
on_time_multiplier: 0.5
|
||||
on_pump_value:
|
||||
- lambda: |-
|
||||
if(pState != id(last_pump_state)){
|
||||
id(last_pump_state) = pState;
|
||||
}
|
||||
on_cycle_start:
|
||||
- lambda: |-
|
||||
id(cycle_text_info).publish_state("tOn: " + std::to_string(tOn) + "s\n tOff: " + std::to_string(tOff) + "s");
|
||||
|
||||
# Here is space for any display implementation that could possibliy be. Have fun OK
|
1
external_components/analog_orp/__init__.py
Normal file
1
external_components/analog_orp/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@nbsgames"]
|
139
external_components/analog_orp/analog_orp.cpp
Normal file
139
external_components/analog_orp/analog_orp.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
#include "analog_orp.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace analog_orp {
|
||||
|
||||
static const char *const TAG = "sensor.analog_orp";
|
||||
|
||||
FloatAverager::FloatAverager(size_t size): size_(size), vector_(size_){
|
||||
this->index_ = 0;
|
||||
}
|
||||
|
||||
void FloatAverager::add_value(float value){
|
||||
vector_.at(index_++) = value;
|
||||
if(index_ == size_){
|
||||
index_ = 0;
|
||||
has_filled_once_ = true;
|
||||
}
|
||||
}
|
||||
float FloatAverager::create_average(){
|
||||
float sum = 0;
|
||||
for(auto point : vector_){
|
||||
sum += point;
|
||||
}
|
||||
return sum / (float) size_;
|
||||
}
|
||||
bool FloatAverager::isFull(){
|
||||
return index_ == 0;
|
||||
}
|
||||
bool FloatAverager::has_filled_once(){
|
||||
return has_filled_once_;
|
||||
}
|
||||
|
||||
void ChlorineSensor::set_pin(InternalGPIOPin *pin){
|
||||
this->pin_ = pin;
|
||||
}
|
||||
|
||||
void ChlorineSensor::set_zero_point(int zero_point){
|
||||
this->zero_point_int_ = zero_point;
|
||||
this->zero_point_ = ((float) zero_point / 1024.0f) * 3.3f; // Base Value for Clorine in Volt
|
||||
}
|
||||
|
||||
void ChlorineSensor::set_print_raw(bool print_raw){
|
||||
this->print_raw_ = print_raw;
|
||||
}
|
||||
void ChlorineSensor::set_inverted(bool inverted){
|
||||
this->inverted_ = inverted;
|
||||
}
|
||||
|
||||
void ChlorineSensor::init_averager(int mesurements, int send_average_every){
|
||||
this->averager_ = new FloatAverager(mesurements);
|
||||
this->send_averager_every_ = send_average_every;
|
||||
}
|
||||
|
||||
void ChlorineSensor::add_read_callback(std::function<void(float)> &&callback){
|
||||
this->on_value_read_.add(std::move(callback));
|
||||
}
|
||||
void ChlorineSensor::add_average_change_callback(std::function<void(float)> &&callback){
|
||||
this->on_average_changed_.add(std::move(callback));
|
||||
}
|
||||
void ChlorineSensor::add_new_value_callback(std::function<void(float)> &&callback){
|
||||
this->new_value_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void ChlorineSensor::setup(){
|
||||
ESP_LOGCONFIG(TAG, "Setting up AnalogOrp '%s'...", this->get_name().c_str());
|
||||
pin_->setup();
|
||||
ESP_LOGCONFIG(TAG, "AnalogOrp '%s' setup finished!", this->get_name().c_str());
|
||||
}
|
||||
|
||||
|
||||
void ChlorineSensor::update() {
|
||||
float value_v = this->sample();
|
||||
ESP_LOGV(TAG, "'%s': Got orp value=%.4fmV", this->get_name().c_str(), value_v);
|
||||
if(averager_ != nullptr){
|
||||
averager_->add_value(value_v);
|
||||
on_value_read_.call(value_v);
|
||||
|
||||
if(++averager_send_counter_ >= send_averager_every_) averager_send_counter_ = 0;
|
||||
|
||||
if(!averager_->has_filled_once()) return;
|
||||
float average = averager_->create_average();
|
||||
on_average_changed_.call(average);
|
||||
|
||||
if(send_averager_every_ == -1){
|
||||
if(averager_->isFull()){
|
||||
this->publish_state(average);
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(averager_->isFull()){
|
||||
this->new_value_callback_.call(average);
|
||||
}
|
||||
if(averager_send_counter_ == 0){
|
||||
this->publish_state(average);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
this->publish_state(value_v);
|
||||
}
|
||||
|
||||
float ChlorineSensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void ChlorineSensor::dump_config(){
|
||||
LOG_SENSOR("", "AnalogOrp Sensor", this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_PIN(" Pin: ", pin_);
|
||||
ESP_LOGCONFIG(TAG, " Zero Point: %d", zero_point_int_);
|
||||
ESP_LOGCONFIG(TAG, " Inverted: %s", inverted_ ? "true" : "false");
|
||||
ESP_LOGCONFIG(TAG, " Print Raw: %s", print_raw_ ? "true" : "false");
|
||||
}
|
||||
bool ChlorineSensor::has_averager(){
|
||||
return averager_ != nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
Nullwert : 680
|
||||
*/
|
||||
float ChlorineSensor::sample(){
|
||||
|
||||
float raw_read = (float) analogRead(pin_->get_pin());
|
||||
if(print_raw_){
|
||||
ESP_LOGD(TAG, "analogRead read a raw value of: %.0f", raw_read);
|
||||
}
|
||||
|
||||
float raw_voltage = (raw_read / 1024.0f) * 3.3f;
|
||||
float messured_voltage = raw_voltage - zero_point_;
|
||||
|
||||
if(inverted_){
|
||||
messured_voltage = messured_voltage * -1.0f;
|
||||
}
|
||||
|
||||
float milli_volt = messured_voltage * 1000.0f;
|
||||
|
||||
return milli_volt;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
78
external_components/analog_orp/analog_orp.h
Normal file
78
external_components/analog_orp/analog_orp.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "Arduino.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace analog_orp {
|
||||
|
||||
class FloatAverager{
|
||||
public:
|
||||
FloatAverager(size_t size);
|
||||
void add_value(float value);
|
||||
bool isFull();
|
||||
float create_average();
|
||||
bool has_filled_once();
|
||||
protected:
|
||||
size_t size_;
|
||||
std::vector<float> vector_;
|
||||
size_t index_;
|
||||
bool has_filled_once_ = false;
|
||||
};
|
||||
|
||||
class ChlorineSensor: public PollingComponent, public sensor::Sensor {
|
||||
public:
|
||||
void set_pin(InternalGPIOPin *pin);
|
||||
void set_zero_point(int zero_point);
|
||||
void set_print_raw(bool print_raw);
|
||||
void set_inverted(bool inverted);
|
||||
void add_read_callback(std::function<void(float)> &&callback);
|
||||
void add_average_change_callback(std::function<void(float)> &&callback);
|
||||
void add_new_value_callback(std::function<void(float)> &&callback);
|
||||
void init_averager(int mesurements, int send_average_every);
|
||||
bool has_averager();
|
||||
void setup() override;
|
||||
void update() override;
|
||||
float sample();
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const;
|
||||
protected:
|
||||
InternalGPIOPin *pin_;
|
||||
CallbackManager<void(float)> on_value_read_;
|
||||
CallbackManager<void(float)> on_average_changed_;
|
||||
CallbackManager<void(float)> new_value_callback_;
|
||||
bool inverted_;
|
||||
bool print_raw_;
|
||||
float zero_point_;
|
||||
int zero_point_int_;
|
||||
FloatAverager *averager_;
|
||||
bool use_averager_ = false;
|
||||
int send_averager_every_ = -1;
|
||||
int averager_send_counter_ = 0;
|
||||
};
|
||||
|
||||
class ChlorineValueReadTrigger : public Trigger<float> {
|
||||
public:
|
||||
explicit ChlorineValueReadTrigger(ChlorineSensor *parent) {
|
||||
parent->add_read_callback([this](int value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
class ChlorineAverageChangeTrigger : public Trigger<float> {
|
||||
public:
|
||||
explicit ChlorineAverageChangeTrigger(ChlorineSensor *parent) {
|
||||
parent->add_average_change_callback([this](int value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
class ChlorineNewValueTrigger : public Trigger<float> {
|
||||
public:
|
||||
explicit ChlorineNewValueTrigger(ChlorineSensor *parent) {
|
||||
parent->add_new_value_callback([this](int value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
99
external_components/analog_orp/sensor.py
Normal file
99
external_components/analog_orp/sensor.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import sensor
|
||||
from esphome.components.adc import sensor as adc
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
CONF_TRIGGER_ID
|
||||
)
|
||||
|
||||
chlorine_sensor_ns = cg.esphome_ns.namespace('analog_orp')
|
||||
ChlorineSensor = chlorine_sensor_ns.class_('ChlorineSensor', cg.PollingComponent, sensor.Sensor)
|
||||
|
||||
CONF_SENSOR_PIN = "pin"
|
||||
CONF_INVERTED = "inverted"
|
||||
CONF_PRINT_RAW = "print_raw"
|
||||
CONF_ZERO_POINT = "zero_point"
|
||||
CONF_AVERAGE = "average"
|
||||
CONF_AVERAGE_MESUREMENTS="mesurements"
|
||||
CONF_SEND_STATE_EVERY="send_state_every"
|
||||
CONF_ON_READ_VALUE = "on_value_read"
|
||||
CONF_AVERAGE_ON_VALUE = "on_value"
|
||||
CONF_AVERAGE_NEW = "on_completely_new_value"
|
||||
|
||||
UNIT_MILLI_VOLT = "mV"
|
||||
|
||||
ICON_TEST_TUBE="mdi:test-tube"
|
||||
|
||||
ChlorineValueRead = chlorine_sensor_ns.class_(
|
||||
"ChlorineValueReadTrigger", automation.Trigger.template(cg.float_)
|
||||
)
|
||||
ChlorineAverageChange = chlorine_sensor_ns.class_(
|
||||
"ChlorineAverageChangeTrigger", automation.Trigger.template(cg.float_)
|
||||
)
|
||||
ChlorineNewValue = chlorine_sensor_ns.class_(
|
||||
"ChlorineNewValueTrigger", automation.Trigger.template(cg.float_)
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
ChlorineSensor,
|
||||
unit_of_measurement=UNIT_MILLI_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
icon=ICON_TEST_TUBE
|
||||
).extend({
|
||||
cv.Required(CONF_SENSOR_PIN): adc.validate_adc_pin,
|
||||
cv.Required(CONF_ZERO_POINT): cv.Range(min=0, max=1023),
|
||||
cv.Optional(CONF_INVERTED, False): cv.boolean,
|
||||
cv.Optional(CONF_PRINT_RAW, False): cv.boolean,
|
||||
cv.Optional(CONF_AVERAGE): cv.Schema({
|
||||
cv.Required(CONF_AVERAGE_MESUREMENTS): cv.int_range(min=1),
|
||||
cv.Optional(CONF_SEND_STATE_EVERY): cv.int_range(min=1),
|
||||
cv.Optional(CONF_ON_READ_VALUE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChlorineValueRead),
|
||||
}),
|
||||
cv.Optional(CONF_AVERAGE_ON_VALUE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChlorineAverageChange),
|
||||
}),
|
||||
cv.Optional(CONF_AVERAGE_NEW): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChlorineNewValue),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s"))
|
||||
)
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
|
||||
sensor_pin = await cg.gpio_pin_expression(config[CONF_SENSOR_PIN])
|
||||
cg.add(var.set_pin(sensor_pin))
|
||||
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_print_raw(config[CONF_PRINT_RAW]))
|
||||
cg.add(var.set_zero_point(config[CONF_ZERO_POINT]))
|
||||
|
||||
if averager_config := config.get(CONF_AVERAGE):
|
||||
update_each = -1
|
||||
if CONF_SEND_STATE_EVERY in averager_config:
|
||||
update_each = averager_config[CONF_SEND_STATE_EVERY]
|
||||
cg.add(var.init_averager(averager_config[CONF_AVERAGE_MESUREMENTS], update_each))
|
||||
|
||||
for conf in averager_config.get(CONF_ON_READ_VALUE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(float, "x")], conf)
|
||||
|
||||
for conf in averager_config.get(CONF_AVERAGE_ON_VALUE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(float, "x")], conf)
|
||||
|
||||
for conf in averager_config.get(CONF_AVERAGE_NEW, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(float, "x")], conf)
|
188
external_components/chlorine_pump/__init__.py
Normal file
188
external_components/chlorine_pump/__init__.py
Normal file
@@ -0,0 +1,188 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import output, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_TRIGGER_ID
|
||||
)
|
||||
|
||||
|
||||
DEPENDENCIES=["sensor"]
|
||||
|
||||
chlorine_pump_ns = cg.esphome_ns.namespace('chlorine_pump')
|
||||
ChlorinePump = chlorine_pump_ns.class_('ChlorinePump', cg.Component)
|
||||
|
||||
CONF_SENSOR = "sensor"
|
||||
CONF_PUMP_OUT = "pump"
|
||||
CONF_PUMP_CYCLE_TIME = "cycle_time"
|
||||
CONF_PUMP_PROPORTIONAL_BAND = "proportional_band"
|
||||
CONF_PUMP_VALUE = "on_pump_value"
|
||||
CONF_CYCLE_START = "on_cycle_start"
|
||||
CONF_DISABLE_CLOCK="disable_clock"
|
||||
CONF_TARGET="target"
|
||||
CONF_TARGET_LAMBDA="get_target"
|
||||
CONF_PUMP_TIME_DIVIDER = "pump_time_divider"
|
||||
CONF_CYCLE_MODIFIERS = "cycle_modifiers"
|
||||
CONF_MIN_ON_TIME = "min_on_time"
|
||||
CONF_MAX_ON_TIME = "max_on_time"
|
||||
CONF_CYCLE_TIME_MULTIPLIER = "on_time_multiplier"
|
||||
CONF_CYCLE_TIME_OFFSET = "on_time_offset"
|
||||
CONF_CYCLE_TIME_IGNORE_MAX_X_BELOW_TARGET = "ignore_max_x_below_target"
|
||||
|
||||
def to_proportional_band(value):
|
||||
try:
|
||||
value = int(value)
|
||||
except (TypeError, ValueError):
|
||||
# pylint: disable=raise-missing-from
|
||||
raise cv.Invalid(f"")
|
||||
return cv.one_of(20, 50, 100, 150, 200, 300, 400)(value)
|
||||
|
||||
ChlorSensorOutputTrigger = chlorine_pump_ns.class_(
|
||||
"ChlorSensorOutputTrigger", automation.Trigger.template(cg.bool_, cg.int_)
|
||||
)
|
||||
ChlorSensorCycleTrigger = chlorine_pump_ns.class_(
|
||||
"ChlorSensorCycleTrigger", automation.Trigger.template(cg.int_, cg.int_)
|
||||
)
|
||||
|
||||
PrimeAction = chlorine_pump_ns.class_("ChlorinePrime", automation.Action)
|
||||
StartAction = chlorine_pump_ns.class_("ChlorineStart", automation.Action)
|
||||
StopAction = chlorine_pump_ns.class_("ChlorineStop", automation.Action)
|
||||
SetTargetAction = chlorine_pump_ns.class_("ChlorineSetTarget", automation.Action)
|
||||
ManualDoseAction = chlorine_pump_ns.class_("ChlorineDose", automation.Action)
|
||||
|
||||
def validate_config(config):
|
||||
if (config.get(CONF_TARGET_LAMBDA, None) is not None) is not (config.get(CONF_TARGET, None) not in config):
|
||||
raise cv.Invalid("Either a fixed target or an get_target lambda must be set to get a target")
|
||||
if CONF_CYCLE_MODIFIERS in config:
|
||||
cycle_mod = config.get(CONF_CYCLE_MODIFIERS)
|
||||
if cycle_mod[CONF_MIN_ON_TIME] >= config[CONF_PUMP_CYCLE_TIME]:
|
||||
raise cv.Invalid("min_on_time must be smaller than cycle_time(default = 360)")
|
||||
if cycle_mod.get(CONF_MAX_ON_TIME) in cycle_mod and config[CONF_PUMP_CYCLE_TIME] < cycle_mod[CONF_MAX_ON_TIME]:
|
||||
raise cv.Invalid("max_on_time must be smaller or equal cycle_time(default = 360)")
|
||||
if cycle_mod.get(CONF_MAX_ON_TIME) in cycle_mod and cycle_mod[CONF_MIN_ON_TIME] >= cycle_mod[CONF_MAX_ON_TIME]:
|
||||
raise cv.Invalid("min_on_time must be smaller than max_on_time")
|
||||
if cycle_mod.get(CONF_CYCLE_TIME_OFFSET) >= config[CONF_PUMP_CYCLE_TIME]:
|
||||
raise cv.Invalid("cycle_time_offset must be smaller than cycle_time(default = 360)")
|
||||
if cycle_mod.get(CONF_CYCLE_TIME_IGNORE_MAX_X_BELOW_TARGET) in cycle_mod and cycle_mod[CONF_CYCLE_TIME_IGNORE_MAX_X_BELOW_TARGET] <= config[CONF_PUMP_PROPORTIONAL_BAND]:
|
||||
raise cv.Invalid("ignore_max_x_below_target must larger than proportional_band(default = 200)")
|
||||
|
||||
return config
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(ChlorinePump),
|
||||
cv.Required(CONF_PUMP_OUT): cv.use_id(output.BinaryOutput),
|
||||
cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Optional(CONF_PUMP_CYCLE_TIME, default=360): cv.int_range(min=30, max=1400),
|
||||
cv.Optional(CONF_PUMP_PROPORTIONAL_BAND, default=200): to_proportional_band,
|
||||
cv.Optional(CONF_DISABLE_CLOCK, default=False): cv.boolean,
|
||||
cv.Optional(CONF_TARGET): cv.int_range(300, 1400),
|
||||
cv.Optional(CONF_PUMP_TIME_DIVIDER, 2): cv.float_range(1, 5),
|
||||
cv.Optional(CONF_TARGET_LAMBDA, 2): cv.templatable(cv.float_),
|
||||
cv.Optional(CONF_CYCLE_MODIFIERS): cv.All({
|
||||
cv.Optional(CONF_MIN_ON_TIME, 0): cv.int_range(0, 1400),
|
||||
cv.Optional(CONF_MAX_ON_TIME): cv.int_range(0, 1400),
|
||||
cv.Optional(CONF_CYCLE_TIME_MULTIPLIER, 1): cv.float_range(0.01, 5.0),
|
||||
cv.Optional(CONF_CYCLE_TIME_OFFSET, 0): cv.int_range(0, 1400),
|
||||
cv.Optional(CONF_CYCLE_TIME_IGNORE_MAX_X_BELOW_TARGET): cv.int_range(100 - 1500),
|
||||
}),
|
||||
|
||||
cv.Optional(CONF_PUMP_VALUE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChlorSensorOutputTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_CYCLE_START): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChlorSensorCycleTrigger),
|
||||
}
|
||||
),
|
||||
|
||||
}),
|
||||
validate_config
|
||||
)
|
||||
|
||||
|
||||
|
||||
ACTION_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(ChlorinePump)
|
||||
})
|
||||
|
||||
@automation.register_action("chlorine_pump.prime", PrimeAction, ACTION_SCHEMA)
|
||||
async def prime_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
@automation.register_action("chlorine_pump.start", StartAction, ACTION_SCHEMA)
|
||||
async def start_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
@automation.register_action("chlorine_pump.stop", StopAction, ACTION_SCHEMA)
|
||||
async def stop_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
@automation.register_action("chlorine_pump.manual_dose", StopAction, ACTION_SCHEMA)
|
||||
async def stop_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
@automation.register_action("chlorine_pump.set_target", SetTargetAction, ACTION_SCHEMA.extend({
|
||||
cv.Required(CONF_TARGET): cv.templatable(cv.float_),
|
||||
}))
|
||||
async def number_set_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_TARGET], args, float)
|
||||
cg.add(var.set_value(template_))
|
||||
return var
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
#yield mqtt.register_mqtt_component(var, config)
|
||||
|
||||
#btnReset = yield cg.gpio_pin_expression(config[CONF_SENSOR_PIN])
|
||||
#cg.add(var.set_input_pin(btnReset))
|
||||
|
||||
if CONF_SENSOR in config:
|
||||
sensor = await cg.get_variable(config[CONF_SENSOR])
|
||||
cg.add(var.set_sensor(sensor))
|
||||
|
||||
pump_out = await cg.get_variable(config[CONF_PUMP_OUT])
|
||||
cg.add(var.set_pump_out(pump_out))
|
||||
|
||||
cg.add(var.set_cycle_time(config[CONF_PUMP_CYCLE_TIME])) # Needs be configured before sensor
|
||||
|
||||
cg.add(var.disable_clock(config[CONF_DISABLE_CLOCK]))
|
||||
#cycle_time = yield cg.get_variable(config[CONF_PUMP_CYCLE_TIME])
|
||||
|
||||
#prop_band = yield cg.get_variable(config[CONF_PUMP_PROPORTIONAL_BAND])
|
||||
cg.add(var.set_prop_band(config[CONF_PUMP_PROPORTIONAL_BAND]))
|
||||
|
||||
if CONF_CYCLE_MODIFIERS in config:
|
||||
conf = config.get(CONF_CYCLE_MODIFIERS)
|
||||
cg.add(var.set_min_on_time(conf[CONF_MIN_ON_TIME]))
|
||||
cg.add(var.set_time_multiplier(conf[CONF_CYCLE_TIME_MULTIPLIER]))
|
||||
cg.add(var.set_time_offset(conf[CONF_CYCLE_TIME_OFFSET]))
|
||||
if CONF_MAX_ON_TIME in conf:
|
||||
cg.add(var.set_max_on_time(conf[CONF_MAX_ON_TIME]))
|
||||
if CONF_CYCLE_TIME_IGNORE_MAX_X_BELOW_TARGET in conf:
|
||||
cg.add(var.set_ignore_max_x_below_target(conf[CONF_CYCLE_TIME_IGNORE_MAX_X_BELOW_TARGET]))
|
||||
|
||||
#cg.add(var.set_state(config[CONF_STATE]))
|
||||
if CONF_TARGET not in config:
|
||||
template_ = await cg.templatable(config[CONF_TARGET_LAMBDA], [], float)
|
||||
cg.add(var.set_target_lambda(template_))
|
||||
else:
|
||||
cg.add(var.set_target(config[CONF_TARGET]))
|
||||
|
||||
|
||||
if CONF_PUMP_VALUE in config:
|
||||
for conf in config.get(CONF_PUMP_VALUE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(bool, "pState"), (int, "pTime")], conf)
|
||||
|
||||
for conf in config.get(CONF_CYCLE_START, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(int, "tOn"), (int, "tOff")], conf)
|
279
external_components/chlorine_pump/pump_cycle.h
Normal file
279
external_components/chlorine_pump/pump_cycle.h
Normal file
@@ -0,0 +1,279 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "Arduino.h"
|
||||
|
||||
static const char *const TAG = "chlorine_pump";
|
||||
|
||||
namespace esphome {
|
||||
namespace chlorine_pump {
|
||||
|
||||
class ChlorinePump : public Component {
|
||||
protected:
|
||||
|
||||
sensor::Sensor *sensor_;
|
||||
output::BinaryOutput *pump_out;
|
||||
int cycle_time;
|
||||
int prop_band;
|
||||
bool state = true;
|
||||
unsigned long last_action;
|
||||
bool disable_clock_ = false;
|
||||
CallbackManager<void(bool, int)> callback_pump_;
|
||||
CallbackManager<void(int, int)> callback_cycle_;
|
||||
int tOn = 0;
|
||||
int tOff = 0;
|
||||
float target_ = -1;
|
||||
float tOn_divider_;
|
||||
std::function<float()> target_lambda_ = nullptr;
|
||||
|
||||
int min_on_time_ = 0;
|
||||
int max_on_time_ = -1;
|
||||
float on_time_multiplier_ = 1;
|
||||
int on_time_offset_ = 0;
|
||||
int ignore_max_x_below_target_ = -1;
|
||||
|
||||
public:
|
||||
|
||||
ChlorinePump(){
|
||||
last_action = 0;
|
||||
}
|
||||
|
||||
void disable_clock(bool disable_clock){
|
||||
this->disable_clock_ = disable_clock;
|
||||
}
|
||||
void set_sensor(sensor::Sensor *sensor){
|
||||
this->sensor_ = sensor;
|
||||
}
|
||||
void set_pump_time_divivder(float pump_time_divider){
|
||||
this->tOn_divider_ = pump_time_divider;
|
||||
}
|
||||
void set_pump_out(output::BinaryOutput *pump_out){
|
||||
this->pump_out = pump_out;
|
||||
}
|
||||
void set_cycle_time(int time_in_sec){
|
||||
this->cycle_time = time_in_sec;
|
||||
}
|
||||
void set_prop_band(int prop_band){
|
||||
this->prop_band = prop_band;
|
||||
}
|
||||
void set_target(float target){
|
||||
this->target_ = target;
|
||||
}
|
||||
|
||||
// Cycle Time Modifiers
|
||||
void set_min_on_time(int min_on_time){
|
||||
this->min_on_time_ = min_on_time;
|
||||
}
|
||||
void set_max_on_time(int max_on_time){
|
||||
this->max_on_time_ = max_on_time;
|
||||
}
|
||||
void set_time_multiplier(float time_multi){
|
||||
this->on_time_multiplier_ = time_multi;
|
||||
}
|
||||
void set_time_offset(int offset){
|
||||
this->on_time_offset_ = offset;
|
||||
}
|
||||
void set_ignore_max_x_below_target(int set_below){
|
||||
this->ignore_max_x_below_target_ = set_below;
|
||||
}
|
||||
|
||||
void setup() override {
|
||||
last_action = millis();
|
||||
if(disable_clock_ && sensor_ != nullptr){
|
||||
sensor_->add_on_state_callback([=](float val) -> void {
|
||||
this->tick_time(val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void set_target_lambda(std::function<float()> callback){
|
||||
target_lambda_ = std::move(callback);
|
||||
}
|
||||
|
||||
void prime() {
|
||||
tOn = 30;
|
||||
tOff = 2;
|
||||
this->pump_out->turn_on();
|
||||
}
|
||||
void start() {
|
||||
this->state = true;
|
||||
}
|
||||
void stop() {
|
||||
this->state = false;
|
||||
tOn = 0;
|
||||
tOff = 0;
|
||||
this->pump_out->turn_off();
|
||||
}
|
||||
bool get_state(){
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setMillisPrecies(unsigned long waitPeriod){
|
||||
|
||||
if(last_action + waitPeriod + waitPeriod > millis()){
|
||||
last_action += waitPeriod;
|
||||
}
|
||||
else{
|
||||
ESP_LOGW(TAG, "Reset millis(). Did the system experience a halt for some reason?");
|
||||
last_action = millis();
|
||||
}
|
||||
}
|
||||
|
||||
int calculatePumpTimeSeconds(float last_mesurement){
|
||||
|
||||
float used_target = -1;
|
||||
if(target_ != -1){
|
||||
used_target = target_;
|
||||
}
|
||||
else{
|
||||
used_target = target_lambda_();
|
||||
}
|
||||
|
||||
if(last_mesurement >= used_target) return 0;
|
||||
|
||||
float currentVal = used_target - last_mesurement;
|
||||
float proportionalValue = (currentVal / (float) prop_band);
|
||||
float timeInSeconds = proportionalValue * (float) cycle_time;
|
||||
|
||||
if(timeInSeconds > cycle_time) timeInSeconds = cycle_time;
|
||||
|
||||
return (int) timeInSeconds;
|
||||
}
|
||||
|
||||
int calculate_changed_on_time(int tOn, float last_mesurement){
|
||||
|
||||
if(ignore_max_x_below_target_ != -1 && (float)(target_ - ignore_max_x_below_target_) >= last_mesurement){
|
||||
return cycle_time;
|
||||
}
|
||||
tOn = (int)((float) tOn * on_time_multiplier_);
|
||||
tOn += on_time_offset_;
|
||||
|
||||
if(tOn < min_on_time_) tOn = min_on_time_;
|
||||
if(max_on_time_ != -1 && tOn > max_on_time_) tOn = max_on_time_;
|
||||
|
||||
return tOn;
|
||||
}
|
||||
|
||||
void add_on_pump_callback(std::function<void(bool, int)> &&callback){
|
||||
this->callback_pump_.add(std::move(callback));
|
||||
}
|
||||
void add_on_cycle_callback(std::function<void(int, int)> &&callback){
|
||||
this->callback_cycle_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void tick_time(float last_mesurement){
|
||||
|
||||
if(tOn == 0 && tOff == 0 && state){
|
||||
int seconds = calculatePumpTimeSeconds(last_mesurement);
|
||||
|
||||
if(seconds != 0)
|
||||
tOn = calculate_changed_on_time(seconds, last_mesurement);
|
||||
else
|
||||
tOn = 0;
|
||||
tOff = cycle_time - tOn;
|
||||
|
||||
if(seconds == 0){
|
||||
tOn = 0;
|
||||
tOff = 360;
|
||||
}
|
||||
ESP_LOGD(TAG, "Time => tOn: %d, tOff: %d", tOn, tOff);
|
||||
|
||||
this->callback_cycle_.call(tOn, tOff);
|
||||
}
|
||||
else if(tOn > 0) {
|
||||
this->callback_pump_.call(true, tOn);
|
||||
--tOn;
|
||||
pump_out->turn_on();
|
||||
}
|
||||
else if(tOff > 0) {
|
||||
this->callback_pump_.call(false, tOff);
|
||||
--tOff;
|
||||
pump_out->turn_off();
|
||||
}
|
||||
}
|
||||
|
||||
void loop() override{
|
||||
|
||||
if(disable_clock_) return;
|
||||
|
||||
if(millis() - last_action > 1000){
|
||||
|
||||
if(sensor_->has_state())
|
||||
tick_time(sensor_->get_state());
|
||||
|
||||
setMillisPrecies(1000);
|
||||
}
|
||||
}
|
||||
|
||||
void dump_config() override {
|
||||
ESP_LOGCONFIG(TAG, "Chlorine_pump controller");
|
||||
ESP_LOGCONFIG(TAG, " Ticker: %s", disable_clock_ ? "using on_value from sensor" : "using internal");
|
||||
//LOG_BINARY_OUTPUT(pump_out);
|
||||
ESP_LOGCONFIG(TAG, " Cycle Time: %ds", cycle_time);
|
||||
ESP_LOGCONFIG(TAG, " Proportional Band: %d", prop_band);
|
||||
if(target_ != -1) ESP_LOGCONFIG(TAG, " Target: %.0fmV", target_);
|
||||
else ESP_LOGCONFIG(TAG, " Target: Using get_target lambda");
|
||||
}
|
||||
};
|
||||
|
||||
class ChlorSensorOutputTrigger : public Trigger<bool, int> {
|
||||
public:
|
||||
explicit ChlorSensorOutputTrigger(ChlorinePump *parent) {
|
||||
parent->add_on_pump_callback([this](bool output_state, int value) { this->trigger(output_state, value); });
|
||||
}
|
||||
};
|
||||
class ChlorSensorCycleTrigger : public Trigger<int, int> {
|
||||
public:
|
||||
explicit ChlorSensorCycleTrigger(ChlorinePump *parent) {
|
||||
parent->add_on_cycle_callback([this](int on_time, int off_time) { this->trigger(on_time, off_time); });
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class ChlorinePrime : public Action<Ts...> {
|
||||
public:
|
||||
ChlorinePrime(ChlorinePump *pump) : pump_(pump) {}
|
||||
|
||||
void play(Ts... x) override { this->pump_->prime(); }
|
||||
|
||||
protected:
|
||||
ChlorinePump *pump_;
|
||||
};
|
||||
template<typename... Ts> class ChlorineStart : public Action<Ts...> {
|
||||
public:
|
||||
ChlorineStart(ChlorinePump *pump) : pump_(pump) {}
|
||||
|
||||
void play(Ts... x) override { this->pump_->start(); }
|
||||
|
||||
protected:
|
||||
ChlorinePump *pump_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ChlorineStop : public Action<Ts...> {
|
||||
public:
|
||||
ChlorineStop(ChlorinePump *pump) : pump_(pump) {}
|
||||
|
||||
void play(Ts... x) override { this->pump_->stop(); }
|
||||
|
||||
protected:
|
||||
ChlorinePump *pump_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ChlorineSetTarget : public Action<Ts...> {
|
||||
public:
|
||||
ChlorineSetTarget(ChlorinePump *pump) : pump_(pump) {}
|
||||
TEMPLATABLE_VALUE(float, value)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->pump_->set_target((int) this->value_.value(x...));
|
||||
}
|
||||
|
||||
protected:
|
||||
ChlorinePump *pump_;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
1
external_components/ezo_orp_i2c/__init__.py
Normal file
1
external_components/ezo_orp_i2c/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@nbsgames"]
|
180
external_components/ezo_orp_i2c/ezo_orp.cpp
Normal file
180
external_components/ezo_orp_i2c/ezo_orp.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
#include "ezo_orp.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ezo_orp_i2c {
|
||||
|
||||
static const char *const TAG = "ezo_orp_i2c.sensor";
|
||||
|
||||
static const uint8_t COMMAND_READ[] = {0x52}; // R
|
||||
static const uint8_t COMMAND_PERFERM_CALIBRATION[] = {0x43, 0x61, 0x6C}; //Cal
|
||||
static const uint8_t COMMAND_IS_CALIBRATED[] = {0x43, 0x61, 0x6C, 0x2C, 0x3F}; //Cal
|
||||
static const uint8_t COMMAND_EXPORT_CALIBRATION[] = {0x45, 0x78, 0x70, 0x6F, 0x72, 0x74}; // Export
|
||||
static const uint8_t COMMAND_INFORMATION[] = {0x69}; // i (DON'T ASK ME WHY THAT IS ONLY A SMALL LETTER
|
||||
static const uint8_t COMMAND_STATUS[] = {0x53, 0x74, 0x61, 0x74, 0x75, 0x73}; // STATUS
|
||||
|
||||
static const uint8_t COMMAND_FACTORY_RESET[] = {0x46, 0x61, 0x63, 0x74, 0x6F, 0x72, 0x79}; // Factory
|
||||
|
||||
static const uint8_t COMMMAND_TO_ARGS_SEPERATOR = {0x2C};
|
||||
|
||||
static const int PROCESSING_DELAY_MS = 300; // FOR SOME OPERATIONS
|
||||
static const int READING_DELAY_MS = 900; // FOR BASICALLY EVERYTHING WHERE LARGE DATA IS READ/SENT
|
||||
|
||||
|
||||
void EzoOrpSensor::setup(){
|
||||
ESP_LOGCONFIG(TAG, "Setting up EzoOrpI2C '%s'...", this->get_name().c_str());
|
||||
ESP_LOGCONFIG(TAG, "EzoOrpI2C '%s' setup finished!", this->get_name().c_str());
|
||||
scheduleNextAction(SensorAction::IS_CALIBRATED, nullptr, 0, 450);
|
||||
}
|
||||
|
||||
void EzoOrpSensor::update() {
|
||||
scheduleNextAction(SensorAction::READ, nullptr, 0, 900);
|
||||
}
|
||||
|
||||
void EzoOrpSensor::read_response(int maxLength, SensorAction action) {
|
||||
uint8_t data[maxLength];
|
||||
|
||||
if(SensorAction::CALIBRATE == action){
|
||||
ESP_LOGD(TAG, "Device Calibrated");
|
||||
busy_ = false;
|
||||
runScheduledAction();
|
||||
return;
|
||||
}
|
||||
|
||||
if(i2c::ERROR_OK != this->read(data, maxLength)){
|
||||
ESP_LOGW(TAG, "Error Occured while reading respnse!");
|
||||
busy_ = false;
|
||||
runScheduledAction();
|
||||
return;
|
||||
}
|
||||
|
||||
if(data[0] != 1 && data[0] != 255){
|
||||
ESP_LOGW(TAG, "Read status code of %d, (Operation: %d)", data[0], action);
|
||||
busy_ = false;
|
||||
runScheduledAction();
|
||||
return;
|
||||
}
|
||||
|
||||
if(SensorAction::READ == action){
|
||||
float value;
|
||||
sscanf((const char*) &data[1], "%f", &value);
|
||||
this->publish_state(value);
|
||||
}
|
||||
else if(SensorAction::IS_CALIBRATED == action){
|
||||
ESP_LOGD(TAG, "%s", data[6] == 0x31 ? "Device has been calibrated" : "Device has NOT been calibrated");
|
||||
}
|
||||
else if(SensorAction::INFORMATION == action){
|
||||
ESP_LOGD(TAG, "Device Printed the following information: %s", &data[1]);
|
||||
}
|
||||
|
||||
busy_ = false;
|
||||
|
||||
runScheduledAction();
|
||||
}
|
||||
|
||||
void EzoOrpSensor::calibrate_to_target(int target){
|
||||
char targetArray[3];
|
||||
sprintf(targetArray, "%d", target);
|
||||
ESP_LOGD(TAG, "Calibrating to: %3s", targetArray);
|
||||
scheduleNextAction(SensorAction::CALIBRATE, (uint8_t*) &targetArray, 3, 4000);
|
||||
|
||||
}
|
||||
|
||||
float EzoOrpSensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void EzoOrpSensor::dump_config(){
|
||||
LOG_SENSOR("", "EzoOrpI2C Sensor", this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_I2C_DEVICE(this);
|
||||
}
|
||||
|
||||
bool EzoOrpSensor::is_busy(){
|
||||
return this->busy_;
|
||||
}
|
||||
|
||||
void EzoOrpSensor::runScheduledAction(){
|
||||
if(scheduledAction_ == SensorAction::NOTHING) return;
|
||||
scheduleNextAction(scheduledAction_, scheduledActionData_, scheduledActionDataSize_, scheduledActionTime_);
|
||||
scheduledAction_ = SensorAction::NOTHING;
|
||||
scheduledActionDataSize_ = 0;
|
||||
}
|
||||
|
||||
void EzoOrpSensor::scheduleNextAction(SensorAction action, uint8_t* data, int size, int expected_response_time){
|
||||
if(busy_){
|
||||
if(scheduledAction_ == action) return; // Queued Action cannot has no need of being repeated;
|
||||
if(scheduledAction_ != SensorAction::NOTHING) return; // Ehh... Felt better lol
|
||||
|
||||
scheduledAction_ = action;
|
||||
if(data != nullptr){
|
||||
for(int i = 0;i < size; ++i){
|
||||
scheduledActionData_[i] = data[i];
|
||||
}
|
||||
}
|
||||
scheduledActionDataSize_ = size;
|
||||
scheduledActionTime_ = expected_response_time;
|
||||
return;
|
||||
}
|
||||
|
||||
switch(action){
|
||||
case SensorAction::NOTHING:
|
||||
return;
|
||||
case SensorAction::READ:
|
||||
busy_ = true;
|
||||
send_command(COMMAND_READ, sizeof(COMMAND_READ));
|
||||
this->set_timeout(expected_response_time, [this](){ this->read_response(9, SensorAction::READ); });
|
||||
break;
|
||||
case SensorAction::CALIBRATE:
|
||||
busy_ = true;
|
||||
send_command(COMMAND_PERFERM_CALIBRATION, sizeof(COMMAND_PERFERM_CALIBRATION), scheduledActionData_, scheduledActionDataSize_);
|
||||
this->set_timeout(expected_response_time, [this](){ this->read_response(2, SensorAction::CALIBRATE); });
|
||||
break;
|
||||
case SensorAction::IS_CALIBRATED:
|
||||
busy_ = true;
|
||||
send_command(COMMAND_IS_CALIBRATED, sizeof(COMMAND_IS_CALIBRATED));
|
||||
this->set_timeout(expected_response_time, [this](){ this->read_response(8, SensorAction::IS_CALIBRATED); });
|
||||
break;
|
||||
case SensorAction::INFORMATION:
|
||||
busy_ = true;
|
||||
send_command(COMMAND_STATUS, sizeof(COMMAND_STATUS));
|
||||
this->set_timeout(expected_response_time, [this](){ this->read_response(18, SensorAction::INFORMATION); });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool EzoOrpSensor::send_command(const uint8_t* data, size_t dataSize){
|
||||
return send_command(data, dataSize, nullptr, 0);
|
||||
}
|
||||
bool EzoOrpSensor::send_command(const uint8_t* data, size_t dataSize, const uint8_t* parameterData, size_t parameterSize){
|
||||
|
||||
if(parameterData == nullptr){
|
||||
if(i2c::ERROR_OK != this->write(data, dataSize)){
|
||||
ESP_LOGW(TAG, "send_command failed writing data!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t completeData[dataSize + 1 + parameterSize];
|
||||
for(int i = 0;i < dataSize; ++i){
|
||||
completeData[i] = data[i];
|
||||
}
|
||||
completeData[dataSize++] = COMMMAND_TO_ARGS_SEPERATOR;
|
||||
for(int i = 0; i < parameterSize; ++i){
|
||||
completeData[dataSize + i] = parameterData[i];
|
||||
}
|
||||
|
||||
if(i2c::ERROR_OK != this->write(completeData, sizeof(completeData))){
|
||||
ESP_LOGW(TAG, "send_command failed writing data!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void EzoOrpSensor::print_info(){
|
||||
scheduleNextAction(SensorAction::INFORMATION, nullptr, 0, 300);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
73
external_components/ezo_orp_i2c/ezo_orp.h
Normal file
73
external_components/ezo_orp_i2c/ezo_orp.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "Arduino.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ezo_orp_i2c {
|
||||
|
||||
enum SensorAction: int{
|
||||
NOTHING = 0,
|
||||
READ = 10,
|
||||
CALIBRATE = 20,
|
||||
IS_CALIBRATED = 21,
|
||||
EXPORT_CALIBRATION = 30,
|
||||
FACTORY_RESET = 40,
|
||||
INFORMATION = 50,
|
||||
};
|
||||
|
||||
class EzoOrpSensor: public PollingComponent, public sensor::Sensor, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const;
|
||||
void calibrate_to_target(int target);
|
||||
void print_info();
|
||||
bool is_busy();
|
||||
protected:
|
||||
SensorAction scheduledAction_ = SensorAction::NOTHING; // THIS ACTS AS A SORT OF QUEUE
|
||||
uint8_t scheduledActionData_[20] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
int scheduledActionDataSize_ = 0;
|
||||
int scheduledActionTime_ = 0;
|
||||
bool busy_ = false;
|
||||
void read_response(int maxLength, SensorAction action);
|
||||
void runScheduledAction();
|
||||
void scheduleNextAction(SensorAction action, uint8_t* data, int size, int expected_response_time);
|
||||
bool send_command(const uint8_t* data, size_t dataSize);
|
||||
bool send_command(const uint8_t* data, size_t dataSize, const uint8_t* parameterData, size_t parameterSize);
|
||||
//bool read_data_(u_int8_t[] * read_data);
|
||||
};
|
||||
|
||||
|
||||
template<typename... Ts> class EzoCalibrateAction : public Action<Ts...> {
|
||||
public:
|
||||
EzoCalibrateAction(EzoOrpSensor *sensor) : sensor_(sensor) {}
|
||||
TEMPLATABLE_VALUE(float, value)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->sensor_->calibrate_to_target((int) this->value_.value(x...));
|
||||
}
|
||||
|
||||
protected:
|
||||
EzoOrpSensor *sensor_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class DeviceInfoAction : public Action<Ts...> {
|
||||
public:
|
||||
DeviceInfoAction(EzoOrpSensor *sensor) : sensor_(sensor) {}
|
||||
TEMPLATABLE_VALUE(float, value)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->sensor_->print_info();
|
||||
}
|
||||
|
||||
protected:
|
||||
EzoOrpSensor *sensor_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
62
external_components/ezo_orp_i2c/sensor.py
Normal file
62
external_components/ezo_orp_i2c/sensor.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import sensor, i2c
|
||||
from esphome.components.adc import sensor as adc
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
chlorine_sensor_ns = cg.esphome_ns.namespace('ezo_orp_i2c')
|
||||
ChlorineSensor = chlorine_sensor_ns.class_('EzoOrpSensor', cg.PollingComponent, sensor.Sensor, i2c.I2CDevice)
|
||||
|
||||
UNIT_MILLI_VOLT = "mV"
|
||||
|
||||
ICON_TEST_TUBE="mdi:test-tube"
|
||||
|
||||
ACTION_CONF_CALIBRATE_TARGET = "calibrate_target"
|
||||
|
||||
CalibrateAction = chlorine_sensor_ns.class_("EzoCalibrateAction", automation.Action)
|
||||
DeviceInfoAction = chlorine_sensor_ns.class_("DeviceInfoAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
ChlorineSensor,
|
||||
unit_of_measurement=UNIT_MILLI_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
icon=ICON_TEST_TUBE
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x62))
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
)
|
||||
|
||||
ACTION_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(ChlorineSensor)
|
||||
})
|
||||
|
||||
@automation.register_action("sensor.ezo_orp_i2c.calibrate", CalibrateAction, ACTION_SCHEMA.extend({
|
||||
cv.Required(ACTION_CONF_CALIBRATE_TARGET): cv.int_range(100, 999),
|
||||
}))
|
||||
async def calibrate_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[ACTION_CONF_CALIBRATE_TARGET], args, float)
|
||||
cg.add(var.set_value(template_))
|
||||
return var
|
||||
|
||||
@automation.register_action("sensor.ezo_orp_i2c.print_device_info", DeviceInfoAction, ACTION_SCHEMA)
|
||||
async def info_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
0
external_components/hoermann_door/__init__.py
Normal file
0
external_components/hoermann_door/__init__.py
Normal file
20
external_components/hoermann_door/cover.py
Normal file
20
external_components/hoermann_door/cover.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import cover
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
hoermann_cover_ns = cg.esphome_ns.namespace('hoermann_door')
|
||||
HoermannDoor = hoermann_cover_ns.class_('HoermanDoor', cover.Cover, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(HoermannDoor)
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield cover.register_cover(var, config)
|
||||
|
||||
cg.add_library("plerup/EspSoftwareSerial", "8.2.0")
|
127
external_components/hoermann_door/cover_component.h
Normal file
127
external_components/hoermann_door/cover_component.h
Normal file
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
#include "door_singleton.h"
|
||||
|
||||
#ifdef USE_COVER
|
||||
|
||||
#include "esphome.h"
|
||||
#include "Arduino.h"
|
||||
#include "hciemulator.h"
|
||||
//#include "cover.h"
|
||||
|
||||
#define RS485 Serial2
|
||||
#define TX_ON 25
|
||||
//#define configMAX_PRIORITIES 25
|
||||
|
||||
#define TAG "hoermann_door"
|
||||
|
||||
namespace esphome {
|
||||
namespace hoermann_door {
|
||||
|
||||
void modBusPolling(void *parameter);
|
||||
class HoermanDoor : public Component, public cover::Cover
|
||||
{
|
||||
public:
|
||||
|
||||
cover::CoverTraits get_traits() override {
|
||||
auto traits = cover::CoverTraits();
|
||||
traits.set_is_assumed_state(false);
|
||||
traits.set_supports_position(true);
|
||||
traits.set_supports_tilt(false);
|
||||
traits.set_supports_stop(true);
|
||||
traits.set_supports_toggle(true);
|
||||
return traits;
|
||||
}
|
||||
|
||||
//u_int8_t stopAt == 0;
|
||||
bool manual = false;
|
||||
void control(const cover::CoverCall &call) override {
|
||||
ESP_LOGW(TAG, "In func control door");
|
||||
// This will be called every time the user requests a state change.
|
||||
if (call.get_position().has_value()) {
|
||||
|
||||
float pos = *call.get_position();
|
||||
|
||||
//emulator(&RS485);
|
||||
// Write pos (range 0-1) to cover
|
||||
// ...
|
||||
|
||||
|
||||
if(pos == 1.0){
|
||||
HoermannSingleton::getInstance()->getEmulator()->openDoor();
|
||||
manual = false;
|
||||
}
|
||||
else if(pos == 0.0){
|
||||
HoermannSingleton::getInstance()->getEmulator()->closeDoor();
|
||||
manual = false;
|
||||
}
|
||||
else{
|
||||
ESP_LOGD("Door controller", "Not yet supported");
|
||||
}
|
||||
|
||||
}
|
||||
if (call.get_stop()) {
|
||||
HoermannSingleton::getInstance()->getEmulator()->stopDoor();
|
||||
}
|
||||
//if(call.get_close()) {
|
||||
//emulator.closeDoor();
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
void modBusPolling(void *parameter)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (lastCall > 0)
|
||||
{
|
||||
maxPeriod = _max(micros() - lastCall, maxPeriod);
|
||||
}
|
||||
lastCall = micros();
|
||||
emulator.poll();
|
||||
vTaskDelay(1);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
*/
|
||||
|
||||
void setup() override
|
||||
{
|
||||
HoermannSingleton::getInstance()->initializeEmulator();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void open(){
|
||||
//emulator.openDoor();
|
||||
}
|
||||
void close(){
|
||||
//emulator.closeDoor();
|
||||
}
|
||||
void stop(){
|
||||
//emulator.stopDoor();
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
unsigned long lastStatus = 0;
|
||||
u_int8_t pos = 255;
|
||||
void loop() override
|
||||
{
|
||||
u_int8_t position = HoermannSingleton::getInstance()->getEmulator()->getState().doorCurrentPosition;
|
||||
if(pos != position){
|
||||
this->position = (float) position / 200.0;
|
||||
this->publish_state();
|
||||
pos = position;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace hoermann
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
79
external_components/hoermann_door/door_singleton.h
Normal file
79
external_components/hoermann_door/door_singleton.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome.h"
|
||||
#include "Arduino.h"
|
||||
#include "hciemulator.h"
|
||||
//#include "cover.h"
|
||||
|
||||
#define RS485 Serial2
|
||||
#define TX_ON 25
|
||||
//#define configMAX_PRIORITIES 25
|
||||
|
||||
#define TAG "hoermann_door"
|
||||
|
||||
|
||||
void modBusPolling(void *parameter);
|
||||
|
||||
class HoermannSingleton {
|
||||
public:
|
||||
HoermannSingleton() {
|
||||
emulator;
|
||||
}
|
||||
static HoermannSingleton* instance_;
|
||||
HCIEmulator emulator;
|
||||
TaskHandle_t modBusTask;
|
||||
bool hasBeenInitialized = false;
|
||||
|
||||
public:
|
||||
static HoermannSingleton* getInstance(){
|
||||
if(instance_ == nullptr){
|
||||
instance_ = new HoermannSingleton();
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
void initializeEmulator(){
|
||||
if(hasBeenInitialized) return;
|
||||
hasBeenInitialized = true;
|
||||
RS485.begin(57600, SERIAL_8E1, 16, 17);
|
||||
pinMode(TX_ON, OUTPUT);
|
||||
digitalWrite(TX_ON, LOW);
|
||||
|
||||
xTaskCreatePinnedToCore(
|
||||
modBusPolling, // Function to implement the task
|
||||
"ModBusTask", // Name of the task
|
||||
10000, // Stack size in words
|
||||
NULL, // Task input parameter
|
||||
// 1, // Priority of the task
|
||||
configMAX_PRIORITIES - 1,
|
||||
&modBusTask, // Task handle.
|
||||
1 // Core where the task should run
|
||||
);
|
||||
}
|
||||
HCIEmulator *getEmulator(){
|
||||
return &emulator;
|
||||
}
|
||||
};
|
||||
|
||||
HoermannSingleton* HoermannSingleton::instance_ = nullptr;
|
||||
|
||||
//DoorManager *DoorManager::getInstance()
|
||||
|
||||
|
||||
volatile unsigned long lastCall = 0;
|
||||
volatile unsigned long maxPeriod = 0;
|
||||
void modBusPolling(void *parameter)
|
||||
{
|
||||
auto emulator = HoermannSingleton::getInstance()->getEmulator();
|
||||
while (true)
|
||||
{
|
||||
if (lastCall > 0)
|
||||
{
|
||||
maxPeriod = _max(micros() - lastCall, maxPeriod);
|
||||
}
|
||||
lastCall = micros();
|
||||
emulator->poll();
|
||||
vTaskDelay(1);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
492
external_components/hoermann_door/hciemulator.cpp
Normal file
492
external_components/hoermann_door/hciemulator.cpp
Normal file
@@ -0,0 +1,492 @@
|
||||
#include "hciemulator.h"
|
||||
#include "Arduino.h"
|
||||
#define CHECKCHANGEDSET(Target, Value, Flag) \
|
||||
if ((Target) != (Value)) \
|
||||
{ \
|
||||
Target = Value; \
|
||||
Flag = true; \
|
||||
}
|
||||
int hciloglevel = LL_DEBUG;
|
||||
|
||||
#define SOFTSERIAL 1
|
||||
|
||||
#ifdef SOFTSERIAL
|
||||
#define Log(Level, Message) LogCore(Level, Message)
|
||||
#define Log3(Level, Message, Buffer, Len) LogCore(Level, Message, Buffer, Len)
|
||||
// LOGLEVEL
|
||||
void LogCore(int Level, const char *msg, const unsigned char *data = NULL, size_t datalen = 0)
|
||||
{
|
||||
if (Level > hciloglevel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (data != NULL && datalen > 0)
|
||||
{
|
||||
String newmsg(msg);
|
||||
char str[4];
|
||||
for (size_t i = 0; i < datalen; i++)
|
||||
{
|
||||
snprintf(str, sizeof(str), "%02x ", data[i]);
|
||||
newmsg += str;
|
||||
}
|
||||
Serial.println(newmsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println(msg);
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define Log(Level, Message)
|
||||
#define Log3(Level, Message, Buffer, Len)
|
||||
#endif
|
||||
|
||||
int HCIEmulator::getLogLevel()
|
||||
{
|
||||
return hciloglevel;
|
||||
}
|
||||
void HCIEmulator::setLogLevel(int level)
|
||||
{
|
||||
hciloglevel = level;
|
||||
}
|
||||
|
||||
// modbus crc calculation borrowed from:
|
||||
// https://github.com/yaacov/ArduinoModbusSlave
|
||||
#define MODBUS_CRC_LENGTH 2
|
||||
#define readCRC(arr, length) word(arr[(length - MODBUS_CRC_LENGTH) + 1], arr[length - MODBUS_CRC_LENGTH])
|
||||
#define readUInt16(arr, index) word(arr[index], arr[index + 1])
|
||||
/**
|
||||
* Calculate the CRC of the passed byte array from zero up to the passed length.
|
||||
*
|
||||
* @param buffer The byte array containing the data.
|
||||
* @param length The length of the byte array.
|
||||
*
|
||||
* @return The calculated CRC as an unsigned 16 bit integer.
|
||||
*
|
||||
* Calculate and add the CRC.
|
||||
* uint16_t crc = Modbus::calculateCRC(_responseBuffer, _responseBufferLength - MODBUS_CRC_LENGTH);
|
||||
* _responseBuffer[_responseBufferLength - MODBUS_CRC_LENGTH] = crc & 0xFF;
|
||||
* _responseBuffer[(_responseBufferLength - MODBUS_CRC_LENGTH) + 1] = crc >> 8;
|
||||
*
|
||||
*
|
||||
* #define MODBUS_FRAME_SIZE 4
|
||||
* #define MODBUS_CRC_LENGTH 2
|
||||
* uint16_t crc = readCRC(_requestBuffer, _requestBufferLength);
|
||||
* #define readUInt16(arr, index) word(arr[index], arr[index + 1])
|
||||
* #define readCRC(arr, length) word(arr[(length - MODBUS_CRC_LENGTH) + 1], arr[length - MODBUS_CRC_LENGTH])
|
||||
*/
|
||||
uint16_t calculateCRC(uint8_t *buffer, int length)
|
||||
{
|
||||
int i, j;
|
||||
uint16_t crc = 0xFFFF;
|
||||
uint16_t tmp;
|
||||
|
||||
// Calculate the CRC.
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
crc = crc ^ buffer[i];
|
||||
for (j = 0; j < 8; j++)
|
||||
{
|
||||
tmp = crc & 0x0001;
|
||||
crc = crc >> 1;
|
||||
if (tmp)
|
||||
{
|
||||
crc = crc ^ 0xA001;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
HCIEmulator::HCIEmulator()
|
||||
{
|
||||
m_state.valid = false;
|
||||
m_statemachine = WAITING;
|
||||
m_rxlen = m_txlen = 0;
|
||||
m_recvTime = m_lastStateTime = 0;
|
||||
m_skipFrame = false;
|
||||
m_port = &Serial2;
|
||||
m_statusCallback = NULL;
|
||||
setLogLevel(DEFAULTLOGLEVEL);
|
||||
};
|
||||
|
||||
#define TX_ON 25
|
||||
|
||||
void HCIEmulator::poll()
|
||||
{
|
||||
|
||||
if (m_port == NULL)
|
||||
return;
|
||||
|
||||
// receive Data
|
||||
if (m_port->available() > 0)
|
||||
{
|
||||
// Serial.println("got data");
|
||||
m_rxlen += m_port->readBytes((char *)(m_rxbuffer + m_rxlen), _min((int)(255 - m_rxlen), m_port->available()));
|
||||
if (m_rxlen > 254)
|
||||
{
|
||||
Log(LL_ERROR, "RX Bufferoverflow, skip next Frame");
|
||||
Log3(LL_DEBUG, "Buffer Data: ", m_rxbuffer, m_rxlen);
|
||||
m_rxlen = 0;
|
||||
m_skipFrame = true;
|
||||
}
|
||||
m_recvTime = micros();
|
||||
}
|
||||
|
||||
// Serial.printf("Data % x\n", m_txbuffer);
|
||||
// check frame, process frame
|
||||
if (m_rxlen > 0 && (micros() - m_recvTime > T3_5))
|
||||
{
|
||||
// Serial.printf("Act on it % x\n", m_txbuffer);
|
||||
// check last action timeout -> reset > then 2sec
|
||||
if (m_statemachine != WAITING && m_lastStateTime + 2000 < millis())
|
||||
{
|
||||
m_statemachine = WAITING;
|
||||
}
|
||||
|
||||
if (!m_skipFrame)
|
||||
{
|
||||
|
||||
processFrame();
|
||||
|
||||
// send response
|
||||
if (m_txlen > 0)
|
||||
{
|
||||
// fix crc
|
||||
uint16_t crc = calculateCRC(m_txbuffer, m_txlen - MODBUS_CRC_LENGTH);
|
||||
m_txbuffer[m_txlen - MODBUS_CRC_LENGTH] = crc & 0xFF;
|
||||
m_txbuffer[(m_txlen - MODBUS_CRC_LENGTH) + 1] = crc >> 8;
|
||||
|
||||
// send data
|
||||
m_lastSendTime = micros() - m_recvTime;
|
||||
|
||||
// Log(LL_DEBUG, ("ST:"+String(m_lastSendTime)).c_str());
|
||||
|
||||
digitalWrite(TX_ON, HIGH);
|
||||
// Log3(LL_DEBUG, "write data: ");
|
||||
m_port->write(m_txbuffer, m_txlen);
|
||||
Log3(LL_DEBUG, "Response: ", m_txbuffer, m_txlen);
|
||||
delayMicroseconds(m_txlen * 9 * 22); // 8 bits + par * Bittime 18 micros on 57600 bauds
|
||||
digitalWrite(TX_ON, LOW);
|
||||
m_txlen = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("skipped frame");
|
||||
}
|
||||
|
||||
m_skipFrame = false;
|
||||
m_rxlen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void HCIEmulator::processFrame()
|
||||
{
|
||||
m_txlen = 0; // clear send buffer
|
||||
|
||||
if (m_rxlen < 5)
|
||||
{
|
||||
Log(LL_ERROR, "Frame skipped, invalid frame len");
|
||||
Log3(LL_ERROR, "Data:", m_rxbuffer, m_rxlen);
|
||||
return;
|
||||
}
|
||||
|
||||
// check device id, pass only device id 2 and 0 (broadcast)
|
||||
if (m_rxbuffer[0] != BROADCASTID && m_rxbuffer[0] != DEVICEID)
|
||||
{
|
||||
Log(LL_DEBUG, "Frame skipped, unsupported device id");
|
||||
Log3(LL_DEBUG, "Data:", m_rxbuffer, m_rxlen);
|
||||
return;
|
||||
}
|
||||
|
||||
// check crc
|
||||
uint16_t crc = readCRC(m_rxbuffer, m_rxlen);
|
||||
if (crc != calculateCRC(m_rxbuffer, m_rxlen - MODBUS_CRC_LENGTH))
|
||||
{
|
||||
Log3(LL_ERROR, "Frame skipped, wrong crc", m_rxbuffer, m_rxlen);
|
||||
return;
|
||||
}
|
||||
|
||||
Log3(LL_DEBUG, "Incomming Data: ", m_rxbuffer, m_rxlen);
|
||||
|
||||
// dispatch modbus function
|
||||
switch (m_rxbuffer[1])
|
||||
{
|
||||
case 0x10:
|
||||
{ // Write Multiple registers
|
||||
if (m_rxlen == 0x1b && m_rxbuffer[0] == BROADCASTID)
|
||||
{
|
||||
processBroadcastStatusFrame();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x17:
|
||||
{ // Read/Write Multiple registers
|
||||
if (m_rxbuffer[0] == DEVICEID)
|
||||
{
|
||||
switch (m_rxlen)
|
||||
{
|
||||
case 0x11:
|
||||
{
|
||||
processDeviceStatusFrame();
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x13:
|
||||
processDeviceBusScanFrame();
|
||||
return;
|
||||
;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
Log3(LL_ERROR, "Frame skipped, unexpected data: ", m_rxbuffer, m_rxlen);
|
||||
}
|
||||
|
||||
const unsigned char ResponseTemplate_Fcn17_Cmd03_L08[] = {0x02, 0x17, 0x10, 0x3E, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x1B};
|
||||
const unsigned char ResponseTemplate_Fcn17_Cmd04_L02[] = {0x02, 0x17, 0x04, 0x0F, 0x00, 0x04, 0xFD, 0x0A, 0x72};
|
||||
void HCIEmulator::processDeviceStatusFrame()
|
||||
{
|
||||
if (m_rxlen == 0x11)
|
||||
{
|
||||
unsigned char counter = m_rxbuffer[11];
|
||||
unsigned char cmd = m_rxbuffer[12];
|
||||
if (m_rxbuffer[5] == 0x08)
|
||||
{
|
||||
// expose internal state
|
||||
// 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
|
||||
// 0011: 02 17 9C B9 00 08 9C 41 00 02 04 3E 03 00 00 EB CC
|
||||
// res=> 02 17 10 3E 00 03 01 00 00 00 00 00 00 00 00 00 00 00 00 74 1B
|
||||
memcpy(m_txbuffer, ResponseTemplate_Fcn17_Cmd03_L08, sizeof(ResponseTemplate_Fcn17_Cmd03_L08));
|
||||
m_txbuffer[0] = m_rxbuffer[0];
|
||||
m_txbuffer[3] = counter;
|
||||
m_txbuffer[5] = cmd;
|
||||
m_txlen = sizeof(ResponseTemplate_Fcn17_Cmd03_L08);
|
||||
|
||||
switch (m_statemachine)
|
||||
{
|
||||
// open Door
|
||||
case STARTOPENDOOR:
|
||||
m_txbuffer[7] = 0x02;
|
||||
m_txbuffer[8] = 0x10;
|
||||
m_statemachine = STARTOPENDOOR_RELEASE;
|
||||
m_lastStateTime = millis();
|
||||
break;
|
||||
case STARTOPENDOOR_RELEASE:
|
||||
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
|
||||
{
|
||||
m_txbuffer[7] = 0x01;
|
||||
m_txbuffer[8] = 0x10;
|
||||
m_statemachine = WAITING;
|
||||
}
|
||||
break;
|
||||
|
||||
// close Door
|
||||
case STARTCLOSEDOOR:
|
||||
m_txbuffer[7] = 0x02;
|
||||
m_txbuffer[8] = 0x20;
|
||||
m_statemachine = STARTCLOSEDOOR_RELEASE;
|
||||
m_lastStateTime = millis();
|
||||
break;
|
||||
case STARTCLOSEDOOR_RELEASE:
|
||||
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
|
||||
{
|
||||
m_txbuffer[7] = 0x01;
|
||||
m_txbuffer[8] = 0x20;
|
||||
m_statemachine = WAITING;
|
||||
}
|
||||
break;
|
||||
|
||||
// stop Door
|
||||
case STARTSTOPDOOR:
|
||||
m_txbuffer[7] = 0x02;
|
||||
m_txbuffer[8] = 0x40;
|
||||
m_statemachine = STARTSTOPDOOR_RELEASE;
|
||||
m_lastStateTime = millis();
|
||||
break;
|
||||
case STARTSTOPDOOR_RELEASE:
|
||||
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
|
||||
{
|
||||
m_txbuffer[7] = 0x01;
|
||||
m_txbuffer[8] = 0x40;
|
||||
m_statemachine = WAITING;
|
||||
}
|
||||
break;
|
||||
|
||||
// Ventilation
|
||||
case STARTVENTPOSITION:
|
||||
m_txbuffer[7] = 0x02;
|
||||
m_txbuffer[9] = 0x40;
|
||||
m_statemachine = STARTVENTPOSITION_RELEASE;
|
||||
m_lastStateTime = millis();
|
||||
break;
|
||||
case STARTVENTPOSITION_RELEASE:
|
||||
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
|
||||
{
|
||||
m_txbuffer[7] = 0x01;
|
||||
m_txbuffer[9] = 0x40;
|
||||
m_statemachine = WAITING;
|
||||
}
|
||||
break;
|
||||
|
||||
// Half Position
|
||||
case STARTOPENDOORHALF:
|
||||
m_txbuffer[7] = 0x02;
|
||||
m_txbuffer[9] = 0x04;
|
||||
m_statemachine = STARTOPENDOORHALF_RELEASE;
|
||||
m_lastStateTime = millis();
|
||||
break;
|
||||
|
||||
case STARTOPENDOORHALF_RELEASE:
|
||||
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
|
||||
{
|
||||
m_txbuffer[7] = 0x01;
|
||||
m_txbuffer[9] = 0x04;
|
||||
m_statemachine = WAITING;
|
||||
}
|
||||
break;
|
||||
|
||||
// Toggle Lamp
|
||||
case STARTTOGGLELAMP:
|
||||
m_txbuffer[7] = 0x10;
|
||||
m_txbuffer[9] = 0x02;
|
||||
m_statemachine = STARTTOGGLELAMP_RELEASE;
|
||||
m_lastStateTime = millis();
|
||||
break;
|
||||
case STARTTOGGLELAMP_RELEASE:
|
||||
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
|
||||
{
|
||||
m_txbuffer[7] = 0x08;
|
||||
m_txbuffer[9] = 0x02;
|
||||
m_statemachine = WAITING;
|
||||
}
|
||||
break;
|
||||
|
||||
case WAITING:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (m_rxbuffer[5] == 0x02)
|
||||
{
|
||||
// 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
|
||||
// 0011: 02 17 9C B9 00 02 9C 41 00 02 04 0F 04 17 00 7B 21
|
||||
// res=> 02 17 04 0F 00 04 FD 0A 72
|
||||
memcpy(m_txbuffer, ResponseTemplate_Fcn17_Cmd04_L02, sizeof(ResponseTemplate_Fcn17_Cmd04_L02));
|
||||
m_txbuffer[0] = m_rxbuffer[0];
|
||||
m_txbuffer[3] = counter;
|
||||
m_txbuffer[5] = cmd;
|
||||
m_txlen = sizeof(ResponseTemplate_Fcn17_Cmd04_L02);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log3(LL_ERROR, "Frame skipped, unexpected data: ", m_rxbuffer, m_rxlen);
|
||||
}
|
||||
|
||||
const unsigned char ResponseTemplate_Fcn17_Cmd02_L05[] = {0x02, 0x17, 0x0a, 0x00, 0x00, 0x02, 0x05, 0x04, 0x30, 0x10, 0xff, 0xa8, 0x45, 0x0e, 0xdf};
|
||||
void HCIEmulator::processDeviceBusScanFrame()
|
||||
{
|
||||
// 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
|
||||
// 0013: 02 17 9C B9 00 05 9C 41 00 03 06 00 02 00 00 01 02 f8 35
|
||||
// res=> 02 17 0a 00 00 02 05 04 30 10 ff a8 45 0e df
|
||||
unsigned char counter = m_rxbuffer[11];
|
||||
unsigned char cmd = m_rxbuffer[12];
|
||||
memcpy(m_txbuffer, ResponseTemplate_Fcn17_Cmd02_L05, sizeof(ResponseTemplate_Fcn17_Cmd02_L05));
|
||||
m_txbuffer[0] = m_rxbuffer[0];
|
||||
m_txbuffer[3] = counter;
|
||||
m_txbuffer[5] = cmd;
|
||||
m_txlen = sizeof(ResponseTemplate_Fcn17_Cmd02_L05);
|
||||
|
||||
Log(LL_INFO, "Busscan received");
|
||||
}
|
||||
|
||||
void HCIEmulator::processBroadcastStatusFrame()
|
||||
{
|
||||
// 001B: 00 10 9D 31 00 09 12 64 00 00 00 40 60 00 00 00 00 00 00 00 00 00 01 00 00 CA 22
|
||||
bool hasChanged = false;
|
||||
CHECKCHANGEDSET(m_state.lampOn, m_rxbuffer[20] == 0x14, hasChanged);
|
||||
CHECKCHANGEDSET(m_state.doorCurrentPosition, m_rxbuffer[10], hasChanged);
|
||||
CHECKCHANGEDSET(m_state.doorTargetPosition, m_rxbuffer[9], hasChanged);
|
||||
CHECKCHANGEDSET(m_state.doorState, m_rxbuffer[11], hasChanged);
|
||||
CHECKCHANGEDSET(m_state.reserved, m_rxbuffer[17], hasChanged);
|
||||
CHECKCHANGEDSET(m_state.valid, true, hasChanged);
|
||||
|
||||
if (hasChanged)
|
||||
{
|
||||
Log3(LL_INFO, "New State: ", m_rxbuffer, m_rxlen);
|
||||
if (m_statusCallback != NULL)
|
||||
{
|
||||
m_statusCallback(m_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HCIEmulator::openDoor()
|
||||
{
|
||||
if (m_statemachine != WAITING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_lastStateTime = millis();
|
||||
m_statemachine = STARTOPENDOOR;
|
||||
}
|
||||
|
||||
void HCIEmulator::openDoorHalf()
|
||||
{
|
||||
if (m_statemachine != WAITING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_lastStateTime = millis();
|
||||
m_statemachine = STARTOPENDOORHALF;
|
||||
}
|
||||
|
||||
void HCIEmulator::closeDoor()
|
||||
{
|
||||
if (m_statemachine != WAITING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_lastStateTime = millis();
|
||||
m_statemachine = STARTCLOSEDOOR;
|
||||
}
|
||||
|
||||
void HCIEmulator::stopDoor()
|
||||
{
|
||||
if (m_statemachine != WAITING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_lastStateTime = millis();
|
||||
m_statemachine = STARTSTOPDOOR;
|
||||
}
|
||||
|
||||
void HCIEmulator::toggleLamp()
|
||||
{
|
||||
if (m_statemachine != WAITING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_lastStateTime = millis();
|
||||
m_statemachine = STARTTOGGLELAMP;
|
||||
}
|
||||
|
||||
void HCIEmulator::ventilationPosition()
|
||||
{
|
||||
if (m_statemachine != WAITING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_lastStateTime = millis();
|
||||
m_statemachine = STARTVENTPOSITION;
|
||||
}
|
||||
|
||||
void HCIEmulator::onStatusChanged(callback_function_t handler)
|
||||
{
|
||||
m_statusCallback = handler;
|
||||
}
|
138
external_components/hoermann_door/hciemulator.h
Normal file
138
external_components/hoermann_door/hciemulator.h
Normal file
@@ -0,0 +1,138 @@
|
||||
#ifndef __hciemulator_h
|
||||
#define __hciemulator_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Stream.h>
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
#define LL_OFF 0
|
||||
#define LL_ERROR 1
|
||||
#define LL_WARN 2
|
||||
#define LL_INFO 3
|
||||
#define LL_DEBUG 4
|
||||
|
||||
#define DEFAULTLOGLEVEL LL_INFO
|
||||
|
||||
#define DEVICEID 0x02
|
||||
#define BROADCASTID 0x00
|
||||
#define SIMULATEKEYPRESSDELAYMS 100
|
||||
|
||||
// Modbus states that a baud rate higher than 19200 must use a fixed 750 us
|
||||
// for inter character time out and 1.75 ms for a frame delay.
|
||||
// For baud rates below 19200 the timeing is more critical and has to be calculated.
|
||||
// E.g. 9600 baud in a 10 bit packet is 960 characters per second
|
||||
// In milliseconds this will be 960characters per 1000ms. So for 1 character
|
||||
// 1000ms/960characters is 1.04167ms per character and finaly modbus states an
|
||||
// intercharacter must be 1.5T or 1.5 times longer than a normal character and thus
|
||||
// 1.5T = 1.04167ms * 1.5 = 1.5625ms. A frame delay is 3.5T.
|
||||
#define T1_5 750
|
||||
#define T3_5 4800 // 1750
|
||||
|
||||
enum DoorState : uint8_t
|
||||
{
|
||||
DOOR_OPEN_POSITION = 0x20,
|
||||
DOOR_CLOSE_POSITION = 0x40,
|
||||
DOOR_HALF_POSITION = 0x80,
|
||||
DOOR_MOVE_CLOSEPOSITION = 0x02,
|
||||
DOOR_MOVE_OPENPOSITION = 0x01,
|
||||
};
|
||||
|
||||
struct SHCIState
|
||||
{
|
||||
bool valid;
|
||||
bool lampOn;
|
||||
uint8_t doorState; // see DoorState
|
||||
uint8_t doorCurrentPosition;
|
||||
uint8_t doorTargetPosition;
|
||||
uint8_t reserved;
|
||||
};
|
||||
|
||||
enum StateMachine : uint8_t
|
||||
{
|
||||
WAITING,
|
||||
|
||||
STARTOPENDOOR,
|
||||
STARTOPENDOOR_RELEASE,
|
||||
|
||||
STARTOPENDOORHALF,
|
||||
STARTOPENDOORHALF_RELEASE,
|
||||
|
||||
STARTCLOSEDOOR,
|
||||
STARTCLOSEDOOR_RELEASE,
|
||||
|
||||
STARTSTOPDOOR,
|
||||
STARTSTOPDOOR_RELEASE,
|
||||
|
||||
STARTTOGGLELAMP,
|
||||
STARTTOGGLELAMP_RELEASE,
|
||||
|
||||
STARTVENTPOSITION,
|
||||
STARTVENTPOSITION_RELEASE
|
||||
};
|
||||
|
||||
class HCIEmulator
|
||||
{
|
||||
public:
|
||||
typedef std::function<void(const SHCIState &)> callback_function_t;
|
||||
|
||||
HCIEmulator();
|
||||
|
||||
void poll();
|
||||
|
||||
void openDoor();
|
||||
void openDoorHalf();
|
||||
void closeDoor();
|
||||
void stopDoor();
|
||||
void toggleLamp();
|
||||
void ventilationPosition();
|
||||
|
||||
const SHCIState &getState()
|
||||
{
|
||||
if (micros() - m_recvTime > 2000000)
|
||||
{
|
||||
// 2 sec without statusmessage
|
||||
m_state.valid = false;
|
||||
}
|
||||
return m_state;
|
||||
};
|
||||
|
||||
unsigned long getMessageAge()
|
||||
{
|
||||
return micros() - m_recvTime;
|
||||
}
|
||||
|
||||
int getLogLevel();
|
||||
void setLogLevel(int level);
|
||||
|
||||
void onStatusChanged(callback_function_t handler);
|
||||
|
||||
protected:
|
||||
void processFrame();
|
||||
void processDeviceStatusFrame();
|
||||
void processDeviceBusScanFrame();
|
||||
void processBroadcastStatusFrame();
|
||||
|
||||
private:
|
||||
callback_function_t m_statusCallback;
|
||||
Stream *m_port;
|
||||
SHCIState m_state;
|
||||
StateMachine m_statemachine;
|
||||
|
||||
unsigned long m_recvTime;
|
||||
unsigned long m_lastStateTime;
|
||||
unsigned long m_lastSendTime;
|
||||
|
||||
size_t m_rxlen;
|
||||
size_t m_txlen;
|
||||
|
||||
unsigned char m_rxbuffer[255] = {
|
||||
0,
|
||||
};
|
||||
unsigned char m_txbuffer[255] = {
|
||||
0,
|
||||
};
|
||||
|
||||
bool m_skipFrame;
|
||||
};
|
||||
|
||||
#endif
|
106
external_components/hoermann_door/light_component.h
Normal file
106
external_components/hoermann_door/light_component.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
|
||||
#include "esphome.h"
|
||||
#include "Arduino.h"
|
||||
#include "hciemulator.h"
|
||||
#include "door_singleton.h"
|
||||
//#include "cover.h"
|
||||
|
||||
#define RS485 Serial2
|
||||
#define TX_ON 25
|
||||
//#define configMAX_PRIORITIES 25
|
||||
|
||||
#define TAG "hoermann_door"
|
||||
|
||||
namespace esphome {
|
||||
namespace hoermann_door {
|
||||
|
||||
// Custom binary output, for exposing binary states
|
||||
class NbsLightManager: public binary::BinaryLightOutput, public light::LightState {
|
||||
protected:
|
||||
HoermannSingleton *single;
|
||||
public:
|
||||
void setup() override {
|
||||
//single = HoermannSingleton::getInstance();
|
||||
//single->initializeGetInstance
|
||||
}
|
||||
|
||||
bool lastState = false;
|
||||
bool firstState = false;
|
||||
void loop() override {
|
||||
//if(!DoorManager.getEmulator().getState().valid) false;
|
||||
//if(!firstState || DoorManager.getEmulator().getState().lampOn != lastState){
|
||||
//ESP_LOGD("Test", "I have no idea");
|
||||
// lastState = DoorManager.getEmulator().getState().lampOn;
|
||||
// this->write_state(lastState);
|
||||
|
||||
// firstState = true;
|
||||
//}
|
||||
}
|
||||
};
|
||||
|
||||
// Custom binary output, for exposing binary states
|
||||
class NbsLightOutput: public output::BinaryOutput, public Component{
|
||||
|
||||
light::LightState *callback;
|
||||
HoermannSingleton *single;
|
||||
bool lastState = false;
|
||||
bool firstState = true;
|
||||
|
||||
public:
|
||||
|
||||
virtual void write_state(bool state){
|
||||
if(lastState != state){
|
||||
single->getEmulator()->toggleLamp();
|
||||
//lastState = state;
|
||||
}
|
||||
}
|
||||
|
||||
void set_state_callback(light::LightState *callback){
|
||||
if(callback == nullptr) ESP_LOGW("Hoermann_door(Light)", "Got Nullable callback");
|
||||
this->callback = callback;
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
if(!single->getEmulator()->getState().valid) false;
|
||||
if(firstState || single->getEmulator()->getState().lampOn != lastState){
|
||||
//ESP_LOGD("Test", "I have no idea");
|
||||
lastState = single->getEmulator()->getState().lampOn;
|
||||
if(lastState == true){
|
||||
ESP_LOGD("Hoermann_door(Light)", "Light State ON");
|
||||
|
||||
auto call = callback->make_call();
|
||||
call.set_state(true);
|
||||
call.perform();
|
||||
}
|
||||
else {
|
||||
ESP_LOGD("Hoermann_door(Light)", "Light State OFF");
|
||||
auto call = callback->make_call();
|
||||
call.set_state(false);
|
||||
call.perform();
|
||||
}
|
||||
|
||||
firstState = false;
|
||||
}
|
||||
}
|
||||
|
||||
void setup() override {
|
||||
single = HoermannSingleton::getInstance();
|
||||
single->initializeEmulator();
|
||||
}
|
||||
|
||||
void turn_on() override {
|
||||
this->write_state(true);
|
||||
}
|
||||
void turn_off() override {
|
||||
this->write_state(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace hoermann
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
24
external_components/hoermann_door/output.py
Normal file
24
external_components/hoermann_door/output.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output, light
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
hoermann_cover_ns = cg.esphome_ns.namespace('hoermann_door')
|
||||
HoermannCoverLight = hoermann_cover_ns.class_('NbsLightOutput', output.BinaryOutput, cg.Component)
|
||||
CONF_STATE_CALLBACK = "state_callback"
|
||||
|
||||
CONFIG_SCHEMA = output.BINARY_OUTPUT_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(HoermannCoverLight),
|
||||
cv.Required(CONF_STATE_CALLBACK): cv.use_id(light.LightState)
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield output.register_output(var, config)
|
||||
|
||||
callback = yield cg.get_variable(config[CONF_STATE_CALLBACK])
|
||||
cg.add(var.set_state_callback(callback))
|
||||
|
||||
cg.add_library("plerup/EspSoftwareSerial", "8.2.0")
|
41
external_components/tablesoccer_btn_helper/__init__.py
Normal file
41
external_components/tablesoccer_btn_helper/__init__.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import mqtt, gpio
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES=["mqtt"]
|
||||
|
||||
tablesoccer_helper_ns = cg.esphome_ns.namespace('tablesoccer_helper')
|
||||
BtnHelperClass = tablesoccer_helper_ns.class_('BtnHelperClass', mqtt.MQTTComponent, cg.Component)
|
||||
#CONF_WS2812_PIN = "pin"
|
||||
#CONF_WS2812_RGB_MODE = "rgb_mode"
|
||||
|
||||
CONF_SENSOR_RED = "sensor_blue"
|
||||
CONF_SENSOR_BLUE = "sender_red"
|
||||
CONF_BTN_RESET = "reset_pin"
|
||||
CONF_BTN_UNDO = "undo_pin"
|
||||
CONF_KEEP_ALIVE_OUTPUT = "keep_alive"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(BtnHelperClass),
|
||||
cv.Required(CONF_BTN_RESET): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_BTN_UNDO): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_KEEP_ALIVE_OUTPUT): cv.use_id(gpio.output.GPIOBinaryOutput),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
#yield cg.register_component(var, config)
|
||||
yield mqtt.register_mqtt_component(var, config)
|
||||
|
||||
btnReset = yield cg.gpio_pin_expression(config[CONF_BTN_RESET])
|
||||
cg.add(var.set_reset_pin(btnReset))
|
||||
|
||||
btnUndo = yield cg.gpio_pin_expression(config[CONF_BTN_UNDO])
|
||||
cg.add(var.set_undo_pin(btnUndo))
|
||||
|
||||
keep_alive = yield cg.get_variable(config[CONF_KEEP_ALIVE_OUTPUT])
|
||||
cg.add(var.set_keep_alive_output(keep_alive))
|
@@ -0,0 +1,73 @@
|
||||
#include "esphome.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace tablesoccer_helper {
|
||||
|
||||
class BtnHelperClass : public Component, public mqtt::CustomMQTTDevice {
|
||||
protected:
|
||||
|
||||
InternalGPIOPin *resetBtn;
|
||||
InternalGPIOPin *undoBtn;
|
||||
gpio::GPIOBinaryOutput *keep_alive;
|
||||
unsigned long lastThingDone = 0;
|
||||
unsigned long enableFrom = 0;
|
||||
|
||||
public:
|
||||
|
||||
void set_reset_pin(InternalGPIOPin *resetBtn){
|
||||
this->resetBtn = resetBtn;
|
||||
}
|
||||
void set_undo_pin(InternalGPIOPin *undoBtn){
|
||||
this->undoBtn = undoBtn;
|
||||
}
|
||||
void set_keep_alive_output(gpio::GPIOBinaryOutput *keep_alive){
|
||||
this->keep_alive = keep_alive;
|
||||
}
|
||||
|
||||
void setup() override {
|
||||
lastThingDone = millis();
|
||||
enableFrom = millis();
|
||||
|
||||
ESP_LOGD("Tablesoccer", "Ready");
|
||||
}
|
||||
|
||||
void reset_last_thing_done(){
|
||||
lastThingDone = millis();
|
||||
}
|
||||
|
||||
void send_score(std::string side){
|
||||
publish("tabletenniscounter/control/score/count", side, 2, false);
|
||||
lastThingDone = millis();
|
||||
enableFrom = lastThingDone + 200;
|
||||
}
|
||||
|
||||
void loop() override{
|
||||
|
||||
if(millis() < enableFrom) return;
|
||||
|
||||
if(resetBtn->digital_read() == LOW && undoBtn->digital_read() == LOW){
|
||||
ESP_LOGD("Tablesoccer", "User requested shutdown, Shutting Down.");
|
||||
keep_alive->set_state(false);
|
||||
}
|
||||
if(resetBtn->digital_read() == LOW){
|
||||
|
||||
publish("tabletenniscounter/control/score/reset", "", 2, false);
|
||||
|
||||
lastThingDone = millis();
|
||||
enableFrom = lastThingDone + 500;
|
||||
}
|
||||
else if(undoBtn->digital_read() == LOW){
|
||||
publish("tabletenniscounter/control/score/undo", "", 2, false);
|
||||
lastThingDone = millis();
|
||||
enableFrom = lastThingDone + 500;
|
||||
}
|
||||
else if((millis() - lastThingDone) >= 300000){
|
||||
ESP_LOGD("Tablesoccer", "Table soccer table has't been used in over 15 minutes, Shutting Down.");
|
||||
keep_alive->set_state(false);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
36
external_components/ws2812_table_tennis/__init__.py
Normal file
36
external_components/ws2812_table_tennis/__init__.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import mqtt, number
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
ws2812_table_tennis_ns = cg.esphome_ns.namespace('ws2812_table_tennis')
|
||||
W2812Display = ws2812_table_tennis_ns.class_('CustomNumberDisplayComponent', mqtt.MQTTComponent, cg.Component)
|
||||
|
||||
DEPENDENCIES = ['mqtt', 'number']
|
||||
|
||||
#CONF_WS2812_PIN = "pin"
|
||||
#CONF_WS2812_RGB_MODE = "rgb_mode"
|
||||
|
||||
CONF_RED_NUM = "red_number"
|
||||
CONF_BLUE_NUM = "blue_number"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(W2812Display),
|
||||
cv.Required(CONF_RED_NUM): cv.use_id(number.Number),
|
||||
cv.Required(CONF_BLUE_NUM): cv.use_id(number.Number)
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
#yield cg.register_component(var, config)
|
||||
yield mqtt.register_mqtt_component(var, config)
|
||||
|
||||
red = yield cg.get_variable(config[CONF_RED_NUM])
|
||||
cg.add(var.set_red_number(red))
|
||||
|
||||
blue = yield cg.get_variable(config[CONF_BLUE_NUM])
|
||||
cg.add(var.set_blue_number(blue))
|
||||
|
||||
cg.add_library("makuna/NeoPixelBus", "2.7.3")
|
373
external_components/ws2812_table_tennis/tableTennisDisplay.h
Normal file
373
external_components/ws2812_table_tennis/tableTennisDisplay.h
Normal file
@@ -0,0 +1,373 @@
|
||||
#include "esphome.h"
|
||||
#include "NeoPixelBus.h"
|
||||
//#include "multi_effect_handler.h"
|
||||
#define SEGMENT_LENGTH 5
|
||||
|
||||
namespace esphome {
|
||||
namespace ws2812_table_tennis {
|
||||
|
||||
class NbsEffect {
|
||||
private:
|
||||
int goalRed = 0, goalGreen = 0, goalBlue = 0;
|
||||
double currentRed = 0, currentGreen = 0, currentBlue = 0;
|
||||
double calcRed = 0, calcGreen = 0, calcBlue = 0;
|
||||
int stepsPerSecond = 0;
|
||||
int totalSteps = 0;
|
||||
int currentStep = 0;
|
||||
bool finalStep = true;
|
||||
|
||||
public:
|
||||
NbsEffect(int stepsPerSecond_){
|
||||
stepsPerSecond = stepsPerSecond_;
|
||||
}
|
||||
|
||||
void setGoalColorAsRgb(int red, int green, int blue, int timeInMilliseconds){
|
||||
|
||||
if(goalRed == red && goalGreen == green && goalBlue == blue){
|
||||
return;
|
||||
}
|
||||
|
||||
finalStep = false;
|
||||
|
||||
currentRed = (double) goalRed;
|
||||
currentGreen = (double) goalGreen;
|
||||
currentBlue = (double) goalBlue;
|
||||
|
||||
goalRed = red;
|
||||
goalGreen = green;
|
||||
goalBlue = blue;
|
||||
|
||||
double timeInSeconds = (double) timeInMilliseconds / 1000.0;
|
||||
totalSteps = (int) (timeInSeconds * (double) stepsPerSecond);
|
||||
currentStep = totalSteps;
|
||||
|
||||
calcRed = ((double) goalRed - currentRed) / totalSteps;
|
||||
calcGreen = ((double) goalGreen - currentGreen) / totalSteps;
|
||||
calcBlue = ((double) goalBlue - currentBlue) / totalSteps;
|
||||
|
||||
currentStep = 0;
|
||||
}
|
||||
|
||||
int debugGoalRed(){
|
||||
return goalRed;
|
||||
}
|
||||
double debugCurrentRed(){
|
||||
return currentRed;
|
||||
}
|
||||
double debugCalcRed(){
|
||||
return calcRed;
|
||||
}
|
||||
|
||||
bool step(){
|
||||
if(currentStep >= totalSteps){
|
||||
return false;
|
||||
}
|
||||
++currentStep;
|
||||
if(currentStep == totalSteps){
|
||||
currentRed = (double) goalRed;
|
||||
currentGreen = (double) goalGreen;
|
||||
currentBlue = (double) goalBlue;
|
||||
finalStep = true;
|
||||
return true;
|
||||
}
|
||||
currentRed += calcRed;
|
||||
currentGreen += calcGreen;
|
||||
currentBlue += calcBlue;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wasFinalStep(){
|
||||
return finalStep;
|
||||
}
|
||||
|
||||
int getCurrentRed(){
|
||||
return (int) currentRed;
|
||||
}
|
||||
int getCurrentGreen(){
|
||||
return (int) currentGreen;
|
||||
}
|
||||
int getCurrentBlue(){
|
||||
return (int) currentBlue;
|
||||
}
|
||||
};
|
||||
|
||||
NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBangWs2812xMethod> nbsStrip(143, D5);
|
||||
class CustomNumberDisplayComponent : public Component, public mqtt::CustomMQTTDevice {
|
||||
protected:
|
||||
const int middleSegment = SEGMENT_LENGTH * 14;
|
||||
|
||||
const int transitionLength = 500; // milliseconds
|
||||
const int middleLength = 2;
|
||||
const double brightness = 0.17;
|
||||
|
||||
NbsEffect *colorAnimation[4][7];
|
||||
NbsEffect *middleSegmentAnim;
|
||||
|
||||
unsigned long lastUpdate = 0;
|
||||
bool shouldShowNBS = true;
|
||||
|
||||
bool numbers[14][7] = {
|
||||
{0, 1, 1, 1, 1, 1, 1}, // 0
|
||||
{0, 1, 0, 0, 0, 0, 1}, // 1
|
||||
{1, 1, 1, 0, 1, 1, 0}, // 2
|
||||
{1, 1, 1, 0, 0, 1, 1}, // 3
|
||||
{1, 1, 0, 1, 0, 0, 1}, // 4
|
||||
{1, 0, 1, 1, 0, 1, 1}, // 5
|
||||
{1, 0, 1, 1, 1, 1, 1}, // 6
|
||||
{0, 1, 1, 0, 0, 0, 1}, // 7
|
||||
{1, 1, 1, 1, 1, 1, 1}, // 8
|
||||
{1, 1, 1, 1, 0, 1, 1}, // 9
|
||||
{0, 0, 0, 0, 0, 0, 0}, // No Display (10)
|
||||
{1, 0, 0, 0, 0, 0, 0}, // - (11)
|
||||
{1, 0, 0, 0, 1, 0, 1}, // n (12)
|
||||
{1, 0, 0, 1, 1, 1, 1} // b (13)
|
||||
};
|
||||
|
||||
template_::TemplateNumber *redNum;
|
||||
template_::TemplateNumber *blueNum;
|
||||
|
||||
public:
|
||||
|
||||
void set_red_number(template_::TemplateNumber *redNum){
|
||||
this->redNum = redNum;
|
||||
}
|
||||
void set_blue_number(template_::TemplateNumber *blueNum){
|
||||
this->blueNum = blueNum;
|
||||
}
|
||||
|
||||
void setup() override {
|
||||
|
||||
|
||||
|
||||
// Segment mapping
|
||||
int segPixelCounter = 0;
|
||||
for(int i = 0; i < 4; ++i){
|
||||
for(int j = 0; j < 7; ++j){
|
||||
colorAnimation[i][j] = new NbsEffect(40);
|
||||
}
|
||||
}
|
||||
middleSegmentAnim = new NbsEffect(40);
|
||||
|
||||
nbsStrip.Begin();
|
||||
setNumber(0, 12, 1.0, 0.0, 0.0);
|
||||
setNumber(1, 13, 0.0, 1.0, 0.0);
|
||||
setNumber(2, 5, 0.0, 0.0, 1.0);
|
||||
setNumber(3, 11, 0.5, 0.5, 0.5);
|
||||
for(int led = middleSegment; led < middleSegment + 2; ++led){
|
||||
nbsStrip.SetPixelColor(led, RgbColor(0, 0, 0));
|
||||
}
|
||||
nbsStrip.Show();
|
||||
lastUpdate = millis();
|
||||
|
||||
// also supports JSON messages
|
||||
subscribe_json("tabletenniscounter/display/number/command", &CustomNumberDisplayComponent::on_json_message, 2);
|
||||
|
||||
ESP_LOGD("TableTennisCounter", "Ready");
|
||||
}
|
||||
|
||||
void on_json_message(const std::string &topic, JsonObject root) {
|
||||
//checker = 0;
|
||||
if(!root.containsKey("state")){
|
||||
return;
|
||||
}
|
||||
if(root["state"] != "ON"){
|
||||
setNumber(0, 10);
|
||||
setNumber(1, 10);
|
||||
setNumber(2, 10);
|
||||
setNumber(3, 10);
|
||||
turnSegmentOff(middleSegmentAnim);
|
||||
publish_json("tabletenniscounter/display/number/state", [=](JsonObject root2) {
|
||||
root2["state"] = "OFF";
|
||||
}, 2, false);
|
||||
return;
|
||||
}
|
||||
if(shouldShowNBS){
|
||||
shouldShowNBS = false;
|
||||
lastUpdate = millis();
|
||||
}
|
||||
if (!root.containsKey("player1") || !root.containsKey("player2") || !(root.containsKey("player1Start") || root.containsKey("displayGreen"))){
|
||||
setNumber(0, 12, 1.0, 0.0, 0.0);
|
||||
setNumber(1, 13, 0.0, 1.0, 0.0);
|
||||
setNumber(2, 5, 0.0, 0.0, 1.0);
|
||||
setNumber(3, 11, 0.5, 0.5, 0.5);
|
||||
turnSegmentOff(middleSegmentAnim);
|
||||
publish_json("tabletenniscounter/display/number/state", [=](JsonObject root2) {
|
||||
root2["state"] = "ON";
|
||||
}, 2, false);
|
||||
return;
|
||||
}
|
||||
int player1 = root["player1"];
|
||||
int player2 = root["player2"];
|
||||
|
||||
redNum->publish_state(player1);
|
||||
blueNum->publish_state(player2);
|
||||
bool player1Start = root["player1Start"];
|
||||
// do something with Json Object
|
||||
if(player1 > 99) { player1 = 99; }
|
||||
else if(player1 < 0) { player1 = 0; }
|
||||
if(player2 > 99) { player2 = 99; }
|
||||
else if(player2 < 0) { player2 = 0; }
|
||||
|
||||
int player1T = player1 / 10;
|
||||
int player1O = player1 % 10;
|
||||
int player2T = player2 / 10;
|
||||
int player2O = player2 % 10;
|
||||
|
||||
int handleColorWinChange = 0;
|
||||
if(root.containsKey("player1Winner")){
|
||||
if(root["player1Winner"]){
|
||||
handleColorWinChange = 1;
|
||||
}
|
||||
else{
|
||||
handleColorWinChange = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if(player1T == 0)
|
||||
setNumber(0, 10);
|
||||
else{
|
||||
if(handleColorWinChange == 1){
|
||||
setNumber(0, player1T, 1.0, 1.0, 0.0);
|
||||
}else{
|
||||
setNumber(0, player1T);
|
||||
}
|
||||
}
|
||||
if(handleColorWinChange == 1){
|
||||
setNumber(1, player1O, 1.0, 1.0, 0.0);
|
||||
}else{
|
||||
setNumber(1, player1O);
|
||||
}
|
||||
if(player2T == 0){
|
||||
if(handleColorWinChange == 2){
|
||||
setNumber(2, player2O, 1.0, 1.0, 0.0);
|
||||
}else{
|
||||
setNumber(2, player2O);
|
||||
}
|
||||
setNumber(3, 10);
|
||||
}
|
||||
else{
|
||||
if(handleColorWinChange == 2){
|
||||
setNumber(2, player2T, 1.0, 1.0, 0.0);
|
||||
setNumber(3, player2O, 1.0, 1.0, 0.0);
|
||||
}else{
|
||||
setNumber(2, player2T);
|
||||
setNumber(3, player2O);
|
||||
}
|
||||
}
|
||||
|
||||
if(root.containsKey("displayGreen")){
|
||||
turnSegmentOn(middleSegmentAnim, brightness, 0.0, 1.0, 0.0);
|
||||
}
|
||||
else{
|
||||
if(player1Start){
|
||||
turnSegmentOn(middleSegmentAnim, brightness, 1.0, 0.0, 0.0);
|
||||
}
|
||||
else{
|
||||
turnSegmentOn(middleSegmentAnim, brightness, 0.0, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
// publish JSON using lambda syntax
|
||||
publish_json("tabletenniscounter/display/number/state", [=](JsonObject root2) {
|
||||
root2["state"] = "ON";
|
||||
root2["player1"] = player1;
|
||||
root2["player2"] = player2;
|
||||
root2["player1Start"] = player1Start;
|
||||
}, 2, false);
|
||||
}
|
||||
|
||||
void loop() override{
|
||||
|
||||
if(lastUpdate + 25 >= millis()){
|
||||
return;
|
||||
}
|
||||
if(lastUpdate + 50 < millis()){
|
||||
//ESP_LOGW("custom_display", "LAGWARN, lastUpdate timer was reseted!");
|
||||
lastUpdate = millis();
|
||||
}
|
||||
|
||||
lastUpdate += 25;
|
||||
|
||||
bool didUpdate = false;
|
||||
for(int i = 0; i < 4; ++i){
|
||||
for(int j = 0; j < 7; ++j){
|
||||
NbsEffect *effect = colorAnimation[i][j];
|
||||
int segStart = calculateSegStart(i, j);
|
||||
if(effect->step()){
|
||||
didUpdate = true;
|
||||
for(int led = segStart; led < segStart + SEGMENT_LENGTH; ++led){
|
||||
nbsStrip.SetPixelColor(led, RgbColor(effect->getCurrentRed(), effect->getCurrentGreen(), effect->getCurrentBlue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(colorAnimation[0][0]->wasFinalStep() && shouldShowNBS){
|
||||
shouldShowNBS = false;
|
||||
lastUpdate = millis() + 5000;
|
||||
setNumber(0, 10, 0.0, 0.0, 0.0);
|
||||
setNumber(1, 10, 0.0, 0.0, 0.0);
|
||||
setNumber(2, 10, 0.0, 0.0, 0.0);
|
||||
setNumber(3, 10, 0.0, 0.0, 0.0);
|
||||
}
|
||||
if(middleSegmentAnim->step()){
|
||||
didUpdate = true;
|
||||
//ESP_LOGD("custom", "#%d, Color Code: %d, %d, %d", checker++, middleSegmentAnim->getCurrentRed(), middleSegmentAnim->getCurrentGreen(), middleSegmentAnim->getCurrentBlue());
|
||||
for(int led = middleSegment; led < middleSegment + 2; ++led){
|
||||
nbsStrip.SetPixelColor(led, RgbColor(middleSegmentAnim->getCurrentRed(), middleSegmentAnim->getCurrentGreen(), middleSegmentAnim->getCurrentBlue()));
|
||||
}
|
||||
}
|
||||
if(didUpdate){
|
||||
nbsStrip.Show();
|
||||
}
|
||||
}
|
||||
|
||||
int calculateSegStart(int digit, int segment){
|
||||
int start = (digit * SEGMENT_LENGTH * 7) + (segment * SEGMENT_LENGTH);
|
||||
if(digit >= 2){
|
||||
start += 2;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
void turnSegmentOn(NbsEffect* segment, double brightness, double red, double green, double blue){
|
||||
segment->setGoalColorAsRgb(makeColorAsInt(brightness, red, 220), makeColorAsInt(brightness, green, 170), makeColorAsInt(brightness, blue, 235), 500);
|
||||
}
|
||||
void turnSegmentOff(NbsEffect* segment){
|
||||
segment->setGoalColorAsRgb(0, 0, 0, 500);
|
||||
}
|
||||
void setNumber(int digit, int number, double red, double green, double blue){
|
||||
if(digit < 0 || digit > 3){
|
||||
return;
|
||||
}
|
||||
for(int i = 0; i < 7; ++i){
|
||||
if(numbers[number][i]){
|
||||
turnSegmentOn(colorAnimation[digit][i], brightness, red, green, blue);
|
||||
}
|
||||
else{
|
||||
turnSegmentOff(colorAnimation[digit][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
void setNumber(int digit, int number){
|
||||
if(digit < 0 || digit > 3){
|
||||
return;
|
||||
}
|
||||
double red = 0;
|
||||
double blue = 0;
|
||||
if(digit == 0 || digit == 1){
|
||||
red = 1.0;
|
||||
blue = 0.0;
|
||||
}
|
||||
else {
|
||||
red = 0.0;
|
||||
blue = 1.0;
|
||||
}
|
||||
setNumber(digit, number, red, 0.0, blue);
|
||||
}
|
||||
int makeColorAsInt(double brightness, double value, double colorMax){
|
||||
return (int) (colorMax * value * brightness);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
46
garage1.yaml
Normal file
46
garage1.yaml
Normal file
@@ -0,0 +1,46 @@
|
||||
substitutions:
|
||||
garageSide: sy # sy, wo
|
||||
|
||||
esphome:
|
||||
name: garage${garageSide}
|
||||
platform: ESP32
|
||||
board: esp32dev
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret gd_passwd
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !secret gd_key
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
power_save_mode: none
|
||||
fast_connect: true
|
||||
logger:
|
||||
#level: VERY_VERBOSE
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: external_components/
|
||||
components: [ hoermann_door ]
|
||||
|
||||
cover:
|
||||
- platform: hoermann_door
|
||||
name: door_${garageSide}
|
||||
|
||||
light:
|
||||
- platform: binary
|
||||
name: door_${garageSide}_lamp
|
||||
id: lamp_id_${garageSide}
|
||||
output: light_out
|
||||
|
||||
output:
|
||||
- id: light_out
|
||||
platform: hoermann_door
|
||||
state_callback: lamp_id_${garageSide}
|
||||
|
||||
|
78
gong.yaml
Normal file
78
gong.yaml
Normal file
@@ -0,0 +1,78 @@
|
||||
esphome:
|
||||
name: gong
|
||||
platform: ESP8266
|
||||
board: nodemcuv2
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: !secret gong_key
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret gong_password
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: true
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: Ton 1
|
||||
id: ton_1
|
||||
icon: "mdi:music-note-eighth"
|
||||
on_press:
|
||||
- logger.log: "PLAY Ton1"
|
||||
- output.turn_on: ton1_pin
|
||||
- delay: 500ms
|
||||
- output.turn_off: ton1_pin
|
||||
- platform: template
|
||||
name: Ton 2
|
||||
id: ton_2
|
||||
icon: "mdi:music-note-eighth"
|
||||
on_press:
|
||||
- logger.log: "PLAY Ton2"
|
||||
- output.turn_on: ton2_pin
|
||||
- delay: 500ms
|
||||
- output.turn_off: ton2_pin
|
||||
- platform: template
|
||||
name: Ton 3
|
||||
id: ton_3
|
||||
icon: "mdi:music-note-eighth"
|
||||
on_press:
|
||||
- logger.log: "PLAY Ton3"
|
||||
- output.turn_on: ton3_pin
|
||||
- delay: 500ms
|
||||
- output.turn_off: ton3_pin
|
||||
- platform: template
|
||||
name: Ton 4
|
||||
id: ton_4
|
||||
icon: "mdi:music-note-eighth"
|
||||
on_press:
|
||||
- logger.log: "PLAY Ton4"
|
||||
- output.turn_on: ton4_pin
|
||||
- delay: 500ms
|
||||
- output.turn_off: ton4_pin
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
pin: D5
|
||||
id: ton1_pin
|
||||
- platform: gpio
|
||||
pin: D6
|
||||
id: ton2_pin
|
||||
- platform: gpio
|
||||
pin: D7
|
||||
id: ton3_pin
|
||||
- platform: gpio
|
||||
pin: D8
|
||||
id: ton4_pin
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
pin: D3
|
||||
name: "Gong Mute"
|
75
ledstrip_simple.yaml
Normal file
75
ledstrip_simple.yaml
Normal file
@@ -0,0 +1,75 @@
|
||||
substitutions:
|
||||
collection_name: mic_holder #mic_holder, stairs, test2
|
||||
|
||||
|
||||
device_name_mic_holder: ledstrip-mic-holder
|
||||
entity_prefix_mic_holder: NBS Mic
|
||||
led_count_mic_holder: "32"
|
||||
|
||||
device_name_stairs: ledstrip-stairs
|
||||
entity_prefix_stairs: Stairs
|
||||
led_count_stairs: "191"
|
||||
|
||||
device_name_test2: teststrip2
|
||||
entity_prefix_test2: Test2
|
||||
led_count_test2: "56"
|
||||
|
||||
esphome:
|
||||
name: ${device_name_${collection_name}}
|
||||
|
||||
esp8266:
|
||||
board: nodemcuv2
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ledsimplepassword
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !secret ledsimplekey
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: true
|
||||
|
||||
light:
|
||||
- platform: neopixelbus
|
||||
type: GRB
|
||||
variant: WS2811
|
||||
pin: D5
|
||||
num_leds: ${led_count_${collection_name}} # 56, 191
|
||||
name: ${entity_prefix_${collection_name}} Leds
|
||||
effects:
|
||||
- addressable_rainbow:
|
||||
- addressable_rainbow:
|
||||
name: Snail Rainbow
|
||||
width: 50
|
||||
speed: 3
|
||||
- addressable_rainbow:
|
||||
name: Fast Rainbow
|
||||
width: 50
|
||||
speed: 30
|
||||
- addressable_rainbow:
|
||||
name: Lightning Rainbow
|
||||
width: 50
|
||||
speed: 60
|
||||
- addressable_color_wipe:
|
||||
- addressable_scan:
|
||||
move_interval: 30ms
|
||||
- addressable_twinkle:
|
||||
twinkle_probability: 15%
|
||||
- addressable_random_twinkle:
|
||||
twinkle_probability: 15%
|
||||
- addressable_fireworks:
|
||||
name: normal
|
||||
spark_probability: 30%
|
||||
- addressable_fireworks:
|
||||
name: '50 Percent'
|
||||
spark_probability: 50%
|
||||
- addressable_fireworks:
|
||||
name: '70 Percent'
|
||||
spark_probability: 77%
|
57
light_switch.yaml
Normal file
57
light_switch.yaml
Normal file
@@ -0,0 +1,57 @@
|
||||
esphome:
|
||||
name: nbs-mainlight
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: true
|
||||
ap:
|
||||
ssid: nbs_light_rescue
|
||||
password: !secret rescue_ap_password
|
||||
|
||||
captive_portal:
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !secret ml_key
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ml_password
|
||||
|
||||
logger:
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO0
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
inverted: true
|
||||
id: button_1
|
||||
on_press:
|
||||
then:
|
||||
- light.toggle: light_1
|
||||
|
||||
- platform: status
|
||||
name: "T1 Status"
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
pin: GPIO12
|
||||
id: relay_1
|
||||
|
||||
light:
|
||||
- platform: binary
|
||||
name: Main Light
|
||||
id: light_1
|
||||
output: relay_1
|
||||
|
||||
status_led:
|
||||
pin:
|
||||
number: GPIO13
|
||||
inverted: no
|
110
pingpongfuss1.yaml
Normal file
110
pingpongfuss1.yaml
Normal file
@@ -0,0 +1,110 @@
|
||||
esphome:
|
||||
name: pingfuss1
|
||||
platform: ESP8266
|
||||
board: nodemcuv2
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- output.turn_on: st_led
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
# DISABLED - We currently do not need it for operation. Maybe someday
|
||||
|
||||
# api:
|
||||
# encryption:
|
||||
# key: !secret ttf_key
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ttf_password
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: true
|
||||
|
||||
|
||||
# Example configuration entry
|
||||
mqtt:
|
||||
broker: !secret mqtt_broker_1
|
||||
username: !secret mqtt_broker_1_username
|
||||
password: !secret mqtt_broker_1_password
|
||||
on_connect:
|
||||
then:
|
||||
- output.turn_off: st_led
|
||||
on_disconnect:
|
||||
then:
|
||||
- output.turn_on: st_led
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: D0
|
||||
mode: output
|
||||
inverted: true
|
||||
id: st_led
|
||||
|
||||
# Example configuration entry
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: D2
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
id: "Fusstaste_BLAU"
|
||||
internal: true
|
||||
#filters:
|
||||
# - delayed_on: 10ms
|
||||
# - delayed_off: 50ms
|
||||
on_multi_click:
|
||||
- timing:
|
||||
- ON for at most 700ms
|
||||
- OFF for at least 50ms
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: tabletenniscounter/control/score/count
|
||||
payload: "1"
|
||||
qos: 2
|
||||
- timing:
|
||||
- ON for at least 1s
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: tabletenniscounter/control/score/undo
|
||||
payload: ""
|
||||
qos: 2
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: D3
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
id: "Fusstaste_ROT"
|
||||
internal: true
|
||||
#filters:
|
||||
# - delayed_on: 10ms
|
||||
# - delayed_off: 50ms
|
||||
on_multi_click:
|
||||
- timing:
|
||||
- ON for at most 700ms
|
||||
- OFF for at least 50ms
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: tabletenniscounter/control/score/count
|
||||
payload: "0"
|
||||
qos: 2
|
||||
- timing:
|
||||
- ON for at least 1s
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: tabletenniscounter/control/score/undo
|
||||
payload: ""
|
||||
qos: 2
|
||||
|
||||
|
||||
|
@@ -2,9 +2,54 @@
|
||||
wifi_ssid: "WIFI SSID"
|
||||
wifi_password: "WIFI PASSWORD"
|
||||
|
||||
# Broker 1
|
||||
mqtt_broker_1: # Broker 1 IP
|
||||
mqtt_broker_1_username: # Broker 1 username
|
||||
mqtt_broker_1_password: # Broker 1 password
|
||||
|
||||
# Papa Broker
|
||||
mqtt_broker_2: # Just put your broker in here...
|
||||
|
||||
# A3Pool
|
||||
a3poolpasswd: # A3Pool OTA and native API password
|
||||
a3poolkey: # Native API encryption KEY
|
||||
a3poolkey: # Native API encryption KEY
|
||||
|
||||
# Table Tennis Footswitches (No ttf encryption but may be added)
|
||||
ttf_password: # TableTennisFootswitch OTA password
|
||||
# ttfkey: # Native API encryption KEY
|
||||
|
||||
# Table Tennis ledstrip display
|
||||
ttdpassword: # TableTennisPassword OTA and native API password
|
||||
ttdkey: # Native API encryption KEY
|
||||
|
||||
# Sonoff Stecker (Currently Dartboard Light)
|
||||
ss1password: # Sonoff Switch 1 OTA and native API password
|
||||
ss1key: # Native API encryption KEY
|
||||
|
||||
# Ledstrip Simple
|
||||
ledsimplepassword: # Ledstrip Simple OTA and native API password
|
||||
ledsimplekey: # Native API encryption KEY
|
||||
|
||||
# Bluetooth Low Energy Global Vars test
|
||||
ble_password: <INSERT>
|
||||
ble_key: <INSERT>
|
||||
|
||||
# streamsign
|
||||
streamsign_password: <INSERT>
|
||||
streamsign_key: <INSERT>
|
||||
|
||||
# Clor Pump
|
||||
cp_key: <INSERT>
|
||||
cp_password: <INSERT>
|
||||
|
||||
# Gong
|
||||
gong_key: <INSERT>
|
||||
gong_password: <INSERT>
|
||||
|
||||
# Garage Doors
|
||||
gd_key: <INSERT>
|
||||
gd_passwd: <INSERT>
|
||||
|
||||
# T1 Sonoff touch switch
|
||||
ml_key: <INSERT>
|
||||
ml_password: <INSERT>
|
||||
|
49
sonoffStecker1.yaml
Normal file
49
sonoffStecker1.yaml
Normal file
@@ -0,0 +1,49 @@
|
||||
esphome:
|
||||
name: sonoffstecker1
|
||||
|
||||
esp8266:
|
||||
board: sonoff_basic
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
# password: !secret ss1password
|
||||
encryption:
|
||||
key: !secret ss1key
|
||||
|
||||
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ss1password
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: true
|
||||
ap:
|
||||
ssid: sonoff_stecker_1_rescue
|
||||
password: !secret rescue_ap_password
|
||||
|
||||
captive_portal:
|
||||
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
name: "Sonoff Stecker 1"
|
||||
pin: GPIO12
|
||||
id: relay
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO0
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
inverted: true
|
||||
id: "sonoff_power"
|
||||
on_press:
|
||||
- switch.toggle: relay
|
27
streamsign.yaml
Normal file
27
streamsign.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
esphome:
|
||||
name: streamsign
|
||||
|
||||
esp8266:
|
||||
board: d1_mini_lite
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: true
|
||||
logger:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret streamsign_password
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !secret streamsign_key
|
||||
|
||||
light:
|
||||
- platform: neopixelbus
|
||||
variant: WS2812
|
||||
pin: D4
|
||||
num_leds: 5
|
||||
type: GRB
|
||||
name: "Streaming Sign"
|
53
tableTennisDisplay.yaml
Normal file
53
tableTennisDisplay.yaml
Normal file
@@ -0,0 +1,53 @@
|
||||
esphome:
|
||||
name: tabletenniscounter
|
||||
platform: ESP8266
|
||||
board: nodemcuv2
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: external_components/
|
||||
components: [ ws2812_table_tennis ]
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ttdpassword
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !secret ttdkey
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: true
|
||||
|
||||
number:
|
||||
- platform: template
|
||||
name: "Red Number"
|
||||
id: red_num
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
max_value: 99
|
||||
step: 1
|
||||
- platform: template
|
||||
name: "Blue Number"
|
||||
id: blue_num
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
max_value: 99
|
||||
step: 1
|
||||
|
||||
mqtt:
|
||||
broker: !secret mqtt_broker_1
|
||||
client_id: "tableTennisCounter"
|
||||
username: !secret mqtt_broker_1_username
|
||||
password: !secret mqtt_broker_1_password
|
||||
|
||||
ws2812_table_tennis:
|
||||
# name: Led Display
|
||||
red_number: red_num
|
||||
blue_number: blue_num
|
101
tablesoccer.yaml
Normal file
101
tablesoccer.yaml
Normal file
@@ -0,0 +1,101 @@
|
||||
esphome:
|
||||
name: tablesoccer
|
||||
platform: ESP8266
|
||||
board: nodemcuv2
|
||||
on_boot:
|
||||
priority: 600
|
||||
then:
|
||||
- output.turn_on: power_control
|
||||
- output.turn_on: st_led
|
||||
|
||||
mqtt:
|
||||
broker: !secret mqtt_broker_1
|
||||
username: !secret mqtt_broker_1_username
|
||||
password: !secret mqtt_broker_1_password
|
||||
on_connect:
|
||||
then:
|
||||
- output.turn_off: st_led
|
||||
on_disconnect:
|
||||
then:
|
||||
- output.turn_on: st_led
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: true
|
||||
logger:
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: external_components/
|
||||
components: [ tablesoccer_btn_helper ]
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret streamsign_password
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
pin: 14
|
||||
id: power_control
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: D0
|
||||
mode: output
|
||||
inverted: true
|
||||
id: st_led
|
||||
|
||||
tablesoccer_btn_helper:
|
||||
keep_alive: power_control
|
||||
reset_pin:
|
||||
number: 12
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
undo_pin:
|
||||
number: 13
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
id: btn_helper
|
||||
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
id: sensor_goal_blue
|
||||
pin:
|
||||
number: D1
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
filters:
|
||||
- delayed_on: 10ms
|
||||
on_press:
|
||||
then:
|
||||
- lambda: |-
|
||||
id(btn_helper).send_score("0");
|
||||
- platform: gpio
|
||||
id: sensor_goal_red
|
||||
pin:
|
||||
number: D2
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
filters:
|
||||
- delayed_on: 10ms
|
||||
on_press:
|
||||
then:
|
||||
- lambda: |-
|
||||
id(btn_helper).send_score("1");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#pinMode(5, INPUT_PULLUP);
|
||||
#pinMode(4, INPUT_PULLUP);
|
||||
|
||||
#pinMode(14, OUTPUT);
|
||||
#pinMode(12, INPUT_PULLUP);
|
||||
#pinMode(13, INPUT_PULLUP);
|
Reference in New Issue
Block a user