Neue Version via Interrupt und für ESP32 zur Vermeidung von Timing-Problemen

HCPBridgeISR für ESP8266
HCPBridgeESP32 für ESP32
HCPBridge nicht mehr nutzen!
This commit is contained in:
Maik Hofmann 2021-02-24 19:08:31 +01:00
parent 3906d73c42
commit e13f6e5274
45 changed files with 2486 additions and 430 deletions

View File

@ -1,3 +0,0 @@
from distutils import dir_util
Import("env")
dir_util.copy_tree("patch",".")

View File

@ -1,423 +0,0 @@
/*
Asynchronous TCP library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "Arduino.h"
#include "SyncClient.h"
#include "ESPAsyncTCP.h"
#include "cbuf.h"
#include <interrupts.h>
#define DEBUG_ESP_SYNC_CLIENT
#if defined(DEBUG_ESP_SYNC_CLIENT) && !defined(SYNC_CLIENT_DEBUG)
#define SYNC_CLIENT_DEBUG( format, ...) DEBUG_GENERIC_P("[SYNC_CLIENT]", format, ##__VA_ARGS__)
#endif
#ifndef SYNC_CLIENT_DEBUG
#define SYNC_CLIENT_DEBUG(...) do { (void)0;} while(false)
#endif
/*
Without LWIP_NETIF_TX_SINGLE_PBUF, all tcp_writes default to "no copy".
Referenced data must be preserved and free-ed from the specified tcp_sent()
callback. Alternative, tcp_writes need to use the TCP_WRITE_FLAG_COPY
attribute.
*/
static_assert(LWIP_NETIF_TX_SINGLE_PBUF, "Required, tcp_write() must always copy.");
void DelayHandler(void);
void customDelay(long ms){
unsigned long startTime = millis();
do{
DelayHandler();
}while(startTime+ms>millis());
}
SyncClient::SyncClient(size_t txBufLen)
: _client(NULL)
, _tx_buffer(NULL)
, _tx_buffer_size(txBufLen)
, _rx_buffer(NULL)
, _ref(NULL)
{
ref();
}
SyncClient::SyncClient(AsyncClient *client, size_t txBufLen)
: _client(client)
, _tx_buffer(new (std::nothrow) cbuf(txBufLen))
, _tx_buffer_size(txBufLen)
, _rx_buffer(NULL)
, _ref(NULL)
{
if(ref() > 0 && _client != NULL)
_attachCallbacks();
}
SyncClient::~SyncClient(){
if (0 == unref())
_release();
}
void SyncClient::_release(){
if(_client != NULL){
_client->onData(NULL, NULL);
_client->onAck(NULL, NULL);
_client->onPoll(NULL, NULL);
_client->abort();
_client = NULL;
}
if(_tx_buffer != NULL){
cbuf *b = _tx_buffer;
_tx_buffer = NULL;
delete b;
}
while(_rx_buffer != NULL){
cbuf *b = _rx_buffer;
_rx_buffer = _rx_buffer->next;
delete b;
}
}
int SyncClient::ref(){
if(_ref == NULL){
_ref = new (std::nothrow) int;
if(_ref != NULL)
*_ref = 0;
else
return -1;
}
return (++*_ref);
}
int SyncClient::unref(){
int count = -1;
if (_ref != NULL) {
count = --*_ref;
if (0 == count) {
delete _ref;
_ref = NULL;
}
}
return count;
}
#if ASYNC_TCP_SSL_ENABLED
int SyncClient::_connect(const IPAddress& ip, uint16_t port, bool secure){
#else
int SyncClient::_connect(const IPAddress& ip, uint16_t port){
#endif
if(connected())
return 0;
if(_client != NULL)
delete _client;
_client = new (std::nothrow) AsyncClient();
if (_client == NULL)
return 0;
_client->onConnect([](void *obj, AsyncClient *c){ ((SyncClient*)(obj))->_onConnect(c); }, this);
_attachCallbacks_Disconnect();
#if ASYNC_TCP_SSL_ENABLED
if(_client->connect(ip, port, secure)){
#else
if(_client->connect(ip, port)){
#endif
while(_client != NULL && !_client->connected() && !_client->disconnecting())
customDelay(1);
return connected();
}
return 0;
}
#if ASYNC_TCP_SSL_ENABLED
int SyncClient::connect(const char *host, uint16_t port, bool secure){
#else
int SyncClient::connect(const char *host, uint16_t port){
#endif
if(connected())
return 0;
if(_client != NULL)
delete _client;
_client = new (std::nothrow) AsyncClient();
if (_client == NULL)
return 0;
_client->onConnect([](void *obj, AsyncClient *c){ ((SyncClient*)(obj))->_onConnect(c); }, this);
_attachCallbacks_Disconnect();
#if ASYNC_TCP_SSL_ENABLED
if(_client->connect(host, port, secure)){
#else
if(_client->connect(host, port)){
#endif
while(_client != NULL && !_client->connected() && !_client->disconnecting())
customDelay(1);
return connected();
}
return 0;
}
//#define SYNCCLIENT_NEW_OPERATOR_EQUAL
#ifdef SYNCCLIENT_NEW_OPERATOR_EQUAL
/*
New behavior for operator=
Allow for the object to be placed on a queue and transfered to a new container
with buffers still in tact. Avoiding receive data drops. Transfers rx and tx
buffers. Supports return by value.
Note, this is optional, the old behavior is the default.
*/
SyncClient & SyncClient::operator=(const SyncClient &other){
int *rhsref = other._ref;
++*rhsref; // Just in case the left and right side are the same object with different containers
if (0 == unref())
_release();
_ref = other._ref;
ref();
--*rhsref;
// Why do I not test _tx_buffer for != NULL and free?
// I allow for the lh target container, to be a copy of an active
// connection. Thus we are just reusing the container.
// The above unref() handles releaseing the previous client of the container.
_tx_buffer_size = other._tx_buffer_size;
_tx_buffer = other._tx_buffer;
_client = other._client;
if (_client != NULL && _tx_buffer == NULL)
_tx_buffer = new (std::nothrow) cbuf(_tx_buffer_size);
_rx_buffer = other._rx_buffer;
if(_client)
_attachCallbacks();
return *this;
}
#else // ! SYNCCLIENT_NEW_OPERATOR_EQUAL
// This is the origianl logic with null checks
SyncClient & SyncClient::operator=(const SyncClient &other){
if(_client != NULL){
_client->abort();
_client->free();
_client = NULL;
}
_tx_buffer_size = other._tx_buffer_size;
if(_tx_buffer != NULL){
cbuf *b = _tx_buffer;
_tx_buffer = NULL;
delete b;
}
while(_rx_buffer != NULL){
cbuf *b = _rx_buffer;
_rx_buffer = b->next;
delete b;
}
if(other._client != NULL)
_tx_buffer = new (std::nothrow) cbuf(other._tx_buffer_size);
_client = other._client;
if(_client)
_attachCallbacks();
return *this;
}
#endif
void SyncClient::setTimeout(uint32_t seconds){
if(_client != NULL)
_client->setRxTimeout(seconds);
}
uint8_t SyncClient::status(){
if(_client == NULL)
return 0;
return _client->state();
}
uint8_t SyncClient::connected(){
return (_client != NULL && _client->connected());
}
bool SyncClient::stop(unsigned int maxWaitMs){
(void)maxWaitMs;
if(_client != NULL)
_client->close(true);
return true;
}
size_t SyncClient::_sendBuffer(){
if(_client == NULL || _tx_buffer == NULL)
return 0;
size_t available = _tx_buffer->available();
if(!connected() || !_client->canSend() || available == 0)
return 0;
size_t sendable = _client->space();
if(sendable < available)
available= sendable;
char *out = new (std::nothrow) char[available];
if(out == NULL)
return 0;
_tx_buffer->read(out, available);
size_t sent = _client->write(out, available);
delete[] out;
return sent;
}
void SyncClient::_onData(void *data, size_t len){
_client->ackLater();
cbuf *b = new (std::nothrow) cbuf(len+1);
if(b != NULL){
b->write((const char *)data, len);
if(_rx_buffer == NULL)
_rx_buffer = b;
else {
cbuf *p = _rx_buffer;
while(p->next != NULL)
p = p->next;
p->next = b;
}
} else {
// We ran out of memory. This fail causes lost receive data.
// The connection should be closed in a manner that conveys something
// bad/abnormal has happened to the connection. Hence, we abort the
// connection to avoid possible data corruption.
// Note, callbacks maybe called.
_client->abort();
}
}
void SyncClient::_onDisconnect(){
if(_client != NULL){
_client = NULL;
}
if(_tx_buffer != NULL){
cbuf *b = _tx_buffer;
_tx_buffer = NULL;
delete b;
}
}
void SyncClient::_onConnect(AsyncClient *c){
_client = c;
if(_tx_buffer != NULL){
cbuf *b = _tx_buffer;
_tx_buffer = NULL;
delete b;
}
_tx_buffer = new (std::nothrow) cbuf(_tx_buffer_size);
_attachCallbacks_AfterConnected();
}
void SyncClient::_attachCallbacks(){
_attachCallbacks_Disconnect();
_attachCallbacks_AfterConnected();
}
void SyncClient::_attachCallbacks_AfterConnected(){
_client->onAck([](void *obj, AsyncClient* c, size_t len, uint32_t time){ (void)c; (void)len; (void)time; ((SyncClient*)(obj))->_sendBuffer(); }, this);
_client->onData([](void *obj, AsyncClient* c, void *data, size_t len){ (void)c; ((SyncClient*)(obj))->_onData(data, len); }, this);
_client->onTimeout([](void *obj, AsyncClient* c, uint32_t time){ (void)obj; (void)time; c->close(); }, this);
}
void SyncClient::_attachCallbacks_Disconnect(){
_client->onDisconnect([](void *obj, AsyncClient* c){ ((SyncClient*)(obj))->_onDisconnect(); delete c; }, this);
}
size_t SyncClient::write(uint8_t data){
return write(&data, 1);
}
size_t SyncClient::write(const uint8_t *data, size_t len){
if(_tx_buffer == NULL || !connected()){
return 0;
}
size_t toWrite = 0;
size_t toSend = len;
while(_tx_buffer->room() < toSend){
toWrite = _tx_buffer->room();
_tx_buffer->write((const char*)data, toWrite);
while(connected() && !_client->canSend())
customDelay(0);
if(!connected())
return 0;
_sendBuffer();
toSend -= toWrite;
}
_tx_buffer->write((const char*)(data+(len - toSend)), toSend);
if(connected() && _client->canSend())
_sendBuffer();
return len;
}
int SyncClient::available(){
if(_rx_buffer == NULL) return 0;
size_t a = 0;
cbuf *b = _rx_buffer;
while(b != NULL){
a += b->available();
b = b->next;
}
return a;
}
int SyncClient::peek(){
if(_rx_buffer == NULL) return -1;
return _rx_buffer->peek();
}
int SyncClient::read(uint8_t *data, size_t len){
if(_rx_buffer == NULL) return -1;
size_t readSoFar = 0;
while(_rx_buffer != NULL && (len - readSoFar) >= _rx_buffer->available()){
cbuf *b = _rx_buffer;
_rx_buffer = _rx_buffer->next;
size_t toRead = b->available();
readSoFar += b->read((char*)(data+readSoFar), toRead);
if(connected()){
_client->ack(b->size() - 1);
}
delete b;
}
if(_rx_buffer != NULL && readSoFar < len){
readSoFar += _rx_buffer->read((char*)(data+readSoFar), (len - readSoFar));
}
return readSoFar;
}
int SyncClient::read(){
uint8_t res = 0;
if(read(&res, 1) != 1)
return -1;
return res;
}
bool SyncClient::flush(unsigned int maxWaitMs){
(void)maxWaitMs;
if(_tx_buffer == NULL || !connected())
return false;
if(_tx_buffer->available()){
while(connected() && !_client->canSend())
customDelay(0);
if(_client == NULL || _tx_buffer == NULL)
return false;
_sendBuffer();
}
return true;
}

View File

@ -15,4 +15,3 @@ framework = arduino
lib_deps =
ottowinter/ESPAsyncWebServer-esphome@^1.2.7
bblanchon/ArduinoJson@^6.17.2
extra_scripts = pre:extra_script.py

View File

@ -5,6 +5,10 @@
#include "hciemulator.h"
#include "index_html.h"
!!! DONT USE THIS, BECAUSE OF TIMING PROBLEMS !!!
!!! USE THE ISR VERSION OR SWITCH TO ESP32 !!!
/* create this file and add your wlan credentials
const char* ssid = "MyWLANSID";
const char* password = "MYPASSWORD";
@ -61,8 +65,6 @@ void switchLamp(bool on){
}
}
// setup mcu
void setup(){
@ -71,6 +73,7 @@ void setup(){
#ifdef SWAPUART
RS485.swap();
#endif
//setup wifi
WiFi.mode(WIFI_STA);

5
HCPBridgeESP32/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

View File

@ -0,0 +1,7 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
]
}

12
HCPBridgeESP32/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
"files.associations": {
"array": "cpp",
"deque": "cpp",
"list": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"initializer_list": "cpp",
"regex": "cpp"
}
}

View File

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

46
HCPBridgeESP32/lib/README Normal file
View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@ -0,0 +1,18 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp12e]
platform = espressif32
board = nodemcu-32s
framework = arduino
lib_deps =
ottowinter/ESPAsyncWebServer-esphome@^1.2.7
bblanchon/ArduinoJson@^6.17.2
plerup/EspSoftwareSerial@^6.11.6

View File

@ -0,0 +1,425 @@
#include "hciemulator.h"
#define CHECKCHANGEDSET(Target,Value,Flag) if((Target)!=(Value)){Target=Value;Flag=true;}
int hciloglevel = DEFAULTLOGLEVEL;
#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(Stream * port) {
m_state.valid = false;
m_statemachine=WAITING;
m_rxlen = m_txlen = 0;
m_recvTime=m_lastStateTime=0;
m_skipFrame=false;
m_port = port;
m_statusCallback = NULL;
setLogLevel(DEFAULTLOGLEVEL);
};
void HCIEmulator::poll(){
if(m_port==NULL) return;
// receive Data
if(m_port->available() >0)
{
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();
}
// check frame, process frame
if(m_rxlen>0 && (micros()-m_recvTime > T3_5))
{
// 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());
m_port->write(m_txbuffer, m_txlen);
Log3(LL_DEBUG,"Response: ", m_txbuffer, m_txlen);
m_txlen = 0;
}
}
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_P(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_P(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_P(m_txbuffer, ResponseTemplate_Fcn17_Cmd02_L05, sizeof(ResponseTemplate_Fcn17_Cmd02_L05));
m_txbuffer[0] = m_rxbuffer[0];
m_txbuffer[3] = counter;
m_txbuffer[5] = cmd;
m_txlen = sizeof(ResponseTemplate_Fcn17_Cmd02_L05);
Log(LL_INFO,"Busscan received");
}
void HCIEmulator::processBroadcastStatusFrame(){
//001B: 00 10 9D 31 00 09 12 64 00 00 00 40 60 00 00 00 00 00 00 00 00 00 01 00 00 CA 22
bool hasChanged = false;
CHECKCHANGEDSET(m_state.lampOn,m_rxbuffer[20] == 0x14,hasChanged);
CHECKCHANGEDSET(m_state.doorCurrentPosition,m_rxbuffer[10],hasChanged);
CHECKCHANGEDSET(m_state.doorTargetPosition, m_rxbuffer[9],hasChanged);
CHECKCHANGEDSET(m_state.doorState, m_rxbuffer[11],hasChanged);
CHECKCHANGEDSET(m_state.reserved, m_rxbuffer[17],hasChanged);
CHECKCHANGEDSET(m_state.valid, true,hasChanged);
if(hasChanged){
Log3(LL_INFO,"New State: ",m_rxbuffer,m_rxlen);
if(m_statusCallback != NULL){
m_statusCallback(m_state);
}
}
}
void HCIEmulator::openDoor(){
if(m_statemachine != WAITING){
return;
}
m_lastStateTime = millis();
m_statemachine = STARTOPENDOOR;
}
void HCIEmulator::openDoorHalf(){
if(m_statemachine != WAITING){
return;
}
m_lastStateTime = millis();
m_statemachine = STARTOPENDOORHALF;
}
void HCIEmulator::closeDoor(){
if(m_statemachine != WAITING){
return;
}
m_lastStateTime = millis();
m_statemachine = STARTCLOSEDOOR;
}
void HCIEmulator::stopDoor(){
if(m_statemachine != WAITING){
return;
}
m_lastStateTime = millis();
m_statemachine = STARTSTOPDOOR;
}
void HCIEmulator::toggleLamp(){
if(m_statemachine != WAITING){
return;
}
m_lastStateTime = millis();
m_statemachine = STARTTOGGLELAMP;
}
void HCIEmulator::ventilationPosition(){
if(m_statemachine != WAITING){
return;
}
m_lastStateTime = millis();
m_statemachine = STARTVENTPOSITION;
}
void HCIEmulator::onStatusChanged(callback_function_t handler) {
m_statusCallback = handler;
}

View File

@ -0,0 +1,131 @@
#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_WARN
#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(Stream * port);
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];
unsigned char m_txbuffer[255];
bool m_skipFrame;
};
#endif

File diff suppressed because one or more lines are too long

200
HCPBridgeESP32/src/main.cpp Normal file
View File

@ -0,0 +1,200 @@
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include "AsyncJson.h"
#include "ArduinoJson.h"
#include "hciemulator.h"
#include "index_html.h"
/* create this file and add your wlan credentials
const char* ssid = "MyWLANSID";
const char* password = "MYPASSWORD";
*/
#include "../../../private/credentials.h"
// switch relay sync to the lamp
// e.g. the Wifi Relay Board U4648
#define USERELAY
// use alternative uart pins
//#define SWAPUART
#define RS485 Serial
// Relay Board parameters
#define ESP8266_GPIO2 2 // Blue LED.
#define ESP8266_GPIO4 4 // Relay control.
#define ESP8266_GPIO5 5 // Optocoupler input.
#define LED_PIN ESP8266_GPIO2
// Hörmann HCP2 based on modbus rtu @57.6kB 8E1
HCIEmulator emulator(&RS485);
// webserver on port 80
AsyncWebServer server(80);
// called by ESPAsyncTCP-esphome:SyncClient.cpp (see patch) instead of delay to avoid connection breaks
void DelayHandler(void){
emulator.poll();
}
// switch GPIO4 und GPIO2 sync to the lamp
void onStatusChanged(const SHCIState& state){
//see https://ucexperiment.wordpress.com/2016/12/18/yunshan-esp8266-250v-15a-acdc-network-wifi-relay-module/
//Setting GPIO4 high, causes the relay to close the NO contact with
if(state.valid){
digitalWrite( ESP8266_GPIO4, state.lampOn );
digitalWrite(LED_PIN, state.lampOn);
}else
{
digitalWrite( ESP8266_GPIO4, false );
digitalWrite(LED_PIN, false);
}
}
// toggle lamp to expected state
void switchLamp(bool on){
bool toggle = (on && !emulator.getState().lampOn) || (!on && emulator.getState().lampOn);
if(toggle){
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);
}
TaskHandle_t modBusTask;
// setup mcu
void setup(){
//setup modbus
RS485.begin(57600,SERIAL_8E1);
#ifdef SWAPUART
RS485.swap();
#endif
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 */
//setup wifi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
WiFi.setAutoReconnect(true);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
// setup http server
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response = request->beginResponse_P( 200, "text/html", index_html,sizeof(index_html));
response->addHeader("Content-Encoding","deflate");
request->send(response);
});
server.on("/status", HTTP_GET, [](AsyncWebServerRequest *request){
const SHCIState& doorstate = emulator.getState();
AsyncResponseStream *response = request->beginResponseStream("application/json");
DynamicJsonDocument root(1024);
root["valid"] = doorstate.valid;
root["doorstate"] = doorstate.doorState;
root["doorposition"] = doorstate.doorCurrentPosition;
root["doortarget"] = doorstate.doorTargetPosition;
root["lamp"] = doorstate.lampOn;
root["debug"] = doorstate.reserved;
root["lastresponse"] = emulator.getMessageAge()/1000;
root["looptime"] = maxPeriod;
lastCall = maxPeriod = 0;
serializeJson(root,*response);
request->send(response);
});
server.on("/command", HTTP_GET, [] (AsyncWebServerRequest *request) {
if (request->hasParam("action")) {
int actionid = request->getParam("action")->value().toInt();
switch (actionid){
case 0:
emulator.closeDoor();
break;
case 1:
emulator.openDoor();
break;
case 2:
emulator.stopDoor();
break;
case 3:
emulator.ventilationPosition();
break;
case 4:
emulator.openDoorHalf();
break;
case 5:
emulator.toggleLamp();
break;
default:
break;
}
}
request->send(200, "text/plain", "OK");
});
server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
if (request->hasParam("channel") && request->hasParam("state")) {
String channel = request->getParam("channel")->value();
String state = request->getParam("state")->value();
if(channel.equals("door")){
if(state=="1"){
emulator.openDoor();
}else{
emulator.closeDoor();
}
}
if(channel.equals("light")){
switchLamp(state=="1");
}
}
request->send(200, "text/plain", "OK");
});
server.begin();
//setup relay board
#ifdef USERELAY
pinMode( ESP8266_GPIO4, OUTPUT ); // Relay control pin.
pinMode( ESP8266_GPIO5, INPUT_PULLUP ); // Input pin.
pinMode( LED_PIN, OUTPUT ); // ESP8266 module blue L
digitalWrite( ESP8266_GPIO4, 0 );
digitalWrite(LED_PIN,0);
emulator.onStatusChanged(onStatusChanged);
#endif
}
// mainloop
void loop(){
}

