Compare commits

...

11 Commits

Author SHA1 Message Date
1f7c437c3e This adds:
- A general folder for documantation configs
- A documantation for general esphome devices with functionallity too small for it's own file.
- Removed the powersave mode NONE on garage doors.
2025-10-08 10:13:10 +02:00
d4edf31a8b I am literally at a point, where I don't know what else to improve here. 2025-10-08 09:09:07 +02:00
d0c2b3993b Made garage1.yaml restart polling after a failed ota update 2025-10-07 14:06:42 +02:00
273cd9e434 Added hoermann_door.stop_polling and .start_polling as automations should that need arise somehow! 2025-10-07 13:15:04 +02:00
3b88f1e988 Made the HCIEmulator Log Level settable inside the .yaml file 2025-10-07 02:01:56 +02:00
9d3930af59 Updated Hörmann_door to completely use namespaces. Makes code a bit cleaner 2025-10-07 00:50:49 +02:00
3ef68a9539 Garage1.yaml now fully uses esp-idf as it's framework. No need for why but here we are. 2025-09-30 14:22:27 +02:00
31861e7785 Final cleanup for hoerman esp-idf ready! Compiles agian, works again. esp-idf might need some trickyness to get running. 2025-09-30 00:40:21 +02:00
b8581dcf31 Fixed bug in hoermann.h and prints log message into esp_logd now. 2025-09-29 19:18:23 +02:00
2809b1a167 Slight bug fix 2025-09-27 12:59:04 +02:00
d3f0520123 HoermannDoor: Now uses GPIOInternalPin and UARTComponent to communicate. 2025-09-26 21:03:45 +02:00
12 changed files with 491 additions and 199 deletions

50
5gModemPower.yaml Normal file
View File

@@ -0,0 +1,50 @@
esphome:
name: modempower
esp8266:
board: sonoff_basic
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
fast_connect: true
ap:
ssid: modempower_rescue
password: !secret rescue_ap_password
captive_portal:
api:
encryption:
key: !secret k5gModem
ota:
- platform: esphome
password: !secret p5gModem
logger:
binary_sensor:
- platform: gpio
pin:
number: GPIO0
mode:
input: true
pullup: true
inverted: true
name: "5G Modem Power Button"
on_press:
- switch.toggle: relay
switch:
- platform: gpio
name: "5G Modem Power Relay"
pin: GPIO12
restore_mode: ALWAYS_ON
id: relay
status_led:
pin:
number: GPIO13
inverted: yes

49
docs/GENERAL_DEVICE.md Normal file
View File

@@ -0,0 +1,49 @@
# What is this:
This is the general documentation for esphome configs deemed too short to deserve their own documantation. Stuff like the 5gModem, the sonoffStecker or the nbs-light-room are just too short for me to believe it necessary.
So, let's get through the basics of any esphome config developed by me.
# General configs
Before I start, I should mention what I consider to be general configurations: modules like esphome, esp8266 or esp32, as well as wifi, logger, ota are all part of that. Nearly all of them require all of them (with exception to esp8266 and esp32 as they are mutually exclusive).
## esphome
The `esphome` defines general points, and is required by any esphome config to function. Weirdly, in the last few years this has become a mere holder for `name` and maybe `version`.
For `name` I recommand something that actually give the device a name that is clear what it does.
For `version` when used, I recommand using semantic versioning that makes sense. But if you actually use it or not is up to you.
## esp8266/esp32
The `esp8266` or `esp32` has a special point about only being their to define the actually board being used.
Most common boards for esp8266 that we use is the `nodemcuv2`. While for the esp32 it is the `wemos_d1_mini32` also sometimes referenced as `esp32dev`.
A special note about `esp32` is that they have multiple frameworks that are supported. This is defined as `esp32.framework.type`:
- arduino
- esp-idf
## wifi
The wifi part is not confusing at all. Normally we use `ssid`, `password` and `fast_connect`. You will see that there is also a captive_portal defined at times should the esp somehow fail to connect to the wifi at all. This captive portal and it's own wifi that it will open in such an event can be used to rescue the device without having to manually flash it again.
SSID is the wifis name, while password is, well, its password. `fast_connect` just means that the esp will NOT attempt to verify that the network exists by scanning for it. This is important if you need to connect to a hidden wifi.
## logger
We actually keep this empty. Just having it around is enough.
## api
The api just defines the encryption key for the esphome native api used by esphome and home assistant.
## ota
Ahh, the usual over the air update component.
The platform used is the normal esphome platform used by the esphome cli as well as the esphome builder in HomeAssistant from there we define a password. Just to be sure.
## status_led
The status led is specific to each esp device. but I use it usually to detect if a device is connected to the wifi or not.
# More device specific configuration
This is the section for more device specific configuration options that might be important
## 5gModemPower
This is a normal sonoff power relay. It has a pin defined for the relay. A pin defined for the actual button should you need to press it directly on the device and that's about it.
The relay that controls the power is controlled by an gpio pin (Number 12) and starts out as always on.
There is a binary sensor included for the button on the actual device. This is an inverted pullup pin on gpio 0, pressing it toggles the relay.

