Compare commits
No commits in common. "9719833b471870dd25b03f9b403e007ca422bdc4" and "fcb8838fb60adbcd9d26dbed5ca6e2adacd8438b" have entirely different histories.
9719833b47
...
fcb8838fb6
11
README.md
11
README.md
@ -77,13 +77,4 @@ 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.
|
||||||
|
|
||||||
# garage1.yaml (Hörmann Garage Doors)
|
More infos are coming soon.
|
||||||
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
|
|
@ -1,37 +0,0 @@
|
|||||||
esphome:
|
|
||||||
name: Clor_Pump
|
|
||||||
platform: ESP8266
|
|
||||||
board: nodemcuv2
|
|
||||||
|
|
||||||
# Enable logging
|
|
||||||
logger:
|
|
||||||
|
|
||||||
ota:
|
|
||||||
password: !secret cp_password
|
|
||||||
|
|
||||||
api:
|
|
||||||
encryption:
|
|
||||||
key: !secret cp_key
|
|
||||||
|
|
||||||
wifi:
|
|
||||||
ssid: !secret wifi_ssid
|
|
||||||
password: !secret wifi_passwd
|
|
||||||
fast_connect: true
|
|
||||||
|
|
||||||
switch:
|
|
||||||
- platform: gpio
|
|
||||||
pin: D3
|
|
||||||
id: pump_switch
|
|
||||||
- platform: custom
|
|
||||||
name: Clor Pump
|
|
||||||
|
|
||||||
button:
|
|
||||||
- platform: custom
|
|
||||||
name: prime
|
|
||||||
|
|
||||||
binary_sensor:
|
|
||||||
- platform: gpio
|
|
||||||
id: pool_pump
|
|
||||||
pin: D2
|
|
||||||
|
|
||||||
# Here is space for any display implementation that could possibliy be. Have fun OK
|
|
@ -1,18 +0,0 @@
|
|||||||
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)
|
|
@ -1,192 +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"
|
|
||||||
|
|
||||||
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
|
|
@ -1,492 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
@ -1,138 +0,0 @@
|
|||||||
#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
|
|
@ -1,18 +0,0 @@
|
|||||||
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)
|
|
56
garage1.yaml
56
garage1.yaml
@ -1,56 +0,0 @@
|
|||||||
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
|
|
Loading…
Reference in New Issue
Block a user