HoermannDoor: Now uses GPIOInternalPin and UARTComponent to communicate.

This commit is contained in:
2025-09-26 21:03:45 +02:00
parent 8198d8f62e
commit d3f0520123
10 changed files with 250 additions and 153 deletions

View File

@@ -0,0 +1,42 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
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)
CONF_UART_ENTRY = "uart_connection"
CONF_TX_PIN = "tx_pin"
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,
}),
validate_config
)
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))
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)) # Needs be configured before sensor

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,11 @@
#pragma once
#include "door_singleton.h"
#ifdef USE_COVER
class HoermannDoorx;
#include "esphome.h"
#include "Arduino.h"
#include "hoermann.h"
#include "hciemulator.h"
//#include "cover.h"
@@ -13,14 +13,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,11 +47,11 @@ 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{
@@ -61,39 +60,22 @@ public:
}
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 +91,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();

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,5 @@
#include "hciemulator.h"
#include "Arduino.h"
#define CHECKCHANGEDSET(Target, Value, Flag) \
if ((Target) != (Value)) \
{ \
@@ -33,7 +33,7 @@ void LogCore(int Level, const char *msg, const unsigned char *data = NULL, size_
}
else
{
Serial.println(msg);
ESP_LOGD(TAG, msg);
}
}
#else
@@ -98,14 +98,15 @@ 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);
};
@@ -122,7 +123,7 @@ 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()));
m_rxlen += m_port->read_array((u_int8_t *)(m_rxbuffer + m_rxlen), _min((int)(255 - m_rxlen), m_port->available()));
if (m_rxlen > 254)
{
Log(LL_ERROR, "RX Bufferoverflow, skip next Frame");
@@ -162,18 +163,19 @@ void HCIEmulator::poll()
// Log(LL_DEBUG, ("ST:"+String(m_lastSendTime)).c_str());
digitalWrite(TX_ON, HIGH);
m_pin->digital_write(HIGH);
// 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);
m_pin->digital_write(LOW);
m_txlen = 0;
}
}
else
{
Serial.println("skipped frame");
ESP_LOGD(TAG, "skipped frame");
}
m_skipFrame = false;

View File

@@ -1,9 +1,14 @@
#ifndef __hciemulator_h
#define __hciemulator_h
#include <Arduino.h>
#include <Stream.h>
#include <SoftwareSerial.h>
#ifdef USE_ESP_IDF
#include <cstdint>
#include <functional>
#include <algorithm>
#include "esp_idf.h"
#endif
#include "esphome/components/uart/uart.h"
#define LL_OFF 0
#define LL_ERROR 1
@@ -26,7 +31,9 @@
// 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 //
static const char *const TAG = "HCIEmulator";
enum DoorState : uint8_t
{
@@ -75,7 +82,7 @@ class HCIEmulator
public:
typedef std::function<void(const SHCIState &)> callback_function_t;
HCIEmulator();
HCIEmulator(esphome::InternalGPIOPin* pin, esphome::uart::UARTComponent* uart);
void poll();
@@ -114,7 +121,10 @@ protected:
private:
callback_function_t m_statusCallback;
Stream *m_port;
esphome::InternalGPIOPin* m_pin;
esphome::uart::UARTComponent* m_port;
SHCIState m_state;
StateMachine m_statemachine;
@@ -135,4 +145,20 @@ private:
bool m_skipFrame;
};
// Now, let's just define micros and millis and delay and delayMicros just so we know
#ifdef USE_ESP_IDF
unsigned long micros(){
return (unsigned long) esp_timer_get_time();
}
unsigned long millis(){
return (unsigned long) esp_timer_get_time() / 1000;
}
void delay(uint32 duration){
}
void delayMicroseconds(uint32 duration){
}
#endif
#endif

View File

@@ -0,0 +1,92 @@
#pragma once
class HoermannMainComponent;
#include "hciemulator.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace hoermann_door {
static const char *const TAG = "Hoermann";
void dispatcherFn(void *arg);
class HoermannMainComponent: public Component{
protected:
HCIEmulator* emulator;
TaskHandle_t modBusTask;
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;
}
HCIEmulator* getEmulator(){
return emulator;
}
void setup() override {
this->_tx_on->setup();
this->emulator = new HCIEmulator(this->_tx_on, this->_uart);
this->_tx_on->digital_write(LOW);
this->set_continue_ = false;
xTaskCreatePinnedToCore(
dispatcherFn, // 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
);
}
void stop_polling(){
this->_tx_on->digital_write(HIGH);
this->set_continue_ = false;
}
void modBusPolling(void *parameter)
{
while (set_continue_)
{
if (lastCall > 0)
{
maxPeriod = _max(micros() - lastCall, maxPeriod);
}
lastCall = micros();
emulator->poll();
vTaskDelay(1);
}
vTaskDelete(NULL);
}
void dump_config() override {
ESP_LOGCONFIG(TAG, "hoermann_door_component:");
ESP_LOGCONFIG(TAG, " UART: %d", this->_uart->get_baud_rate());
//LOG_PIN(TAG, this->_tx_on);
}
};
void dispatcherFn(void *arg)
{
HoermannMainComponent *x = (HoermannMainComponent *)arg;
x->modBusPolling(arg);
}
}
}

View File

@@ -2,10 +2,11 @@
#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
@@ -20,13 +21,14 @@ 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,15 +47,15 @@ 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;
}
}
@@ -62,21 +64,24 @@ class NbsLightOutput: public output::BinaryOutput, public Component{
if(callback == nullptr) ESP_LOGW("Hoermann_door(Light)", "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(TAG, "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");
ESP_LOGD(TAG, "Hoermann_door(Light)", "Light State OFF");
auto call = callback->make_call();
call.set_state(false);
call.perform();
@@ -87,8 +92,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

@@ -3,8 +3,11 @@ substitutions:
esphome:
name: garage${garageSide}
platform: ESP32
board: esp32dev
esp32:
board: wemos_d1_mini32
framework:
type: arduino
ota:
- platform: esphome
@@ -31,6 +34,18 @@ external_components:
cover:
- platform: hoermann_door
name: door_${garageSide}
hoermann_controller: door_controll_internal
uart:
rx_pin: 16
tx_pin: 17
baud_rate: 57600
id: hm_connection
hoermann_door:
id: door_controll_internal
uart_connection: hm_connection
tx_pin: 25
light:
- platform: binary
@@ -42,5 +57,7 @@ output:
- id: light_out
platform: hoermann_door
state_callback: lamp_id_${garageSide}
hoermann_controller: door_controll_internal