View File

@@ -0,0 +1,73 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins, automation
from esphome.components import uart
from esphome.const import (
CONF_ID
)
CODEOWNERS = ["@nbsgames"]
DEPENDENCIES=["uart"]
hoermann_door_ns = cg.esphome_ns.namespace('hoermann_door')
HoermannDoor = hoermann_door_ns.class_('HoermannMainComponent', cg.Component)
StopPolling = hoermann_door_ns.class_('StopPollingAction', automation.Action)
StartPolling = hoermann_door_ns.class_('StartPollingAction', automation.Action)
CONF_UART_ENTRY = "uart_connection"
CONF_TX_PIN = "tx_pin"
CONF_LOG_LEVEL = "log_level"
#define LL_OFF 0
#define LL_ERROR 1
#define LL_WARN 2
#define LL_INFO 3
#define LL_DEBUG 4
CONF_LOG_LEVELS = {
"NONE": 0,
"ERROR": 1,
"WARN": 2,
"INFO": 3,
"DEBUG": 4,
}
def validate_config(config):
return config
CONFIG_SCHEMA = cv.All(
cv.COMPONENT_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(HoermannDoor),
cv.Required(CONF_UART_ENTRY): cv.use_id(uart.UARTComponent),
cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_LOG_LEVEL, default="INFO"): cv.enum(CONF_LOG_LEVELS, upper=True),
}),
validate_config
)
ACTION_SCHEMA = automation.maybe_simple_id({
cv.GenerateID(): cv.use_id(HoermannDoor)
})
@automation.register_action("hoermann_door.stop_polling", StopPolling, ACTION_SCHEMA)
async def stop_polling(config, action_id, template_arg, args):
parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, parent)
@automation.register_action("hoermann_door.start_polling", StartPolling, ACTION_SCHEMA)
async def start_polling(config, action_id, template_arg, args):
parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, parent)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
code = await cg.get_variable(config[CONF_UART_ENTRY])
cg.add(var.set_seriel_connection(code))
code2 = await cg.gpio_pin_expression(config[CONF_TX_PIN])
cg.add(var.set_tx_on_pin(code2))
log_level = config[CONF_LOG_LEVEL]
cg.add(var.set_log_level(log_level))

View File

@@ -3,18 +3,23 @@ import esphome.config_validation as cv
from esphome import automation
from esphome.components import cover
from esphome.const import CONF_ID
from . import HoermannDoor
hoermann_cover_ns = cg.esphome_ns.namespace('hoermann_door')
HoermannDoor = hoermann_cover_ns.class_('HoermanDoor', cover.Cover, cg.Component)
HoermannCover = hoermann_cover_ns.class_('HoermannDoor', cover.Cover, cg.Component)
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(HoermannDoor)
}).extend(cv.COMPONENT_SCHEMA)
CONF_HOERMANN_CONTROLLER = "hoermann_controller"
CONFIG_SCHEMA = cover.cover_schema(HoermannCover).extend({
cv.Required(CONF_HOERMANN_CONTROLLER): cv.use_id(HoermannDoor)
})
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield cover.register_cover(var, config)
await cg.register_component(var, config)
await cover.register_cover(var, config)
controller = await cg.get_variable(config[CONF_HOERMANN_CONTROLLER])
cg.add(var.set_emulator_component(controller))
cg.add_library("plerup/EspSoftwareSerial", "8.2.0")

View File