View File

@ -0,0 +1 @@
python compress.py

View File

View File

@ -0,0 +1,35 @@
#pip install htmlmin
#or python -m pip install htmlmin
#pip install jsmin
#or python -m pip install jsmin
import gzip
import zlib
import htmlmin
from jsmin import jsmin
content = ""
with open('index.html','rt',encoding="utf-8") as f:
content=f.read()
content = htmlmin.minify(content, remove_comments=True, remove_empty_space=True, remove_all_empty_space=True, reduce_empty_attributes=True, reduce_boolean_attributes=False, remove_optional_attribute_quotes=True, convert_charrefs=True, keep_pre=False)
import re
regex = r"<script>(.+?)<\/script>"
content = re.sub(regex, lambda x: "<script>"+jsmin(x.group(1))+"</script>" ,content, 0, re.DOTALL)
result =""
for c in zlib.compress(content.encode("UTF-8"),9):
result= result + ("0x%02X" %c)
if len(result)> 0:
result=result + ","
with open('../index_html.h',"wt") as f:
f.write("const uint8_t index_html[] PROGMEM = {");
f.write(result.strip(","))
f.write("};");

View File

@ -0,0 +1,159 @@
<!DOCTYPE HTML><html>
<head>
<title>Garagentor Steuerung</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<link rel="icon" href="data:,">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 3.0rem;}
p {font-size: 3.0rem;}
body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
.switch {position: relative; display: inline-block; width: 80px; height: 48px}
.switch input {display: none}
.slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 6px}
.slider:before {position: absolute; content: ""; height: 32px; width: 32px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
input:checked+.slider {background-color: #b30000}
input:checked+.slider:before {-webkit-transform: translateX(32px); -ms-transform: translateX(32px); transform: translateX(32px)}
.button {
border: 4px;
border-style: solid;
color: black;
width: 100px;
height: 100px;
text-align: center;
display: inline;
text-decoration: none;
display: inline-block;
font-weight: bold;
font-size: 20px;
margin: 4px 2px;
cursor: pointer;
border-radius: 50px;
}
.buttonred {border-color: #ED5961;}
.buttonyellow {border-color: #fcca00;}
.buttongreen {border-color: #48bd81;}
</style>
</head>
<body>
<h2>Garagentor</h2>
<canvas width="250px" height="250px" id="dc"></canvas>
<h3 id="status">warte auf Verbindung.</h3>
<hr/>
<button class="button buttonred" onclick="doCommand(1)">Auf</button> <button class="button buttonyellow" onclick="doCommand(2)">Stop</button> <button class="button buttongreen" onclick="doCommand(0)">Zu</button>
<hr/>
<h4>Licht</h4><label class="switch"><input type="checkbox" onchange="doCommand(5)" id="light"><span class="slider"></span></label>
<hr/>
<script>
doorpos = 10;
dir = -1;
isconnected = false;
var animation;
function startDoorAnimation() {
if(!animation){
animation = setInterval(Draw, 200);
}
}
function stopDoorAnimation() {
if(animation){
clearInterval(animation);
}
animation = null;
}
function Draw(){
var svg =document.getElementById("svg");
var dc = document.getElementById("dc");
var ctx = dc.getContext("2d");
width = dc.width;
height = dc.height;
ctx.clearRect(0, 0, width, height);
xa= [18,18,10,3,174,347,340,332,332,297,297,52,52,18];
ya= [327,114,117,100,22,100,117,114,327,327,112,112,327,327];
ctx.fillStyle = "black";
ctx.beginPath();
for (i =0;i<xa.length;i++){
if(i==0){
ctx.moveTo((xa[i]/350) * width , (ya[i]/350)*width);
}else{
ctx.lineTo((xa[i]/350) * width, (ya[i]/350)*width);
}
}
ctx.fill();
for(i=0;i<doorpos;i++){
ctx.fillRect((62/350) * width, ((120+21*i)/350) * width, (225/350) * width, (18/350) * width);
}
doorpos+=dir;
if(dir<0 && doorpos<=0){
dir=dir*-1;
}
if(dir>0 &&doorpos>=10){
dir=dir*-1;
}
}
function doCommand(action) {
if(!isconnected){return;}
var xhr = new XMLHttpRequest();
xhr.open("GET", "/command?action="+action, true);
xhr.send();
}
function setDoorState(state){
stopDoorAnimation();
if(state == 0) {doorpos = 10;dir = -1};
if(state == 1) {doorpos = 0; dir = 1};
if(state == 2) {doorpos = 5; dir = 1};
Draw();
if(state == 3) startDoorAnimation();
}
function getStatusText(id){
result = "Das Tor "
switch(id)
{
case 0x20: setDoorState(1); return result+"ist geöffnet.";
case 0x40: setDoorState(0); return result+"ist geschlossen.";
case 0x80: setDoorState(2); return result+"ist teilgeöffnet.";
case 0x00: setDoorState(2); return result+"ist teilgeöffnet.";
case 0x02: setDoorState(3); return result+"schließt.";
case 0x01: setDoorState(3); return result+"öffnet.";
case -1: setDoorState(0); return "keine Verbindung zum Tor.";
default: return "unbekanter Status: " + id;
}
}
function updateData(){
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var status = JSON.parse(this.responseText);
isconnected = status.valid;
document.getElementById("status").innerHTML = getStatusText(status.valid?status.doorstate:-1);
document.getElementById("light").checked = status.lamp & status.valid;
return;
}
isconnected= false;
};
xmlhttp.open("GET", "/status", true);
xmlhttp.send();
}
Draw();
updateData();
setInterval(updateData, 3000);
</script>
</body>
</html>

View File

@ -0,0 +1 @@
python -m http.server

View File

@ -0,0 +1,9 @@
{
"valid" : true,
"doorstate" : 1,
"doorposition" : 0,
"doortarget" : 0,
"lamp" : true,
"debug" : 0,
"lastresponse" : 0
}

View File

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html

5
HCPBridgeISR/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

7
HCPBridgeISR/.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
]
}

13
HCPBridgeISR/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"files.associations": {
"array": "cpp",
"deque": "cpp",
"list": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"initializer_list": "cpp",
"regex": "cpp",
"functional": "cpp"
}
}

