Compare commits

...

53 Commits

Author SHA1 Message Date
8198d8f62e Forcing Raspi and NBS repos to be back like how they should be. MAYBE 2025-05-13 16:13:51 +02:00
3d06338782 Fixed pingpongfuss1.yaml for further use 2024-11-22 20:36:28 +01:00
3b2414c3c4 Combined patches to 2024.11.0 2024-11-18 14:08:12 +01:00
db2cb2a556 Updated Light switch for esphome 2024.8.0 2024-08-22 18:37:04 +02:00
051c6f32ce Fixed a tiny bug when attempting to use device stairs since device_name_stairs was spelled wrong 2024-06-11 21:10:36 +02:00
47335abc89 Fixed typo on Raspberri 2024-06-11 19:24:26 +02:00
6fd235c2f9 Removed Project data because it might cause trouble 2024-06-11 11:29:49 +02:00
8c9c9f403c Dammit Forgot something 2024-06-11 10:49:29 +02:00
a9bc9d15cd I seem to have forgotten what I changed in the Pi 2024-06-11 10:48:02 +02:00
a28af51304 Some tiny changes in ledstrip. Now we are perfect! 2024-06-11 10:45:05 +02:00
e1a7d166e5 Ledstrip simple got the biggest update in centuries. Now with 100% less confusion. 2024-06-11 10:38:05 +02:00
1d0c3fd07b ledstrip_simple now has to have an entity_prefix set for reasons 2024-06-10 12:23:12 +02:00
8b11af147f Changed chlorPump on_time_multiplier to 0.5 2024-06-05 08:42:10 +02:00
f3d7f4183a Added a on_time_multiplier to be 0.8
This should help to a better scaling of certain numbers
2024-06-04 10:14:56 +02:00
30db0802a0 Fixed:
Slight bug fixes for ezo_orp_i2c component

Added: A new time_modifier for chlorine_pump,
Used to set maximums and stuff for chlorine pump pump
2024-06-04 08:42:37 +02:00
f0be663618 Added Captive Portal to a3pool and sonoffstecker1 2024-05-29 10:49:54 +02:00
82d3a7458a Added fast connect to the nbs_mainlight 2024-05-29 10:40:26 +02:00
53831f23f3 Added Light_switch.yaml 2024-05-29 10:14:22 +02:00
cfd7c86235 Added ESP_LOGD chlorine-pump 2024-05-27 17:06:53 +02:00
d21229fa73 Fixed a bug for chlorPump.yaml where the target would not properly load on start 2024-05-27 14:43:10 +02:00
1bd3b7eb90 Changed chlorine-pump to use the Ezo Orp module by AtlasScientific using I2C.
This module is by miles more accurate than any analog crap we could have come up with...

Not that the last idea was anything to scoff at I guess
2024-05-25 20:01:05 +02:00
4b7195a037 Added Door Status again 2024-05-24 16:02:00 +02:00
fd6d54697c Added all the changes necessary to run chlorine pump again 2024-05-24 16:00:34 +02:00
cb6a250d29 Just commited my GONG.yaml 2024-05-24 13:11:45 +02:00
d38b9b38d7 Fixed bug that A3pool wouldn't turn off 2024-05-13 22:20:52 +02:00
1e35bff570 Removed the second check on a3pool again.yaml 2024-05-13 11:02:49 +02:00
12f9dea01b Changed one tiny things about a3pool 2024-05-13 10:51:20 +02:00
01548813dc Updated a3pool to use the new datetime template picker in HA 2024-05-13 10:44:42 +02:00
3d7e80154a All the code changes to the chlorine pump... More will follow 2024-05-12 23:02:52 +02:00
a96531dda9 Added Averager 2024-05-11 10:54:14 +02:00
d6c8d35385 Stream sign updated. Nothing more 2024-05-10 15:38:55 +02:00
056990076e Updated chlorine Pump 2024-05-10 12:37:19 +02:00
9fac30e714 Added:
OnBoard LED to turn on while ESP is not connected to MQTT for pingpongfuss and tablesoccer

Changed:
  Garage to no use NO power saving while on wifi