@@ -1,11 +1,9 @@
#pragma once
#include "door_singleton.h"
#ifdef USE_COVER
//#ifdef USE_COVER
#include "esphome.h"
#include "Arduino.h"
#include "hoermann.h"
#include "hciemulator.h"
//#include "cover.h"
@@ -13,14 +11,13 @@
#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
class HoermannDoor : public Component, public cover::Cover
{
private:
HoermannMainComponent* mainComponent;
public:
cover::CoverTraits get_traits() override {
@@ -48,52 +45,35 @@ public:
if(pos == 1.0){
HoermannSingleton::getInstance()->getEmulator()->openDoor();
mainComponent->getEmulator()->openDoor();
manual = false;
}
else if(pos == 0.0){
HoermannSingleton::getInstance()->getEmulator()->closeDoor();
mainComponent->getEmulator()->closeDoor();
manual = false;
}
else{
ESP_LOGD("Door controller", "Not yet supported");
ESP_LOGD(COMP_TAG, "Not yet supported");
}
}
if (call.get_stop()) {
HoermannSingleton::getInstance()->getEmulator()->stopDoor();
mainComponent->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();
//HoermannSingleton::getInstance()->initializeEmulator();
}
void set_emulator_component(HoermannMainComponent* component){
this->mainComponent = component;
}
void open(){
//emulator.openDoor();
}
@@ -109,7 +89,7 @@ void modBusPolling(void *parameter)
u_int8_t pos = 255;
void loop() override
{
u_int8_t position = HoermannSingleton::getInstance()->getEmulator()->getState().doorCurrentPosition;
u_int8_t position = mainComponent->getEmulator()->getState().doorCurrentPosition;
if(pos != position){
this->position = (float) position / 200.0;
this->publish_state();
@@ -124,4 +104,4 @@ void modBusPolling(void *parameter)
} // namespace hoermann
} // namespace esphome
#endif
//#endif

View File

@@ -1,79 +0,0 @@
#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

@@ -1,5 +1,9 @@
#include "hciemulator.h"
#include "Arduino.h"
namespace esphome {
namespace hoermann_door {
#define CHECKCHANGEDSET(Target, Value, Flag) \
if ((Target) != (Value)) \
{ \
@@ -8,9 +12,9 @@
}
int hciloglevel = LL_DEBUG;
#define SOFTSERIAL 1
//#define SOFTSERIAL 1
#ifdef SOFTSERIAL
//#ifdef SOFTSERIAL
#define Log(Level, Message) LogCore(Level, Message)
#define Log3(Level, Message, Buffer, Len) LogCore(Level, Message, Buffer, Len)
// LOGLEVEL
@@ -22,24 +26,29 @@ void LogCore(int Level, const char *msg, const unsigned char *data = NULL, size_
}
if (data != NULL && datalen > 0)
{
String newmsg(msg);
//std::string newmsg(msg);
char* newmsg = (char*)malloc(strlen(msg) + (datalen * 3) + 1);
strncpy(newmsg, msg, strlen(msg) + 1);
newmsg[strlen(msg)] = '\0';
char str[4];
for (size_t i = 0; i < datalen; i++)
{
snprintf(str, sizeof(str), "%02x ", data[i]);
newmsg += str;
str[3] = '\0';
strncat(newmsg, str, sizeof(str));
}
Serial.println(newmsg);
ESP_LOGD(TAG, newmsg);
free(newmsg);
}
else
{
Serial.println(msg);
ESP_LOGD(TAG, msg);
}
}
#else
#define Log(Level, Message)
#define Log3(Level, Message, Buffer, Len)
#endif
//#else
//#define Log(Level, Message)
//#define Log3(Level, Message, Buffer, Len)
//#endif
int HCIEmulator::getLogLevel()
{
@@ -50,6 +59,12 @@ void HCIEmulator::setLogLevel(int level)
hciloglevel = level;
}
//#ifdef USE_ESP_IDF
uint16_t HCIEmulator::word(uint8_t high, uint8_t low){
return (uint16_t) ((high << 8) | low);
}
//#endif
// modbus crc calculation borrowed from:
// https://github.com/yaacov/ArduinoModbusSlave
#define MODBUS_CRC_LENGTH 2
@@ -98,16 +113,16 @@ uint16_t calculateCRC(uint8_t *buffer, int length)
return crc;
}
HCIEmulator::HCIEmulator()
HCIEmulator::HCIEmulator(esphome::InternalGPIOPin* pin, esphome::uart::UARTComponent* uart)
{
m_state.valid = false;
m_statemachine = WAITING;
m_rxlen = m_txlen = 0;
m_recvTime = m_lastStateTime = 0;
m_skipFrame = false;
m_port = &Serial2;
m_port = uart;
m_pin = pin;
m_statusCallback = NULL;
setLogLevel(DEFAULTLOGLEVEL);
};
#define TX_ON 25
@@ -122,7 +137,13 @@ void HCIEmulator::poll()
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()));
int bytesToRead = std::min((int)(255 - m_rxlen), m_port->available());
if(m_port->read_array((uint8_t *)(m_rxbuffer + m_rxlen), bytesToRead)) {
m_rxlen += bytesToRead;
} else {
Log(LL_ERROR, "Error reading from UART");
//m_port->flush();
}
if (m_rxlen > 254)
{
Log(LL_ERROR, "RX Bufferoverflow, skip next Frame");
@@ -130,16 +151,16 @@ void HCIEmulator::poll()
m_rxlen = 0;
m_skipFrame = true;
}
m_recvTime = micros();
m_recvTime = esphome::micros();
}
// Serial.printf("Data % x\n", m_txbuffer);
// check frame, process frame
if (m_rxlen > 0 && (micros() - m_recvTime > T3_5))
if (m_rxlen > 0 && (esphome::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())
if (m_statemachine != WAITING && m_lastStateTime + 2000 < esphome::millis())
{
m_statemachine = WAITING;
}
@@ -158,22 +179,23 @@ void HCIEmulator::poll()
m_txbuffer[(m_txlen - MODBUS_CRC_LENGTH) + 1] = crc >> 8;
// send data
m_lastSendTime = micros() - m_recvTime;
m_lastSendTime = esphome::micros() - m_recvTime;
// Log(LL_DEBUG, ("ST:"+String(m_lastSendTime)).c_str());
digitalWrite(TX_ON, HIGH);
m_pin->digital_write(true);
// Log3(LL_DEBUG, "write data: ");
m_port->write(m_txbuffer, m_txlen);
m_port->write_array(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);
esphome::delayMicroseconds(m_txlen * 9 * 22); // 8 bits + par * Bittime 18 micros on 57600 bauds
m_pin->digital_write(false);
m_txlen = 0;
}
}
else
{
Serial.println("skipped frame");
ESP_LOGD(TAG, "skipped frame");
}
m_skipFrame = false;
@@ -184,6 +206,7 @@ void HCIEmulator::poll()
void HCIEmulator::processFrame()
{
m_txlen = 0; // clear send buffer
Log3(LL_DEBUG, "Incomming Data: ", m_rxbuffer, m_rxlen);
if (m_rxlen < 5)
{
@@ -274,10 +297,10 @@ void HCIEmulator::processDeviceStatusFrame()
m_txbuffer[7] = 0x02;
m_txbuffer[8] = 0x10;
m_statemachine = STARTOPENDOOR_RELEASE;
m_lastStateTime = millis();
m_lastStateTime = esphome::millis();
break;
case STARTOPENDOOR_RELEASE:
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < esphome::millis())
{
m_txbuffer[7] = 0x01;
m_txbuffer[8] = 0x10;
@@ -290,10 +313,10 @@ void HCIEmulator::processDeviceStatusFrame()
m_txbuffer[7] = 0x02;
m_txbuffer[8] = 0x20;
m_statemachine = STARTCLOSEDOOR_RELEASE;
m_lastStateTime = millis();
m_lastStateTime = esphome::millis();
break;
case STARTCLOSEDOOR_RELEASE:
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < esphome::millis())
{
m_txbuffer[7] = 0x01;
m_txbuffer[8] = 0x20;
@@ -306,10 +329,10 @@ void HCIEmulator::processDeviceStatusFrame()
m_txbuffer[7] = 0x02;
m_txbuffer[8] = 0x40;
m_statemachine = STARTSTOPDOOR_RELEASE;
m_lastStateTime = millis();
m_lastStateTime = esphome::millis();
break;
case STARTSTOPDOOR_RELEASE:
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < esphome::millis())
{
m_txbuffer[7] = 0x01;
m_txbuffer[8] = 0x40;
@@ -322,10 +345,10 @@ void HCIEmulator::processDeviceStatusFrame()
m_txbuffer[7] = 0x02;
m_txbuffer[9] = 0x40;
m_statemachine = STARTVENTPOSITION_RELEASE;
m_lastStateTime = millis();
m_lastStateTime = esphome::millis();
break;
case STARTVENTPOSITION_RELEASE:
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < esphome::millis())
{
m_txbuffer[7] = 0x01;
m_txbuffer[9] = 0x40;
@@ -338,11 +361,11 @@ void HCIEmulator::processDeviceStatusFrame()
m_txbuffer[7] = 0x02;
m_txbuffer[9] = 0x04;
m_statemachine = STARTOPENDOORHALF_RELEASE;
m_lastStateTime = millis();
m_lastStateTime = esphome::millis();
break;
case STARTOPENDOORHALF_RELEASE:
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < esphome::millis())
{
m_txbuffer[7] = 0x01;
m_txbuffer[9] = 0x04;
@@ -355,10 +378,10 @@ void HCIEmulator::processDeviceStatusFrame()
m_txbuffer[7] = 0x10;
m_txbuffer[9] = 0x02;
m_statemachine = STARTTOGGLELAMP_RELEASE;
m_lastStateTime = millis();
m_lastStateTime = esphome::millis();
break;
case STARTTOGGLELAMP_RELEASE:
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < esphome::millis())
{
m_txbuffer[7] = 0x08;
m_txbuffer[9] = 0x02;
@@ -432,7 +455,7 @@ void HCIEmulator::openDoor()
{
return;
}
m_lastStateTime = millis();
m_lastStateTime = esphome::millis();
m_statemachine = STARTOPENDOOR;
}
@@ -442,7 +465,7 @@ void HCIEmulator::openDoorHalf()
{
return;
}
m_lastStateTime = millis();
m_lastStateTime = esphome::millis();
m_statemachine = STARTOPENDOORHALF;
}
@@ -452,7 +475,7 @@ void HCIEmulator::closeDoor()
{
return;
}
m_lastStateTime = millis();
m_lastStateTime = esphome::millis();
m_statemachine = STARTCLOSEDOOR;
}
@@ -462,7 +485,7 @@ void HCIEmulator::stopDoor()
{
return;
}
m_lastStateTime = millis();
m_lastStateTime = esphome::millis();
m_statemachine = STARTSTOPDOOR;
}
@@ -472,7 +495,7 @@ void HCIEmulator::toggleLamp()
{
return;
}
m_lastStateTime = millis();
m_lastStateTime = esphome::millis();
m_statemachine = STARTTOGGLELAMP;
}
@@ -482,7 +505,7 @@ void HCIEmulator::ventilationPosition()
{
return;
}
m_lastStateTime = millis();
m_lastStateTime = esphome::millis();
m_statemachine = STARTVENTPOSITION;
}
@@ -490,3 +513,6 @@ void HCIEmulator::onStatusChanged(callback_function_t handler)
{
m_statusCallback = handler;
}
}
}