View File

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

46
HCPBridgeISR/lib/README Normal file
View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@ -0,0 +1,17 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp12e]
platform = espressif8266
board = esp12e
framework = arduino
lib_deps =
ottowinter/ESPAsyncWebServer-esphome@^1.2.7
bblanchon/ArduinoJson@^6.17.2

View File

@ -0,0 +1,18 @@
import serial
import time
ser = serial.Serial('COM3', baudrate=57600, bytesize=8,timeout=1, parity=serial.PARITY_EVEN, stopbits=serial.STOPBITS_ONE) # open serial port
while True:
busscan = "02 17 9C B9 00 05 9C 41 00 03 06 00 02 00 00 01 02 f8 35"
print(bytearray.fromhex(busscan))
ser.write(bytearray.fromhex(busscan))
#time.sleep(2)
print(ser.read(15))
status = "02 17 9C B9 00 08 9C 41 00 02 04 3E 03 00 00 EB CC"
print(bytearray.fromhex(status))
ser.write(bytearray.fromhex(status))
#time.sleep(2)
print(ser.read(21))

21
HCPBridgeISR/src/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,10 @@
# esp-uart
Blocking ESP8266 UART driver with interrupt-driven RX and TX buffers.
I needed an UART driver where I have complete control over the input and output, because I have the UART chip attached to a multiplexer that connects to multiple serial ports. The driver supplied by the manufacturer works by queueing read callback tasks from the interrupts, which was unnecessary complication for my use case. This driver is a bit work in progress, but should be already usable.
The license of uart_register.h is a little bit unclear, but because it only
defines the hardware memory locations, it should not be copyrightable. In any
case it can only be used on ESP8266 and is necessary to be able to use the UART
functionality, so the manufacturer most likely does not have any issues with using it.