2024-05-04 15:00:31 +02:00
cdcd297d68 Added stream sign. Haha 2024-05-02 12:25:39 +02:00
42f9224c05 Removed the api password. Scream all you like, my gods can't hear you 2024-05-02 12:20:25 +02:00
264bcb66f6 Added TableSoccer as esphome... It's finally no longer an ill-configure Homie 2024-05-02 12:19:08 +02:00
8ed0efd32c Moved SoftwareSeriel to the .py files 2024-05-02 12:17:43 +02:00
5e89b866b4 That is the new nbs mainlight config which replaced the old tasmota config of my light switch 2024-05-01 12:35:11 +02:00
bdb3ec110f Updated secrets.yaml.template 2024-05-01 12:34:45 +02:00
a837b8494a Updated pingfuss.yaml and tabletenniscounter.yaml 2024-05-01 12:33:02 +02:00
69851b34ff UPDATED PUMP AND README.md 2024-04-30 11:04:02 +02:00
6b99942647 Pushed Sonoff Stecker 1 into things again 2024-04-30 10:45:14 +02:00
26341e075a Now properly sends light state back to HA 2024-04-29 13:49:12 +02:00
ef7144ebc1 Just added .gitignore stuff because I forgot to include it 2024-04-27 09:12:59 +02:00
9719833b47 Added garage1.yaml, including the implementation for the hoermann external components 2024-04-26 21:02:48 +02:00
ac345026e7 Added clorPump, it is not complet. Not bei a long shot but it's a start 2024-04-24 11:50:28 +02:00
fcb8838fb6 Added gong. Now with 100% more security! 2024-04-24 11:20:43 +02:00
713faae604 That is why a company might get a negative impression of you... Not fully updating stuff in the README before
merging the pull request
2022-07-12 14:23:02 +02:00
089a0fe069 Merge pull request 'Added self-managed time control to a3pool.yaml' (#1) from test_time into main
Reviewed-on: #1
2022-07-12 12:14:39 +00:00
00ccfbfcc0 Added a little bit to the README.md to mention the newest changes to a3pool.yaml 2022-07-12 14:07:59 +02:00
48e1bbaeef This is the finished product for a3pool. Reminds me: Documentation is not good yet! 2022-07-12 13:59:45 +02:00
ebb9d7f65b Fixed slight bug with the turn_off time at a3pool 2022-06-28 11:40:29 +02:00
774016bbfb Adds a timezone and automatic pump control. Further control coming soon 2022-06-28 11:37:19 +02:00
36 changed files with 3001 additions and 78 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@
**/src/ **/src/
**/platformio.ini **/platformio.ini
secrets.yaml secrets.yaml
external_components/*/__pycache__/

View File

@ -17,11 +17,12 @@ This is a standard config for the a3pool. Nothing special by far with 2 temp sen
The temperature sensors have the `pool_temperature` and `solar_temperature` and will show report the pool temperature and solar temperature respectively every 10 seconds. The temperature sensors have the `pool_temperature` and `solar_temperature` and will show report the pool temperature and solar temperature respectively every 10 seconds.
There is also one switch that is designed to turn the pump on and off. This works by giving an 500ms long impulse that should turn the pump on/off and sensing on a GPIO ping whenever the pump is currently on/off. This switch carries the name of `pump`. I know HOW CREATIVE... There is also one switch that is designed to turn the pump on and off. This works by giving an 500ms long impulse that should turn the pump on/off and sensing on a GPIO ping whenever the pump is currently on/off. This switch carries the name of `pump`. I know HOW CREATIVE...
It also has the possibility of having it's own clock.
Simply adjust the time using the `start_h`, `start_m` as well as `end_h`, `end_m` and enable the `control` switch to ensure the automatic control in on. The button `sync_to_clock`is used to sync the system back to the clock without having to know/care about on/off times, simply press the button and it should switch to on/off if it should be on/off respectively.
**HOWEVER: If the pump is set to off or to the physical clock with physical switch in the garage this will not work!** It will still report it's state correclty however. **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 Count!
## Table Tennis Footswitch ## Table Tennis Footswitch
The files go by the name of (pingpongfuss.yaml)[pingpongfuss1.yaml]. The files go by the name of (pingpongfuss.yaml)[pingpongfuss1.yaml].
@ -76,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: 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. 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).

View File

@ -1,13 +1,14 @@
esphome: esphome:
name: a3pool name: a3pool
esp8266:
board: nodemcuv2 board: nodemcuv2
platform: ESP8266
ota: ota:
- platform: esphome
password: !secret a3poolpasswd password: !secret a3poolpasswd
api: api:
password: !secret a3poolpasswd
encryption: encryption:
key: !secret a3poolkey key: !secret a3poolkey
@ -17,10 +18,12 @@ wifi:
ssid: !secret wifi_ssid ssid: !secret wifi_ssid
password: !secret wifi_password password: !secret wifi_password
fast_connect: true 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: binary_sensor:
- platform: gpio - platform: gpio
@ -32,6 +35,55 @@ binary_sensor:
pullup: true pullup: true
id: pump_state id: pump_state
datetime:
- platform: template
name: Pool Pump Start Time
id: pool_start
type: time
optimistic: true
restore_value: true
initial_value: "08:00:00"
- platform: template
name: Pool Pump End Time
id: pool_end
type: time
optimistic: true
restore_value: true
initial_value: "20:00:00"
button:
- platform: template
name: "Sync To Clock"
on_press:
then:
- lambda: |-
int startSec = (int) (id(pool_start).hour * 3600) + (id(pool_start).minute * 60) + id(pool_start).second;
int endSec = (int) (id(pool_end).hour * 3600) + (id(pool_end).minute * 60) + id(pool_end).second;
int nowSec = (id(a3poolTime).now().hour * 3600) + (id(a3poolTime).now().minute * 60) + id(a3poolTime).now().second;
if(!id(control).state){
// Make sure wierd error or whatever
return;
}
if(startSec < endSec){
if(startSec < nowSec && nowSec < endSec){
id(pump).turn_on();
}
else{
id(pump).turn_off();
}
}
else if(startSec > endSec){
if(endSec < nowSec && nowSec < startSec){
id(pump).turn_off();
}
else{
id(pump).turn_on();
}
}
switch: switch:
- platform: gpio - platform: gpio
pin: 5 pin: 5
@ -41,12 +93,13 @@ switch:
- switch.turn_off: relay - switch.turn_off: relay
- platform: template - platform: template
name: "Automate Control" name: "Automate Control"
id: control
optimistic: true optimistic: true
restore_state: true restore_mode: RESTORE_DEFAULT_OFF
- platform: template - platform: template
optimistic: false optimistic: false
name: pump name: pump
id: pump
lambda: |- lambda: |-
if (id(pump_state).state) { if (id(pump_state).state) {
return true; return true;
@ -70,14 +123,39 @@ switch:
- delay: 500ms - delay: 500ms
- switch.turn_off: relay - switch.turn_off: relay
dallas: time:
- pin: D4 timezone: "Europe/Vienna"
update_interval: 10s id: a3poolTime
platform: sntp
on_time:
- seconds: 0
months: 4-10
then:
- if:
condition:
lambda: 'return id(a3poolTime).now().hour == id(pool_start).hour && id(a3poolTime).now().minute == id(pool_start).minute/* && id(a3poolTime).now().second == id(pool_start).second*/ && id(control).state;'
then:
- switch.turn_on: pump
- seconds: 0
months: 4-10
then:
- if:
condition:
lambda: 'return id(a3poolTime).now().hour == id(pool_end).hour && id(a3poolTime).now().minute == id(pool_end).minute/* && id(a3poolTime).now().second == id(pool_end).second*/ && id(control).state;'
then:
- switch.turn_off: pump
one_wire:
- platform: gpio
pin: D4
sensor: sensor:
- platform: dallas - platform: dallas_temp
address: 0xd1000007494b3628 address: 0xd1000007494b3628
name: "Pool Temperature" name: "Pool Temperature"
- platform: dallas update_interval: 10s
- platform: dallas_temp
address: 0x440000073de2e728 address: 0x440000073de2e728
name: "Solar Temperature" name: "Solar Temperature"
update_interval: 10s

205
chlorPump.yaml Normal file
View 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

View File

@ -0,0 +1 @@
CODEOWNERS = ["@nbsgames"]

View 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;
}
}
}

