Compare commits
48 Commits
089a0fe069
...
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
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,3 +9,4 @@
|
||||
**/src/
|
||||
**/platformio.ini
|
||||
secrets.yaml
|
||||
external_components/*/__pycache__/
|
16
README.md
16
README.md
@ -23,9 +23,6 @@ Simply adjust the time using the `start_h`, `start_m` as well as `end_h`, `end_m
|
||||
|
||||
**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.
|
||||
|
||||
There is an other switch that does not have any functionality yet but it's `automate_control` if anyone wants to know.
|
||||
|
||||
|
||||
# Table Tennis Count!
|
||||
## Table Tennis Footswitch
|
||||
The files go by the name of (pingpongfuss.yaml)[pingpongfuss1.yaml].
|
||||
@ -80,4 +77,15 @@ There are a few light effects configured so please play around with them for a l
|
||||
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.
|
||||
|
||||
More infos are coming soon.
|
||||
# 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).
|
93
a3pool.yaml
93
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,47 +35,21 @@ binary_sensor:
|
||||
pullup: true
|
||||
id: pump_state
|
||||
|
||||
number:
|
||||
datetime:
|
||||
- platform: template
|
||||
name: "Start Hour"
|
||||
id: start_h
|
||||
name: Pool Pump Start Time
|
||||
id: pool_start
|
||||
type: time
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
max_value: 23
|
||||
restore_value: true
|
||||
initial_value: 8
|
||||
mode: box
|
||||
step: 1
|
||||
initial_value: "08:00:00"
|
||||
- platform: template
|
||||
name: "Start Minute"
|
||||
id: start_m
|
||||
name: Pool Pump End Time
|
||||
id: pool_end
|
||||
type: time
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
max_value: 59
|
||||
initial_value: 0
|
||||
restore_value: true
|
||||
mode: box
|
||||
step: 1
|
||||
- platform: template
|
||||
name: "End Hour"
|
||||
id: end_h
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
max_value: 23
|
||||
restore_value: true
|
||||
initial_value: 20
|
||||
mode: box
|
||||
step: 1
|
||||
- platform: template
|
||||
name: "End Minute"
|
||||
id: end_m
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
max_value: 59
|
||||
restore_value: true
|
||||
initial_value: 0
|
||||
mode: box
|
||||
step: 1
|
||||
initial_value: "20:00:00"
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
@ -80,25 +57,25 @@ button:
|
||||
on_press:
|
||||
then:
|
||||
- lambda: |-
|
||||
int startMins = (int) (id(start_h).state * 60) + id(start_m).state;
|
||||
int endMins = (int) (id(end_h).state * 60) + id(end_m).state;
|
||||
int nowMins = (id(a3poolTime).now().hour * 60) + id(a3poolTime).now().minute;
|
||||
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(startMins < endMins){
|
||||
if(startMins < nowMins && nowMins < endMins){
|
||||
if(startSec < endSec){
|
||||
if(startSec < nowSec && nowSec < endSec){
|
||||
id(pump).turn_on();
|
||||
}
|
||||
else{
|
||||
id(pump).turn_off();
|
||||
}
|
||||
}
|
||||
else if(startMins > endMins){
|
||||
if(endMins < nowMins && nowMins < startMins){
|
||||
else if(startSec > endSec){
|
||||
if(endSec < nowSec && nowSec < startSec){
|
||||
id(pump).turn_off();
|
||||
}
|
||||
else{
|
||||
@ -118,8 +95,7 @@ switch:
|
||||
name: "Automate Control"
|
||||
id: control
|
||||
optimistic: true
|
||||
restore_state: true
|
||||
|
||||
restore_mode: RESTORE_DEFAULT_OFF
|
||||
- platform: template
|
||||
optimistic: false
|
||||
name: pump
|
||||
@ -157,7 +133,7 @@ time:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(a3poolTime).now().hour == (int) id(start_h).state && id(a3poolTime).now().minute == (int) id(start_m).state && id(control).state;'
|
||||
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
|
||||
@ -165,18 +141,21 @@ time:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(a3poolTime).now().hour == (int) id(end_h).state && id(a3poolTime).now().minute == (int) id(end_m).state && id(control).state;'
|
||||
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
|
||||
|
||||
dallas:
|
||||
- pin: D4
|
||||
update_interval: 10s
|
||||
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")
|
@ -3,6 +3,9 @@
|
||||
//#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;
|
||||
@ -89,40 +92,53 @@ class NbsEffect {
|
||||
};
|
||||
|
||||
NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBangWs2812xMethod> nbsStrip(143, D5);
|
||||
class CustomNumberDisplayComponent : public Component, public CustomMQTTDevice {
|
||||
const int middleSegment = SEGMENT_LENGTH * 14;
|
||||
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;
|
||||
|
||||
const int transitionLength = 500; // milliseconds
|
||||
const int middleLength = 2;
|
||||
const double brightness = 0.17;
|
||||
NbsEffect *colorAnimation[4][7];
|
||||
NbsEffect *middleSegmentAnim;
|
||||
|
||||
NbsEffect *colorAnimation[4][7];
|
||||
NbsEffect *middleSegmentAnim;
|
||||
unsigned long lastUpdate = 0;
|
||||
bool shouldShowNBS = true;
|
||||
|
||||
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)
|
||||
};
|
||||
|
||||
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){
|
||||
@ -146,7 +162,7 @@ bool numbers[14][7] = {
|
||||
// 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) {
|
||||
@ -183,8 +199,8 @@ bool numbers[14][7] = {
|
||||
int player1 = root["player1"];
|
||||
int player2 = root["player2"];
|
||||
|
||||
id(red_num).set(player1);
|
||||
id(blue_num).set(player2);
|
||||
redNum->publish_state(player1);
|
||||
blueNum->publish_state(player2);
|
||||
bool player1Start = root["player1Start"];
|
||||
// do something with Json Object
|
||||
if(player1 > 99) { player1 = 99; }
|
||||
@ -353,3 +369,5 @@ bool numbers[14][7] = {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
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"
|
@ -1,16 +1,33 @@
|
||||
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: teststrip
|
||||
platform: ESP8266
|
||||
name: ${device_name_${collection_name}}
|
||||
|
||||
esp8266:
|
||||
board: nodemcuv2
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
ota:
|
||||
password: !secret ledsimplepassword
|
||||
- platform: esphome
|
||||
password: !secret ledsimplepassword
|
||||
|
||||
api:
|
||||
password: !secret ledsimplepassword
|
||||
encryption:
|
||||
key: !secret ledsimplekey
|
||||
|
||||
@ -18,21 +35,14 @@ wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: true
|
||||
# use_address: teststrip2.local
|
||||
|
||||
mqtt:
|
||||
broker: !secret mqtt_broker_1
|
||||
client_id: "teststrip"
|
||||
username: !secret mqtt_broker_1_username
|
||||
password: !secret mqtt_broker_1_password
|
||||
|
||||
light:
|
||||
- platform: neopixelbus
|
||||
type: GRB
|
||||
variant: WS2811
|
||||
pin: D5
|
||||
num_leds: 56
|
||||
name: ledstrip
|
||||
num_leds: ${led_count_${collection_name}} # 56, 191
|
||||
name: ${entity_prefix_${collection_name}} Leds
|
||||
effects:
|
||||
- addressable_rainbow:
|
||||
- addressable_rainbow:
|
||||
|
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
|
@ -2,6 +2,10 @@ esphome:
|
||||
name: pingfuss1
|
||||
platform: ESP8266
|
||||
board: nodemcuv2
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- output.turn_on: st_led
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
@ -10,10 +14,12 @@ logger:
|
||||
# DISABLED - We currently do not need it for operation. Maybe someday
|
||||
|
||||
# api:
|
||||
# password: !secret ttfpassword
|
||||
# encryption:
|
||||
# key: !secret ttf_key
|
||||
|
||||
ota:
|
||||
password: !secret ttfpassword
|
||||
- platform: esphome
|
||||
password: !secret ttf_password
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
@ -26,6 +32,20 @@ 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:
|
||||
|
@ -15,7 +15,7 @@ a3poolpasswd: # A3Pool OTA and native API password
|
||||
a3poolkey: # Native API encryption KEY
|
||||
|
||||
# Table Tennis Footswitches (No ttf encryption but may be added)
|
||||
ttfpassword: # TableTennisFootswitch OTA password
|
||||
ttf_password: # TableTennisFootswitch OTA password
|
||||
# ttfkey: # Native API encryption KEY
|
||||
|
||||
# Table Tennis ledstrip display
|
||||
@ -29,3 +29,27 @@ 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>
|
||||
|
@ -1,6 +1,7 @@
|
||||
esphome:
|
||||
name: sonoffstecker1
|
||||
platform: ESP8266
|
||||
|
||||
esp8266:
|
||||
board: sonoff_basic
|
||||
|
||||
# Enable logging
|
||||
@ -8,19 +9,26 @@ logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
password: !secret ss1password
|
||||
# password: !secret ss1password
|
||||
encryption:
|
||||
key: !secret ss1password
|
||||
key: !secret ss1key
|
||||
|
||||
|
||||
|
||||
ota:
|
||||
password: !secret ss1password
|
||||
- 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
|
||||
|
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"
|
@ -2,24 +2,21 @@ esphome:
|
||||
name: tabletenniscounter
|
||||
platform: ESP8266
|
||||
board: nodemcuv2
|
||||
includes:
|
||||
- cpp-files/tableTennisDisplay.h
|
||||
libraries:
|
||||
- "makuna/NeoPixelBus"
|
||||
|
||||
custom_component:
|
||||
- lambda: |-
|
||||
auto customNumberDisplay = new CustomNumberDisplayComponent();
|
||||
return {customNumberDisplay};
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: external_components/
|
||||
components: [ ws2812_table_tennis ]
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
ota:
|
||||
password: !secret ttdpassword
|
||||
- platform: esphome
|
||||
password: !secret ttdpassword
|
||||
|
||||
api:
|
||||
password: !secret ttdpassword
|
||||
encryption:
|
||||
key: !secret ttdkey
|
||||
|
||||
@ -49,3 +46,8 @@ mqtt:
|
||||
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