44
HCPBridgeISR/src/crc.c Normal file
View File

@ -0,0 +1,44 @@
#include "crc.h"
/**
* 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;
}

22
HCPBridgeISR/src/crc.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef __crc_h
#define __crc_h
#ifdef __cplusplus
extern "C" {
#endif
#include <Arduino.h>
//modbus crc calculation borrowed from:
//https://github.com/yaacov/ArduinoModbusSlave
#define MODBUS_CRC_LENGTH 2
#define readUInt16(arr, index) (arr[index]<<8 | arr[index + 1])
#define readCRC(arr, length) (arr[(length - MODBUS_CRC_LENGTH) + 1] << 8 | arr[length - MODBUS_CRC_LENGTH])
uint16_t calculateCRC(uint8_t *buffer, int length);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,511 @@
/*
* modified/reduced to provide HCP Modbus low level functions
*
* The MIT License (MIT)
*
* Copyright (c) 2016 Juho Vähä-Herttua (juhovh@iki.fi)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "hciemulator.h"
#include "crc.h"
#include "osapi.h"
#include "ets_sys.h"
#include "uart_register.h"
#define T3_5 4800 //1750
#define DEVICEID 0x02
#define BROADCASTID 0x00
#define SIMULATEKEYPRESSDELAYMS 100
#define CHECKCHANGEDSET(Target,Value,Flag) if((Target)!=(Value)){Target=Value;Flag=true;}
#define UART0 0
// Missing defines from SDK
#define FUNC_U0RXD 0
// Define FIFO sizes, actually 128 but playing safe
#define UART_RXFIFO_SIZE 126
#define UART_TXFIFO_SIZE 126
#define UART_RXTOUT_TH 2
#define UART_RXFIFO_TH 100
#define UART_TXFIFO_TH 10
// Define some helper macros to handle FIFO functions
#define UART_TXFIFO_LEN(uart_no) \
((READ_PERI_REG(UART_STATUS(uart_no)) >> UART_TXFIFO_CNT_S) & UART_RXFIFO_CNT)
#define UART_TXFIFO_PUT(uart_no, byte) \
WRITE_PERI_REG(UART_FIFO(uart_no), (byte) & 0xff)
#define UART_RXFIFO_LEN(uart_no) \
((READ_PERI_REG(UART_STATUS(uart_no)) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT)
#define UART_RXFIFO_GET(uart_no) \
READ_PERI_REG(UART_FIFO(uart_no))
bool skipFrame = false;
int recvTime = 0;
int lastSendTime = 0;
int lastStateTime = 0;
size_t rxlen = 0;
size_t txlen = 0;
size_t txpos = 0;
unsigned char rxbuffer[255];
unsigned char txbuffer[255];
EStateMachine statemachine = WAITING;
SHCIState state;
//--------------------------------------------------------------
void openDoor(){
if(statemachine != WAITING){
return;
}
lastStateTime = millis();
statemachine = STARTOPENDOOR;
}
void openDoorHalf(){
if(statemachine != WAITING){
return;
}
lastStateTime = millis();
statemachine = STARTOPENDOORHALF;
}
void closeDoor(){
if(statemachine != WAITING){
return;
}
lastStateTime = millis();
statemachine = STARTCLOSEDOOR;
}
void stopDoor(){
if(statemachine != WAITING){
return;
}
lastStateTime = millis();
statemachine = STARTSTOPDOOR;
}
void toggleLamp(){
if(statemachine != WAITING){
return;
}
lastStateTime = millis();
statemachine = STARTTOGGLELAMP;
}
void ventilationPosition(){
if(statemachine != WAITING){
return;
}
lastStateTime = millis();
statemachine = STARTVENTPOSITION;
}
unsigned long getMessageAge(){
return micros()-recvTime;
}
SHCIState* getHCIState(){
return &state;
}
//--------------------------------------------------------------
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};
static void processDeviceStatusFrame(){
if(rxlen==0x11){
unsigned char counter = rxbuffer[11];
unsigned char cmd = rxbuffer[12];
if(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_P(txbuffer, ResponseTemplate_Fcn17_Cmd03_L08, sizeof(ResponseTemplate_Fcn17_Cmd03_L08));
txbuffer[0] = rxbuffer[0];
txbuffer[3] = counter;
txbuffer[5] = cmd;
txlen = sizeof(ResponseTemplate_Fcn17_Cmd03_L08);
switch(statemachine)
{
// open Door
case STARTOPENDOOR:
txbuffer[7]= 0x02;
txbuffer[8]= 0x10;
statemachine = STARTOPENDOOR_RELEASE;
lastStateTime = millis();
break;
case STARTOPENDOOR_RELEASE:
if(lastStateTime+SIMULATEKEYPRESSDELAYMS<millis()){
txbuffer[7]= 0x01;
txbuffer[8]= 0x10;
statemachine = WAITING;
}
break;
// close Door
case STARTCLOSEDOOR:
txbuffer[7]= 0x02;
txbuffer[8]= 0x20;
statemachine = STARTCLOSEDOOR_RELEASE;
lastStateTime = millis();
break;
case STARTCLOSEDOOR_RELEASE:
if(lastStateTime+SIMULATEKEYPRESSDELAYMS<millis()){
txbuffer[7]= 0x01;
txbuffer[8]= 0x20;
statemachine = WAITING;
}
break;
// stop Door
case STARTSTOPDOOR:
txbuffer[7]= 0x02;
txbuffer[8]= 0x40;
statemachine = STARTSTOPDOOR_RELEASE;
lastStateTime = millis();
break;
case STARTSTOPDOOR_RELEASE:
if(lastStateTime+SIMULATEKEYPRESSDELAYMS<millis()){
txbuffer[7]= 0x01;
txbuffer[8]= 0x40;
statemachine = WAITING;
}
break;
// Ventilation
case STARTVENTPOSITION:
txbuffer[7]= 0x02;
txbuffer[9]= 0x40;
statemachine = STARTVENTPOSITION_RELEASE;
lastStateTime = millis();
break;
case STARTVENTPOSITION_RELEASE:
if(lastStateTime+SIMULATEKEYPRESSDELAYMS<millis()){
txbuffer[7]= 0x01;
txbuffer[9]= 0x40;
statemachine = WAITING;
}
break;
// Half Position
case STARTOPENDOORHALF:
txbuffer[7]= 0x02;
txbuffer[9]= 0x04;
statemachine = STARTOPENDOORHALF_RELEASE;
lastStateTime = millis();
break;
case STARTOPENDOORHALF_RELEASE:
if(lastStateTime+SIMULATEKEYPRESSDELAYMS<millis()){
txbuffer[7]= 0x01;
txbuffer[9]= 0x04;
statemachine = WAITING;
}
break;
// Toggle Lamp
case STARTTOGGLELAMP:
txbuffer[7]= 0x10;
txbuffer[9]= 0x02;
statemachine = STARTTOGGLELAMP_RELEASE;
lastStateTime = millis();
break;
case STARTTOGGLELAMP_RELEASE:
if(lastStateTime+SIMULATEKEYPRESSDELAYMS<millis()){
txbuffer[7]= 0x08;
txbuffer[9]= 0x02;
statemachine = WAITING;
}
break;
case WAITING:
break;
}
return;
}
else if(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_P(txbuffer, ResponseTemplate_Fcn17_Cmd04_L02, sizeof(ResponseTemplate_Fcn17_Cmd04_L02));
txbuffer[0] = rxbuffer[0];
txbuffer[3] = counter;
txbuffer[5] = cmd;
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};
static void 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 = rxbuffer[11];
unsigned char cmd = rxbuffer[12];
memcpy_P(txbuffer, ResponseTemplate_Fcn17_Cmd02_L05, sizeof(ResponseTemplate_Fcn17_Cmd02_L05));
txbuffer[0] = rxbuffer[0];
txbuffer[3] = counter;
txbuffer[5] = cmd;
txlen = sizeof(ResponseTemplate_Fcn17_Cmd02_L05);
//Log(LL_INFO,"Busscan received");
}
static void 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 hasStateChanged = false;
CHECKCHANGEDSET(state.lampOn,rxbuffer[20] == 0x14,hasStateChanged);
CHECKCHANGEDSET(state.doorCurrentPosition,rxbuffer[10],hasStateChanged);
CHECKCHANGEDSET(state.doorTargetPosition, rxbuffer[9],hasStateChanged);
CHECKCHANGEDSET(state.doorState, rxbuffer[11],hasStateChanged);
CHECKCHANGEDSET(state.reserved, rxbuffer[17],hasStateChanged);
CHECKCHANGEDSET(state.valid, true,hasStateChanged);
}
static bool processFrame(){
switch (rxlen)
{
case 0x1b:
case 0x11:
case 0x13:
break;
default:
// unexpected rxlen
return false;
}
txlen=txpos=0;
// check device id, pass only device id 2 and 0 (broadcast)
if(rxbuffer[0] != BROADCASTID && rxbuffer[0] != DEVICEID){
// Frame skipped, unsupported device id
return false;
}
// check crc
uint16_t crc = readCRC(rxbuffer, rxlen);
if(crc != calculateCRC(rxbuffer,rxlen-MODBUS_CRC_LENGTH)){
// Frame skipped, wrong crc
return false;
}
// dispatch modbus function
switch(rxbuffer[1]){
case 0x10:{ // Write Multiple registers
if(rxlen == 0x1b && rxbuffer[0] == BROADCASTID)
{
processBroadcastStatusFrame();
return true;
}
break;
}
case 0x17:{ // Read/Write Multiple registers
if(rxbuffer[0] == DEVICEID){
switch(rxlen){
case 0x11:{
processDeviceStatusFrame();
return true;
}
case 0x13:
processDeviceBusScanFrame();
return true;
}
}
break;
}
}
// Frame skipped, unexpected data
return false;
}
static void processMessage (){
// check frame, process frame
if(statemachine!= WAITING && lastStateTime+2000<millis()){
statemachine = WAITING;
}
if(!processFrame()){
return;
}
rxlen = 0;
// prepare response fix crc
if(txlen > 0){
uint16_t crc = calculateCRC(txbuffer, txlen - MODBUS_CRC_LENGTH);
txbuffer[txlen - MODBUS_CRC_LENGTH] = crc & 0xFF;
txbuffer[(txlen - MODBUS_CRC_LENGTH) + 1] = crc >> 8;
// Enable send
SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
}
}
//--------------------------------------------------------------
static uint16
uart0_receive()
{
uint16 i;
uint16 uart0_rxfifo_len = UART_RXFIFO_LEN(UART0);
if(rxlen>0 && (micros()-recvTime > T3_5)){
// last message timeout, reset buffer
rxlen = 0;
skipFrame = false;
}
// receive data from uart
for (i=0; i<uart0_rxfifo_len; i++) {
if (rxlen < sizeof(rxbuffer)) {
rxbuffer[rxlen++] = UART_RXFIFO_GET(UART0);
}else{
// buffer overflow!!!
skipFrame = true;
UART_RXFIFO_GET(UART0);
}
recvTime = micros();
}
if(!skipFrame && rxlen>0){
processMessage();
}
return i;
}
static uint16
uart0_send()
{
uint16 i;
for (i=UART_TXFIFO_LEN(UART0); i<UART_TXFIFO_SIZE; i++) {
if (txpos==txlen) {
txpos=txlen=0;
CLEAR_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
break;
}
UART_TXFIFO_PUT(UART0, txbuffer[txpos++]);
lastSendTime = micros()-recvTime;
}
return i-1;
}
static void
uart0_intr_handler(void *arg)
{
uint32 uart0_status;
uart0_status = READ_PERI_REG(UART_INT_ST(UART0));
if (uart0_status & UART_RXFIFO_TOUT_INT_ST) {
// No character received for some time, read to ringbuf
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_TOUT_INT_ST);
uart0_receive();
} else if (uart0_status & UART_RXFIFO_FULL_INT_ST) {
// RX buffer becoming full,
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_ST);
uart0_receive();
} else if (uart0_status & UART_TXFIFO_EMPTY_INT_ST) {
// TX buffer empty, check if ringbuf has space or disable int
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_TXFIFO_EMPTY_INT_ST);
uart0_send();
}
}
void ICACHE_FLASH_ATTR
uart0_open(uint32 baud_rate, uint32 flags)
{
uint32 clkdiv;
ETS_UART_INTR_DISABLE();
// Set both RX and TX pins to correct mode
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD);
// Disable pullup on TX pin, enable on RX pin
PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U);
PIN_PULLUP_EN(PERIPHS_IO_MUX_U0RXD_U);
// Configure baud rate for the port
clkdiv = (UART_CLK_FREQ / baud_rate) & UART_CLKDIV_CNT;
WRITE_PERI_REG(UART_CLKDIV(UART0), clkdiv);
// Configure parameters for the port
WRITE_PERI_REG(UART_CONF0(UART0), flags);
// Reset UART0
uart0_reset(baud_rate, flags);
}
void ICACHE_FLASH_ATTR
uart0_reset()
{
// Disable interrupts while resetting UART0
ETS_UART_INTR_DISABLE();
// Clear all RX and TX buffers and flags
SET_PERI_REG_MASK(UART_CONF0(UART0), UART_RXFIFO_RST | UART_TXFIFO_RST);
CLEAR_PERI_REG_MASK(UART_CONF0(UART0), UART_RXFIFO_RST | UART_TXFIFO_RST);
// Set RX and TX interrupt thresholds
WRITE_PERI_REG(UART_CONF1(UART0),
UART_RX_TOUT_EN |
((UART_RXTOUT_TH & UART_RX_TOUT_THRHD) << UART_RX_TOUT_THRHD_S) |
((UART_RXFIFO_TH & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S) |
((UART_TXFIFO_TH & UART_TXFIFO_EMPTY_THRHD) << UART_TXFIFO_EMPTY_THRHD_S));
// Disable all existing interrupts and enable ours
WRITE_PERI_REG(UART_INT_CLR(UART0), 0xffff);
SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_RXFIFO_TOUT_INT_ENA);
SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_RXFIFO_FULL_INT_ENA);
// Restart the interrupt handler for UART0
ETS_UART_INTR_ATTACH(uart0_intr_handler, NULL);
ETS_UART_INTR_ENABLE();
state.valid = false;
}

View File

@ -0,0 +1,113 @@
/* modified/reduced to provide HCP Modbus low level functions
* The MIT License (MIT)
*
* Copyright (c) 2016 Juho Vähä-Herttua (juhovh@iki.fi)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef ESP_UART_H
#define ESP_UART_H
#ifdef __cplusplus
extern "C" {
#endif
#include <c_types.h>
typedef enum {
WAITING,
STARTOPENDOOR,
STARTOPENDOOR_RELEASE,
STARTOPENDOORHALF,
STARTOPENDOORHALF_RELEASE,
STARTCLOSEDOOR,
STARTCLOSEDOOR_RELEASE,
STARTSTOPDOOR,
STARTSTOPDOOR_RELEASE,
STARTTOGGLELAMP,
STARTTOGGLELAMP_RELEASE,
STARTVENTPOSITION,
STARTVENTPOSITION_RELEASE
} EStateMachine;
typedef struct {
int valid;
int lampOn;
uint8_t doorState; // see DoorState
uint8_t doorCurrentPosition;
uint8_t doorTargetPosition;
uint8_t reserved;
} SHCIState;
SHCIState* getHCIState();
unsigned long getMessageAge();
void openDoor();
void openDoorHalf();
void closeDoor();
void stopDoor();
void toggleLamp();
void ventilationPosition();
#define UART_STOP_BITS_ONE 0x10
#define UART_STOP_BITS_ONE_HALF 0x20
#define UART_STOP_BITS_TWO 0x30
#define UART_STOP_BITS_MASK 0x30
#define UART_BITS_FIVE 0x00
#define UART_BITS_SIX 0x04
#define UART_BITS_SEVEN 0x08
#define UART_BITS_EIGHT 0x0C
#define UART_BITS_MASK 0x0C
#define UART_PARITY_NONE 0x00
#define UART_PARITY_EVEN 0x02
#define UART_PARITY_ODD 0x03
#define UART_PARITY_MASK 0x03
#define UART_FLAGS_8N1 (UART_BITS_EIGHT | UART_PARITY_NONE | UART_STOP_BITS_ONE)
#define UART_FLAGS_8E1 (UART_BITS_EIGHT | UART_PARITY_EVEN | UART_STOP_BITS_ONE)
#define UART_STATUS_IDLE 0x00
#define UART_STATUS_RECEIVING 0x01
#define UART_STATUS_OVERFLOW 0x02
void uart0_open(uint32 baud_rate, uint32 flags);
void uart0_reset();
uint16 uart0_available();
uint16 uart0_read_buf(const void *buf, uint16 nbyte, uint16 timeout);
uint16 uart0_write_buf(const void *buf, uint16 nbyte, uint16 timeout);
void uart0_flush();
void uart1_open(uint32 baud_rate, uint32 flags);
void uart1_reset();
uint8 uart1_write_byte(uint8 byte);
#ifdef __cplusplus
}
#endif
#endif

File diff suppressed because one or more lines are too long

169
HCPBridgeISR/src/main.cpp Normal file
View File

@ -0,0 +1,169 @@
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include "AsyncJson.h"
#include "ArduinoJson.h"
#include "hciemulator.h"
#include "index_html.h"
/* create this file and add your wlan credentials
const char* ssid = "MyWLANSID";
const char* password = "MYPASSWORD";
*/
#include "../../../private/credentials.h"
// switch relay sync to the lamp
// e.g. the Wifi Relay Board U4648
#define USERELAY
// use alternative uart pins
//#define SWAPUART
#define RS485 Serial
// Relay Board parameters
#define ESP8266_GPIO2 2 // Blue LED.
#define ESP8266_GPIO4 4 // Relay control.
#define ESP8266_GPIO5 5 // Optocoupler input.
#define LED_PIN ESP8266_GPIO2
// webserver on port 80
AsyncWebServer server(80);
#ifdef USERELAY
// switch GPIO4 und GPIO2 sync to the lamp
void onStatusChanged(SHCIState* state){
//see https://ucexperiment.wordpress.com/2016/12/18/yunshan-esp8266-250v-15a-acdc-network-wifi-relay-module/
//Setting GPIO4 high, causes the relay to close the NO contact with
if(state->valid){
digitalWrite( ESP8266_GPIO4, state->lampOn );
digitalWrite(LED_PIN, state->lampOn);
}else
{
digitalWrite( ESP8266_GPIO4, false );
digitalWrite(LED_PIN, false);
}
}
#else
void onStatusChanged(SHCIState* state){
}
#endif
// toggle lamp to expected state
void switchLamp(bool on){
int lampon = getHCIState()->lampOn;
bool toggle = (on && !lampon) || (!on && lampon);
if(toggle){
toggleLamp();
}
}
// setup mcu
void setup(){
//setup modbus
// Hörmann HCP2 based on modbus rtu @57.6kB 8E1
uart0_open(57600,UART_FLAGS_8E1);
//setup wifi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
WiFi.setAutoReconnect(true);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
// setup http server
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response = request->beginResponse_P( 200, "text/html", index_html,sizeof(index_html));
response->addHeader("Content-Encoding","deflate");
request->send(response);
});
server.on("/status", HTTP_GET, [](AsyncWebServerRequest *request){
SHCIState *doorstate = getHCIState();
AsyncResponseStream *response = request->beginResponseStream("application/json");
DynamicJsonDocument root(1024);
root["valid"] = doorstate->valid;
root["doorstate"] = doorstate->doorState;
root["doorposition"] = doorstate->doorCurrentPosition;
root["doortarget"] = doorstate->doorTargetPosition;
root["lamp"] = doorstate->lampOn;
root["debug"] = doorstate->reserved;
root["lastresponse"] = getMessageAge()/1000;
serializeJson(root,*response);
request->send(response);
});
server.on("/command", HTTP_GET, [] (AsyncWebServerRequest *request) {
if (request->hasParam("action")) {
int actionid = request->getParam("action")->value().toInt();
switch (actionid){
case 0:
closeDoor();
break;
case 1:
openDoor();
break;
case 2:
stopDoor();
break;
case 3:
ventilationPosition();
break;
case 4:
openDoorHalf();
break;
case 5:
toggleLamp();
break;
default:
break;
}
}
request->send(200, "text/plain", "OK");
});
server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
if (request->hasParam("channel") && request->hasParam("state")) {
String channel = request->getParam("channel")->value();
String state = request->getParam("state")->value();
if(channel.equals("door")){
if(state=="1"){
openDoor();
}else{
closeDoor();
}
}
if(channel.equals("light")){
switchLamp(state=="1");
}
}
request->send(200, "text/plain", "OK");
});
server.begin();
//setup relay board
#ifdef USERELAY
pinMode( ESP8266_GPIO4, OUTPUT ); // Relay control pin.
pinMode( ESP8266_GPIO5, INPUT_PULLUP ); // Input pin.
pinMode( LED_PIN, OUTPUT ); // ESP8266 module blue L
digitalWrite( ESP8266_GPIO4, 0 );
digitalWrite(LED_PIN,0);
#endif
}
// mainloop
bool isLampOn = false;
void loop(){
bool newisLampOn = getHCIState()->valid && getHCIState()->lampOn;
if(isLampOn!=newisLampOn){
onStatusChanged(getHCIState());
isLampOn = newisLampOn;
}
}

