From 9719833b471870dd25b03f9b403e007ca422bdc4 Mon Sep 17 00:00:00 2001 From: Nicolas Bachschwell Date: Fri, 26 Apr 2024 21:02:48 +0200 Subject: [PATCH] Added garage1.yaml, including the implementation for the hoermann external components --- README.md | 11 +- external_components/hoermann_door/__init__.py | 0 external_components/hoermann_door/cover.py | 18 + .../hoermann_door/custom_garage_component.h | 192 +++++++ .../hoermann_door/hciemulator.cpp | 492 ++++++++++++++++++ .../hoermann_door/hciemulator.h | 138 +++++ external_components/hoermann_door/output.py | 18 + garage1.yaml | 56 ++ 8 files changed, 924 insertions(+), 1 deletion(-) create mode 100644 external_components/hoermann_door/__init__.py create mode 100644 external_components/hoermann_door/cover.py create mode 100644 external_components/hoermann_door/custom_garage_component.h create mode 100644 external_components/hoermann_door/hciemulator.cpp create mode 100644 external_components/hoermann_door/hciemulator.h create mode 100644 external_components/hoermann_door/output.py create mode 100644 garage1.yaml diff --git a/README.md b/README.md index 359494a..9d77af7 100644 --- a/README.md +++ b/README.md @@ -77,4 +77,13 @@ There are a few light effects configured so please play around with them for a l There is one thing I should mention tho: At line 34 in the ledstrip_simple.yaml there is a number called num_leds which dictates how many leds there are on the ledstrip. Please change that according to your ledstrip. -More infos are coming soon. \ No newline at end of file +# 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. You currently cannot use it without using BOTH the door and the output since they are on the same file and I put no safeguards in yet. Maybe later. + +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 \ No newline at end of file diff --git a/external_components/hoermann_door/__init__.py b/external_components/hoermann_door/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/external_components/hoermann_door/cover.py b/external_components/hoermann_door/cover.py new file mode 100644 index 0000000..a203815 --- /dev/null +++ b/external_components/hoermann_door/cover.py @@ -0,0 +1,18 @@ +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) \ No newline at end of file diff --git a/external_components/hoermann_door/custom_garage_component.h b/external_components/hoermann_door/custom_garage_component.h new file mode 100644 index 0000000..8a9640f --- /dev/null +++ b/external_components/hoermann_door/custom_garage_component.h @@ -0,0 +1,192 @@ +#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" + +namespace esphome { +namespace hoermann_door { + +HCIEmulator emulator; +TaskHandle_t modBusTask; +void modBusPolling(void *parameter); +class HoermanDoor : public Component, public cover::Cover +{ +public: + + HoermanDoor() + { + emulator; + } + + 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){ + emulator.openDoor(); + manual = false; + } + else if(pos == 0.0){ + emulator.closeDoor(); + manual = false; + } + else{ + ESP_LOGD("Door controller", "Not yet supported"); + } + + } + if (call.get_stop()) { + emulator.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 + { + // setup modbus + 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 + ); + + + + + } + + + + + 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 = emulator.getState().doorCurrentPosition; + if(pos != position){ + this->position = (float) position / 200.0; + this->publish_state(); + pos = position; + } + } + +}; + +// Custom binary output, for exposing binary states +class NbsLightOutput: public output::BinaryOutput, public Component { + public: + void setup() override { + + } + + void write_state(bool state) override { + + } + + bool lastState = false; + bool firstState = false; + void loop() override { + if(!emulator.getState().valid) false; + if(!firstState || emulator.getState().lampOn != lastState){ + //ESP_LOGD("Test", "I have no idea"); + lastState = emulator.getState().lampOn; + this->set_state(lastState); + firstState = true; + } + } + + void turn_on() override { + if(!lastState) emulator.toggleLamp(); + } + void turn_off() override { + if(lastState) emulator.toggleLamp(); + } +}; + +volatile unsigned long lastCall = 0; +volatile unsigned long maxPeriod = 0; +void modBusPolling(void *parameter) +{ + while (true) + { + if (lastCall > 0) + { + maxPeriod = _max(micros() - lastCall, maxPeriod); + } + lastCall = micros(); + emulator.poll(); + vTaskDelay(1); + } + vTaskDelete(NULL); +} + +} // namespace hoermann +} // namespace esphome \ No newline at end of file diff --git a/external_components/hoermann_door/hciemulator.cpp b/external_components/hoermann_door/hciemulator.cpp new file mode 100644 index 0000000..be233b9 --- /dev/null +++ b/external_components/hoermann_door/hciemulator.cpp @@ -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; +} diff --git a/external_components/hoermann_door/hciemulator.h b/external_components/hoermann_door/hciemulator.h new file mode 100644 index 0000000..be5296f --- /dev/null +++ b/external_components/hoermann_door/hciemulator.h @@ -0,0 +1,138 @@ +#ifndef __hciemulator_h +#define __hciemulator_h + +#include +#include +#include + +#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 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 diff --git a/external_components/hoermann_door/output.py b/external_components/hoermann_door/output.py new file mode 100644 index 0000000..055f383 --- /dev/null +++ b/external_components/hoermann_door/output.py @@ -0,0 +1,18 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import output +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) + +CONFIG_SCHEMA = output.BINARY_OUTPUT_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(HoermannCoverLight) +}).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) \ No newline at end of file diff --git a/garage1.yaml b/garage1.yaml new file mode 100644 index 0000000..16ed17c --- /dev/null +++ b/garage1.yaml @@ -0,0 +1,56 @@ +substitutions: + garageSide: sy # sy, wo + +esphome: + name: garage${garageSide} + platform: ESP32 + board: esp32dev + + libraries: + - plerup/EspSoftwareSerial + +# custom_component: +# - lambda: |- +# auto door = new HoermanDoor(); +# App.register_component(door); +# return {door}; +# components: +# - id: door_${garageSide} +# - lambda: |- +# auto light = new NbsLightOutput(); +# App.register_component(light); +# return {light}; +# components: +# - id: door_${garageSide}_lamp + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + 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 + output: light_out + +output: + - id: light_out + platform: hoermann_door + + +ota: + password: !secret gd_passwd +api: + encryption: + key: !secret gd_key \ No newline at end of file