View File

@@ -1,9 +1,7 @@
#ifndef __hciemulator_h
#define __hciemulator_h
#include <Arduino.h>
#include <Stream.h>
#include <SoftwareSerial.h>
#include "esphome/components/uart/uart.h"
#define LL_OFF 0
#define LL_ERROR 1
@@ -11,8 +9,6 @@
#define LL_INFO 3
#define LL_DEBUG 4
#define DEFAULTLOGLEVEL LL_INFO
#define DEVICEID 0x02
#define BROADCASTID 0x00
#define SIMULATEKEYPRESSDELAYMS 100
@@ -26,7 +22,12 @@
// 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
#define T3_5 4800 //
namespace esphome {
namespace hoermann_door {
static const char *const TAG = "HCIEmulator";
enum DoorState : uint8_t
{
@@ -75,7 +76,7 @@ class HCIEmulator
public:
typedef std::function<void(const SHCIState &)> callback_function_t;
HCIEmulator();
HCIEmulator(InternalGPIOPin* pin, uart::UARTComponent* uart);
void poll();
@@ -111,10 +112,16 @@ protected:
void processDeviceStatusFrame();
void processDeviceBusScanFrame();
void processBroadcastStatusFrame();
//#ifdef USE_ESP_IDF
uint16_t word(uint8_t high, uint8_t low);
//#endif
private:
callback_function_t m_statusCallback;
Stream *m_port;
InternalGPIOPin* m_pin;
uart::UARTComponent* m_port;
SHCIState m_state;
StateMachine m_statemachine;
@@ -125,14 +132,17 @@ private:
size_t m_rxlen;
size_t m_txlen;
unsigned char m_rxbuffer[255] = {
uint8_t m_rxbuffer[255] = {
0,
};
unsigned char m_txbuffer[255] = {
uint8_t m_txbuffer[255] = {
0,
};
bool m_skipFrame;
};
}
}
#endif

View File

@@ -0,0 +1,138 @@
#pragma once
#include <algorithm>
namespace esphome {
namespace hoermann_door {
class HoermannMainComponent;
}
}
#include "hciemulator.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace hoermann_door {
static const char *const COMP_TAG = "Hoermann";
void dispatcherFn(void *arg);
class HoermannMainComponent: public Component{
protected:
HCIEmulator* emulator;
TaskHandle_t modBusTask;
int log_level = 3; // Defaults to INFO
uart::UARTComponent* _uart;
InternalGPIOPin* _tx_on;
volatile bool set_continue_ = true;
volatile unsigned long lastCall = 0;
volatile unsigned long maxPeriod = 0;
public:
void set_seriel_connection(uart::UARTComponent* uart){
this->_uart = uart;
}
void set_tx_on_pin(InternalGPIOPin* pin){
this->_tx_on = pin;
}
void set_log_level(int level){
this->log_level = level;
}
HCIEmulator* getEmulator(){
return emulator;
}
void setup() override {
this->_tx_on->setup();
this->emulator = new HCIEmulator(this->_tx_on, this->_uart);
this->emulator->setLogLevel(this->log_level);
this->start_polling();
}
void stop_polling(){
this->set_continue_ = false;
if(modBusTask != NULL){
// Wait for the task to be deleted
while(eTaskGetState(modBusTask) != eDeleted){
vTaskDelay(1);
}
modBusTask = NULL;
}
this->_tx_on->digital_write(false);
}
void start_polling() {
if(modBusTask == NULL){
this->_tx_on->digital_write(false);
this->set_continue_ = true;
xTaskCreatePinnedToCore(
dispatcherFn, // Function to implement the task
"ModBusTask", // Name of the task
10000, // Stack size in words
this, // Task input parameter
// 1, // Priority of the task
configMAX_PRIORITIES - 1,
&modBusTask, // Task handle.
1 // Core where the task should run
);
}
}
void modBusPolling(void *parameter)
{
while (set_continue_)
{
if (lastCall > 0)
{
maxPeriod = std::max((micros() - lastCall), (unsigned long)maxPeriod);
}
lastCall = micros();
emulator->poll();
vTaskDelay(1);
}
vTaskDelete(NULL);
}
void dump_config() override {
ESP_LOGCONFIG(COMP_TAG, "hoermann_door_component:");
ESP_LOGCONFIG(COMP_TAG, " UART: ");
ESP_LOGCONFIG(COMP_TAG, " UART is configured");
LOG_PIN(" TX_ON_PIN", this->_tx_on);
ESP_LOGCONFIG(COMP_TAG, " Log Level: %d", this->log_level);
}
};
void dispatcherFn(void *arg)
{
HoermannMainComponent *x = (HoermannMainComponent *)arg;
x->modBusPolling(arg);
}
template<typename... Ts> class StopPollingAction: public Action<Ts...> {
public:
StopPollingAction(HoermannMainComponent *motor) : motor_(motor) {}
void play(Ts... x) override { this->motor_->stop_polling(); }
protected:
HoermannMainComponent *motor_;
};
template<typename... Ts> class StartPollingAction: public Action<Ts...> {
public:
StartPollingAction(HoermannMainComponent *motor) : motor_(motor) {}
void play(Ts... x) override { this->motor_->start_polling(); }
protected:
HoermannMainComponent *motor_;
};
}
}