View File

@ -0,0 +1,128 @@
//Generated at 2012-07-03 18:44:06
/*
* Copyright (c) 2010 - 2011 Espressif System
*
*/
#ifndef UART_REGISTER_H_INCLUDED
#define UART_REGISTER_H_INCLUDED
#define REG_UART_BASE( i ) (0x60000000+(i)*0xf00)
//version value:32'h062000
#define UART_FIFO( i ) (REG_UART_BASE( i ) + 0x0)
#define UART_RXFIFO_RD_BYTE 0x000000FF
#define UART_RXFIFO_RD_BYTE_S 0
#define UART_INT_RAW( i ) (REG_UART_BASE( i ) + 0x4)
#define UART_RXFIFO_TOUT_INT_RAW (BIT(8))
#define UART_BRK_DET_INT_RAW (BIT(7))
#define UART_CTS_CHG_INT_RAW (BIT(6))
#define UART_DSR_CHG_INT_RAW (BIT(5))
#define UART_RXFIFO_OVF_INT_RAW (BIT(4))
#define UART_FRM_ERR_INT_RAW (BIT(3))
#define UART_PARITY_ERR_INT_RAW (BIT(2))
#define UART_TXFIFO_EMPTY_INT_RAW (BIT(1))
#define UART_RXFIFO_FULL_INT_RAW (BIT(0))
#define UART_INT_ST( i ) (REG_UART_BASE( i ) + 0x8)
#define UART_RXFIFO_TOUT_INT_ST (BIT(8))
#define UART_BRK_DET_INT_ST (BIT(7))
#define UART_CTS_CHG_INT_ST (BIT(6))
#define UART_DSR_CHG_INT_ST (BIT(5))
#define UART_RXFIFO_OVF_INT_ST (BIT(4))
#define UART_FRM_ERR_INT_ST (BIT(3))
#define UART_PARITY_ERR_INT_ST (BIT(2))
#define UART_TXFIFO_EMPTY_INT_ST (BIT(1))
#define UART_RXFIFO_FULL_INT_ST (BIT(0))
#define UART_INT_ENA( i ) (REG_UART_BASE( i ) + 0xC)
#define UART_RXFIFO_TOUT_INT_ENA (BIT(8))
#define UART_BRK_DET_INT_ENA (BIT(7))
#define UART_CTS_CHG_INT_ENA (BIT(6))
#define UART_DSR_CHG_INT_ENA (BIT(5))
#define UART_RXFIFO_OVF_INT_ENA (BIT(4))
#define UART_FRM_ERR_INT_ENA (BIT(3))
#define UART_PARITY_ERR_INT_ENA (BIT(2))
#define UART_TXFIFO_EMPTY_INT_ENA (BIT(1))
#define UART_RXFIFO_FULL_INT_ENA (BIT(0))
#define UART_INT_CLR( i ) (REG_UART_BASE( i ) + 0x10)
#define UART_RXFIFO_TOUT_INT_CLR (BIT(8))
#define UART_BRK_DET_INT_CLR (BIT(7))
#define UART_CTS_CHG_INT_CLR (BIT(6))
#define UART_DSR_CHG_INT_CLR (BIT(5))
#define UART_RXFIFO_OVF_INT_CLR (BIT(4))
#define UART_FRM_ERR_INT_CLR (BIT(3))
#define UART_PARITY_ERR_INT_CLR (BIT(2))
#define UART_TXFIFO_EMPTY_INT_CLR (BIT(1))
#define UART_RXFIFO_FULL_INT_CLR (BIT(0))
#define UART_CLKDIV( i ) (REG_UART_BASE( i ) + 0x14)
#define UART_CLKDIV_CNT 0x000FFFFF
#define UART_CLKDIV_S 0
#define UART_AUTOBAUD( i ) (REG_UART_BASE( i ) + 0x18)
#define UART_GLITCH_FILT 0x000000FF
#define UART_GLITCH_FILT_S 8
#define UART_AUTOBAUD_EN (BIT(0))
#define UART_STATUS( i ) (REG_UART_BASE( i ) + 0x1C)
#define UART_TXD (BIT(31))
#define UART_RTSN (BIT(30))
#define UART_DTRN (BIT(29))
#define UART_TXFIFO_CNT 0x000000FF
#define UART_TXFIFO_CNT_S 16
#define UART_RXD (BIT(15))
#define UART_CTSN (BIT(14))
#define UART_DSRN (BIT(13))
#define UART_RXFIFO_CNT 0x000000FF
#define UART_RXFIFO_CNT_S 0
#define UART_CONF0( i ) (REG_UART_BASE( i ) + 0x20)
#define UART_TXFIFO_RST (BIT(18))
#define UART_RXFIFO_RST (BIT(17))
#define UART_IRDA_EN (BIT(16))
#define UART_TX_FLOW_EN (BIT(15))
#define UART_LOOPBACK (BIT(14))
#define UART_IRDA_RX_INV (BIT(13))
#define UART_IRDA_TX_INV (BIT(12))
#define UART_IRDA_WCTL (BIT(11))
#define UART_IRDA_TX_EN (BIT(10))
#define UART_IRDA_DPLX (BIT(9))
#define UART_TXD_BRK (BIT(8))
#define UART_SW_DTR (BIT(7))
#define UART_SW_RTS (BIT(6))
#define UART_STOP_BIT_NUM 0x00000003
#define UART_STOP_BIT_NUM_S 4
#define UART_BIT_NUM 0x00000003
#define UART_BIT_NUM_S 2
#define UART_PARITY_EN (BIT(1))
#define UART_PARITY (BIT(0))
#define UART_CONF1( i ) (REG_UART_BASE( i ) + 0x24)
#define UART_RX_TOUT_EN (BIT(31))
#define UART_RX_TOUT_THRHD 0x0000007F
#define UART_RX_TOUT_THRHD_S 24
#define UART_RX_FLOW_EN (BIT(23))
#define UART_RX_FLOW_THRHD 0x0000007F
#define UART_RX_FLOW_THRHD_S 16
#define UART_TXFIFO_EMPTY_THRHD 0x0000007F
#define UART_TXFIFO_EMPTY_THRHD_S 8
#define UART_RXFIFO_FULL_THRHD 0x0000007F
#define UART_RXFIFO_FULL_THRHD_S 0
#define UART_LOWPULSE( i ) (REG_UART_BASE( i ) + 0x28)
#define UART_LOWPULSE_MIN_CNT 0x000FFFFF
#define UART_LOWPULSE_MIN_CNT_S 0
#define UART_HIGHPULSE( i ) (REG_UART_BASE( i ) + 0x2C)
#define UART_HIGHPULSE_MIN_CNT 0x000FFFFF
#define UART_HIGHPULSE_MIN_CNT_S 0
#define UART_PULSE_NUM( i ) (REG_UART_BASE( i ) + 0x30)
#define UART_PULSE_NUM_CNT 0x0003FF
#define UART_PULSE_NUM_CNT_S 0
#define UART_DATE( i ) (REG_UART_BASE( i ) + 0x78)
#define UART_ID( i ) (REG_UART_BASE( i ) + 0x7C)
#endif // UART_REGISTER_H_INCLUDED