View 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); });
}
};
}
}

View 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)

View 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)

View 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_;
};
}
}

View File

@ -0,0 +1 @@
CODEOWNERS = ["@nbsgames"]

View 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);
}
}
}

View 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_;
};
}
}

View 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)

View 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")

View 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

View 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);
}

View 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;
}

View 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

View 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

View 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")

View 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))

View File

@ -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);
}
}
};
}
}

View 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")

View File

@ -3,6 +3,9 @@
//#include "multi_effect_handler.h" //#include "multi_effect_handler.h"
#define SEGMENT_LENGTH 5 #define SEGMENT_LENGTH 5
namespace esphome {
namespace ws2812_table_tennis {
class NbsEffect { class NbsEffect {
private: private:
int goalRed = 0, goalGreen = 0, goalBlue = 0; int goalRed = 0, goalGreen = 0, goalBlue = 0;
@ -89,10 +92,10 @@ class NbsEffect {
}; };
NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBangWs2812xMethod> nbsStrip(143, D5); NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBangWs2812xMethod> nbsStrip(143, D5);
class CustomNumberDisplayComponent : public Component, public CustomMQTTDevice { class CustomNumberDisplayComponent : public Component, public mqtt::CustomMQTTDevice {
protected:
const int middleSegment = SEGMENT_LENGTH * 14; const int middleSegment = SEGMENT_LENGTH * 14;
const int transitionLength = 500; // milliseconds const int transitionLength = 500; // milliseconds
const int middleLength = 2; const int middleLength = 2;
const double brightness = 0.17; const double brightness = 0.17;
@ -120,9 +123,22 @@ bool numbers[14][7] = {
{1, 0, 0, 1, 1, 1, 1} // b (13) {1, 0, 0, 1, 1, 1, 1} // b (13)
}; };
template_::TemplateNumber *redNum;
template_::TemplateNumber *blueNum;
public: public:
void set_red_number(template_::TemplateNumber *redNum){
this->redNum = redNum;
}
void set_blue_number(template_::TemplateNumber *blueNum){
this->blueNum = blueNum;
}
void setup() override { void setup() override {
// Segment mapping // Segment mapping
int segPixelCounter = 0; int segPixelCounter = 0;
for(int i = 0; i < 4; ++i){ for(int i = 0; i < 4; ++i){
@ -146,7 +162,7 @@ bool numbers[14][7] = {
// also supports JSON messages // also supports JSON messages
subscribe_json("tabletenniscounter/display/number/command", &CustomNumberDisplayComponent::on_json_message, 2); 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) { void on_json_message(const std::string &topic, JsonObject root) {
@ -183,8 +199,8 @@ bool numbers[14][7] = {
int player1 = root["player1"]; int player1 = root["player1"];
int player2 = root["player2"]; int player2 = root["player2"];
id(red_num).set(player1); redNum->publish_state(player1);
id(blue_num).set(player2); blueNum->publish_state(player2);
bool player1Start = root["player1Start"]; bool player1Start = root["player1Start"];
// do something with Json Object // do something with Json Object
if(player1 > 99) { player1 = 99; } if(player1 > 99) { player1 = 99; }
@ -353,3 +369,5 @@ bool numbers[14][7] = {
} }
}; };
}
}

46
garage1.yaml Normal file
View 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
View 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"

View File

@ -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: esphome:
name: teststrip name: ${device_name_${collection_name}}
platform: ESP8266
esp8266:
board: nodemcuv2 board: nodemcuv2
# Enable logging # Enable logging
logger: logger:
ota: ota:
- platform: esphome
password: !secret ledsimplepassword password: !secret ledsimplepassword
api: api:
password: !secret ledsimplepassword
encryption: encryption:
key: !secret ledsimplekey key: !secret ledsimplekey
@ -18,21 +35,14 @@ wifi:
ssid: !secret wifi_ssid ssid: !secret wifi_ssid
password: !secret wifi_password password: !secret wifi_password
fast_connect: true 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: light:
- platform: neopixelbus - platform: neopixelbus
type: GRB type: GRB
variant: WS2811 variant: WS2811
pin: D5 pin: D5
num_leds: 56 num_leds: ${led_count_${collection_name}} # 56, 191
name: ledstrip name: ${entity_prefix_${collection_name}} Leds
effects: effects:
- addressable_rainbow: - addressable_rainbow:
- addressable_rainbow: - addressable_rainbow:

57
light_switch.yaml Normal file
View 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

View File

@ -2,6 +2,10 @@ esphome:
name: pingfuss1 name: pingfuss1
platform: ESP8266 platform: ESP8266
board: nodemcuv2 board: nodemcuv2
on_boot:
priority: 300
then:
- output.turn_on: st_led
# Enable logging # Enable logging
logger: logger:
@ -10,10 +14,12 @@ logger:
# DISABLED - We currently do not need it for operation. Maybe someday # DISABLED - We currently do not need it for operation. Maybe someday
# api: # api:
# password: !secret ttfpassword # encryption:
# key: !secret ttf_key
ota: ota:
password: !secret ttfpassword - platform: esphome
password: !secret ttf_password
wifi: wifi:
ssid: !secret wifi_ssid ssid: !secret wifi_ssid
@ -26,6 +32,20 @@ mqtt:
broker: !secret mqtt_broker_1 broker: !secret mqtt_broker_1
username: !secret mqtt_broker_1_username username: !secret mqtt_broker_1_username
password: !secret mqtt_broker_1_password 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 # Example configuration entry
binary_sensor: binary_sensor:

View File

@ -15,7 +15,7 @@ a3poolpasswd: # A3Pool OTA and native API password
a3poolkey: # Native API encryption KEY a3poolkey: # Native API encryption KEY
# Table Tennis Footswitches (No ttf encryption but may be added) # Table Tennis Footswitches (No ttf encryption but may be added)
ttfpassword: # TableTennisFootswitch OTA password ttf_password: # TableTennisFootswitch OTA password
# ttfkey: # Native API encryption KEY # ttfkey: # Native API encryption KEY
# Table Tennis ledstrip display # Table Tennis ledstrip display
@ -29,3 +29,27 @@ ss1key: # Native API encryption KEY
# Ledstrip Simple # Ledstrip Simple
ledsimplepassword: # Ledstrip Simple OTA and native API password ledsimplepassword: # Ledstrip Simple OTA and native API password
ledsimplekey: # Native API encryption KEY 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>

View File

@ -1,6 +1,7 @@
esphome: esphome:
name: sonoffstecker1 name: sonoffstecker1
platform: ESP8266
esp8266:
board: sonoff_basic board: sonoff_basic
# Enable logging # Enable logging
@ -8,19 +9,26 @@ logger:
# Enable Home Assistant API # Enable Home Assistant API
api: api:
password: !secret ss1password # password: !secret ss1password
encryption: encryption:
key: !secret ss1password key: !secret ss1key
ota: ota:
- platform: esphome
password: !secret ss1password password: !secret ss1password
wifi: wifi:
ssid: !secret wifi_ssid ssid: !secret wifi_ssid
password: !secret wifi_password password: !secret wifi_password
fast_connect: true fast_connect: true
ap:
ssid: sonoff_stecker_1_rescue
password: !secret rescue_ap_password
captive_portal:
switch: switch:
- platform: gpio - platform: gpio

27
streamsign.yaml Normal file
View 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"

View File

@ -2,24 +2,21 @@ esphome:
name: tabletenniscounter name: tabletenniscounter
platform: ESP8266 platform: ESP8266
board: nodemcuv2 board: nodemcuv2
includes:
- cpp-files/tableTennisDisplay.h
libraries:
- "makuna/NeoPixelBus"
custom_component: external_components:
- lambda: |- - source:
auto customNumberDisplay = new CustomNumberDisplayComponent(); type: local
return {customNumberDisplay}; path: external_components/
components: [ ws2812_table_tennis ]
# Enable logging # Enable logging
logger: logger:
ota: ota:
- platform: esphome
password: !secret ttdpassword password: !secret ttdpassword
api: api:
password: !secret ttdpassword
encryption: encryption:
key: !secret ttdkey key: !secret ttdkey
@ -49,3 +46,8 @@ mqtt:
client_id: "tableTennisCounter" client_id: "tableTennisCounter"
username: !secret mqtt_broker_1_username username: !secret mqtt_broker_1_username
password: !secret mqtt_broker_1_password 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
View 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);