View File

@@ -2,31 +2,31 @@
#ifdef USE_LIGHT
class NbsLightManager;
#include "esphome.h"
#include "Arduino.h"
#include "hciemulator.h"
#include "door_singleton.h"
#include "hoermann.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 {
@@ -45,38 +45,41 @@ class NbsLightManager: public binary::BinaryLightOutput, public light::LightStat
class NbsLightOutput: public output::BinaryOutput, public Component{
light::LightState *callback;
HoermannSingleton *single;
bool lastState = false;
bool firstState = true;
HoermannMainComponent *mainComponent;
public:
virtual void write_state(bool state){
if(lastState != state){
single->getEmulator()->toggleLamp();
mainComponent->getEmulator()->toggleLamp();
//lastState = state;
}
}
void set_state_callback(light::LightState *callback){
if(callback == nullptr) ESP_LOGW("Hoermann_door(Light)", "Got Nullable callback");
if(callback == nullptr) ESP_LOGW(COMP_TAG, "Got Nullable callback");
this->callback = callback;
}
void set_emulator_component(HoermannMainComponent* component){
this->mainComponent = component;
}
void loop() override {
if(!single->getEmulator()->getState().valid) false;
if(firstState || single->getEmulator()->getState().lampOn != lastState){
if(!mainComponent->getEmulator()->getState().valid) false;
if(firstState || mainComponent->getEmulator()->getState().lampOn != lastState){
//ESP_LOGD("Test", "I have no idea");
lastState = single->getEmulator()->getState().lampOn;
lastState = mainComponent->getEmulator()->getState().lampOn;
if(lastState == true){
ESP_LOGD("Hoermann_door(Light)", "Light State ON");
ESP_LOGD(COMP_TAG, "Light State ON");
auto call = callback->make_call();
call.set_state(true);
call.perform();
}
else {
ESP_LOGD("Hoermann_door(Light)", "Light State OFF");
ESP_LOGD(COMP_TAG, "Light State OFF");
auto call = callback->make_call();
call.set_state(false);
call.perform();
@@ -87,8 +90,7 @@ class NbsLightOutput: public output::BinaryOutput, public Component{
}
void setup() override {
single = HoermannSingleton::getInstance();
single->initializeEmulator();
}
void turn_on() override {

View File

@@ -2,23 +2,29 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output, light
from esphome.const import CONF_ID
from . import HoermannDoor
hoermann_cover_ns = cg.esphome_ns.namespace('hoermann_door')
HoermannCoverLight = hoermann_cover_ns.class_('NbsLightOutput', output.BinaryOutput, cg.Component)
CONF_STATE_CALLBACK = "state_callback"
CONF_HOERMANN_CONTROLLER = "hoermann_controller"
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)
cv.Required(CONF_STATE_CALLBACK): cv.use_id(light.LightState),
cv.Required(CONF_HOERMANN_CONTROLLER): cv.use_id(HoermannDoor)
}).extend(cv.COMPONENT_SCHEMA)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield output.register_output(var, config)
await cg.register_component(var, config)
await output.register_output(var, config)
callback = yield cg.get_variable(config[CONF_STATE_CALLBACK])
callback = await cg.get_variable(config[CONF_STATE_CALLBACK])
cg.add(var.set_state_callback(callback))
controller = await cg.get_variable(config[CONF_HOERMANN_CONTROLLER])
cg.add(var.set_emulator_component(controller))
cg.add_library("plerup/EspSoftwareSerial", "8.2.0")
# cg.add_library("plerup/EspSoftwareSerial", "8.2.0")

View File

@@ -1,14 +1,26 @@
substitutions:
garageSide: sy # sy, wo
version: "2.1.3"
esphome:
name: garage${garageSide}
platform: ESP32
board: esp32dev
esp32:
board: wemos_d1_mini32
framework:
type: esp-idf
ota:
- platform: esphome
password: !secret gd_passwd
on_begin:
then:
- hoermann_door.stop_polling: door_controll_internal
- logger.log: "OTA-Update started, stopped polling"
on_error:
then:
- hoermann_door.start_polling: door_controll_internal
- logger.log: "OTA-Update failed, restarted polling"
api:
encryption:
@@ -17,7 +29,6 @@ api:
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
power_save_mode: none
fast_connect: true
logger:
#level: VERY_VERBOSE
@@ -29,8 +40,27 @@ external_components:
components: [ hoermann_door ]
cover:
- platform: hoermann_door
name: door_${garageSide}
- platform: hoermann_door
name: door_${garageSide}
hoermann_controller: door_controll_internal
uart:
rx_pin: 16
tx_pin: 17
baud_rate: 57600
id: hm_connection
data_bits: 8 # Standard, just defined for clarity
parity: EVEN # The chip uses even parity
stop_bits: 1 # Standard, just defined for clarity
hoermann_door:
id: door_controll_internal
uart_connection: hm_connection
log_level: INFO
tx_pin:
number: 25
inverted: false # Just for clarity, false is standard
light:
- platform: binary
@@ -42,5 +72,7 @@ output:
- id: light_out
platform: hoermann_door
state_callback: lamp_id_${garageSide}
hoermann_controller: door_controll_internal