View File

@ -0,0 +1 @@
python compress.py

View File

View File

@ -0,0 +1,35 @@
#pip install htmlmin
#or python -m pip install htmlmin
#pip install jsmin
#or python -m pip install jsmin
import gzip
import zlib
import htmlmin
from jsmin import jsmin
content = ""
with open('index.html','rt',encoding="utf-8") as f:
content=f.read()
content = htmlmin.minify(content, remove_comments=True, remove_empty_space=True, remove_all_empty_space=True, reduce_empty_attributes=True, reduce_boolean_attributes=False, remove_optional_attribute_quotes=True, convert_charrefs=True, keep_pre=False)
import re
regex = r"<script>(.+?)<\/script>"
content = re.sub(regex, lambda x: "<script>"+jsmin(x.group(1))+"</script>" ,content, 0, re.DOTALL)
result =""
for c in zlib.compress(content.encode("UTF-8"),9):
result= result + ("0x%02X" %c)
if len(result)> 0:
result=result + ","
with open('../index_html.h',"wt") as f:
f.write("const uint8_t index_html[] PROGMEM = {");
f.write(result.strip(","))
f.write("};");

View File

@ -0,0 +1,159 @@
<!DOCTYPE HTML><html>
<head>
<title>Garagentor Steuerung</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<link rel="icon" href="data:,">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 3.0rem;}
p {font-size: 3.0rem;}
body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
.switch {position: relative; display: inline-block; width: 80px; height: 48px}
.switch input {display: none}
.slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 6px}
.slider:before {position: absolute; content: ""; height: 32px; width: 32px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
input:checked+.slider {background-color: #b30000}
input:checked+.slider:before {-webkit-transform: translateX(32px); -ms-transform: translateX(32px); transform: translateX(32px)}
.button {
border: 4px;
border-style: solid;
color: black;
width: 100px;
height: 100px;
text-align: center;
display: inline;
text-decoration: none;
display: inline-block;
font-weight: bold;
font-size: 20px;
margin: 4px 2px;
cursor: pointer;
border-radius: 50px;
}
.buttonred {border-color: #ED5961;}
.buttonyellow {border-color: #fcca00;}
.buttongreen {border-color: #48bd81;}
</style>
</head>
<body>
<h2>Garagentor</h2>
<canvas width="250px" height="250px" id="dc"></canvas>
<h3 id="status">warte auf Verbindung.</h3>
<hr/>
<button class="button buttonred" onclick="doCommand(1)">Auf</button> <button class="button buttonyellow" onclick="doCommand(2)">Stop</button> <button class="button buttongreen" onclick="doCommand(0)">Zu</button>
<hr/>
<h4>Licht</h4><label class="switch"><input type="checkbox" onchange="doCommand(5)" id="light"><span class="slider"></span></label>
<hr/>
<script>
doorpos = 10;
dir = -1;
isconnected = false;
var animation;
function startDoorAnimation() {
if(!animation){
animation = setInterval(Draw, 200);
}
}
function stopDoorAnimation() {
if(animation){
clearInterval(animation);
}
animation = null;
}
function Draw(){
var svg =document.getElementById("svg");
var dc = document.getElementById("dc");
var ctx = dc.getContext("2d");
width = dc.width;
height = dc.height;
ctx.clearRect(0, 0, width, height);
xa= [18,18,10,3,174,347,340,332,332,297,297,52,52,18];
ya= [327,114,117,100,22,100,117,114,327,327,112,112,327,327];
ctx.fillStyle = "black";
ctx.beginPath();
for (i =0;i<xa.length;i++){
if(i==0){
ctx.moveTo((xa[i]/350) * width , (ya[i]/350)*width);
}else{
ctx.lineTo((xa[i]/350) * width, (ya[i]/350)*width);
}
}
ctx.fill();
for(i=0;i<doorpos;i++){
ctx.fillRect((62/350) * width, ((120+21*i)/350) * width, (225/350) * width, (18/350) * width);
}
doorpos+=dir;
if(dir<0 && doorpos<=0){
dir=dir*-1;
}
if(dir>0 &&doorpos>=10){
dir=dir*-1;
}
}
function doCommand(action) {
if(!isconnected){return;}
var xhr = new XMLHttpRequest();
xhr.open("GET", "/command?action="+action, true);
xhr.send();
}
function setDoorState(state){
stopDoorAnimation();
if(state == 0) {doorpos = 10;dir = -1};
if(state == 1) {doorpos = 0; dir = 1};
if(state == 2) {doorpos = 5; dir = 1};
Draw();
if(state == 3) startDoorAnimation();
}
function getStatusText(id){
result = "Das Tor "
switch(id)
{
case 0x20: setDoorState(1); return result+"ist geöffnet.";
case 0x40: setDoorState(0); return result+"ist geschlossen.";
case 0x80: setDoorState(2); return result+"ist teilgeöffnet.";
case 0x00: setDoorState(2); return result+"ist teilgeöffnet.";
case 0x02: setDoorState(3); return result+"schließt.";
case 0x01: setDoorState(3); return result+"öffnet.";
case -1: setDoorState(0); return "keine Verbindung zum Tor.";
default: return "unbekanter Status: " + id;
}
}
function updateData(){
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var status = JSON.parse(this.responseText);
isconnected = status.valid;
document.getElementById("status").innerHTML = getStatusText(status.valid?status.doorstate:-1);
document.getElementById("light").checked = status.lamp & status.valid;
return;
}
isconnected= false;
};
xmlhttp.open("GET", "/status", true);
xmlhttp.send();
}
Draw();
updateData();
setInterval(updateData, 3000);
</script>
</body>
</html>

View File

@ -0,0 +1 @@
python -m http.server

View File

@ -0,0 +1,9 @@
{
"valid" : true,
"doorstate" : 1,
"doorposition" : 0,
"doortarget" : 0,
"lamp" : true,
"debug" : 0,
"lastresponse" : 0
}

11
HCPBridgeISR/test/README Normal file
View File

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html

View File

@ -74,4 +74,4 @@ Zwischen A+ (Red) und B- (Green) ist ein 120 Ohm Widerstand zum terminieren des
- Busscan aussführen (blauer Pfeil auf off und wieder zurück auf on). Der Adapter bekommt erst dann Strom über die 25V Leitung und muss während des Busscans antworten, sonst wird der Strom wieder abgeschaltet. Im Falle eines Fehlers oder wenn der Adapter abgezogen werden soll, einfach die Busscan Prozedur (On/Off) wiederholen.
## Changelog
TODO
24.02.2021: Neue Version via Interrupt und für ESP32 zur Vermeidung von Timing-Problemen