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:
parent
3906d73c42
commit
e13f6e5274
@ -1,3 +0,0 @@
|
||||
from distutils import dir_util
|
||||
Import("env")
|
||||
dir_util.copy_tree("patch",".")
|
@ -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;
|
||||
}
|
@ -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
|
||||
|
@ -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
5
HCPBridgeESP32/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
7
HCPBridgeESP32/.vscode/extensions.json
vendored
Normal file
7
HCPBridgeESP32/.vscode/extensions.json
vendored
Normal 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
12
HCPBridgeESP32/.vscode/settings.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
39
HCPBridgeESP32/include/README
Normal file
39
HCPBridgeESP32/include/README
Normal 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
46
HCPBridgeESP32/lib/README
Normal 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
|
18
HCPBridgeESP32/platformio.ini
Normal file
18
HCPBridgeESP32/platformio.ini
Normal 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
|
425
HCPBridgeESP32/src/hciemulator.cpp
Normal file
425
HCPBridgeESP32/src/hciemulator.cpp
Normal 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;
|
||||
}
|
131
HCPBridgeESP32/src/hciemulator.h
Normal file
131
HCPBridgeESP32/src/hciemulator.h
Normal 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
|
1
HCPBridgeESP32/src/index_html.h
Normal file
1
HCPBridgeESP32/src/index_html.h
Normal file
File diff suppressed because one or more lines are too long
200
HCPBridgeESP32/src/main.cpp
Normal file
200
HCPBridgeESP32/src/main.cpp
Normal 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(){
|
||||
}
|
1
HCPBridgeESP32/src/webpage/buildindex.cmd
Normal file
1
HCPBridgeESP32/src/webpage/buildindex.cmd
Normal file
@ -0,0 +1 @@
|
||||
python compress.py
|
0
HCPBridgeESP32/src/webpage/command
Normal file
0
HCPBridgeESP32/src/webpage/command
Normal file
35
HCPBridgeESP32/src/webpage/compress.py
Normal file
35
HCPBridgeESP32/src/webpage/compress.py
Normal 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("};");
|
159
HCPBridgeESP32/src/webpage/index.html
Normal file
159
HCPBridgeESP32/src/webpage/index.html
Normal 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>
|
1
HCPBridgeESP32/src/webpage/runtestserver.cmd
Normal file
1
HCPBridgeESP32/src/webpage/runtestserver.cmd
Normal file
@ -0,0 +1 @@
|
||||
python -m http.server
|
9
HCPBridgeESP32/src/webpage/status
Normal file
9
HCPBridgeESP32/src/webpage/status
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"valid" : true,
|
||||
"doorstate" : 1,
|
||||
"doorposition" : 0,
|
||||
"doortarget" : 0,
|
||||
"lamp" : true,
|
||||
"debug" : 0,
|
||||
"lastresponse" : 0
|
||||
}
|
11
HCPBridgeESP32/test/README
Normal file
11
HCPBridgeESP32/test/README
Normal 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
5
HCPBridgeISR/.gitignore
vendored
Normal 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
7
HCPBridgeISR/.vscode/extensions.json
vendored
Normal 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
13
HCPBridgeISR/.vscode/settings.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
39
HCPBridgeISR/include/README
Normal file
39
HCPBridgeISR/include/README
Normal 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
46
HCPBridgeISR/lib/README
Normal 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
|
17
HCPBridgeISR/platformio.ini
Normal file
17
HCPBridgeISR/platformio.ini
Normal 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
|
18
HCPBridgeISR/simulator/hcisim.py
Normal file
18
HCPBridgeISR/simulator/hcisim.py
Normal 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
21
HCPBridgeISR/src/LICENSE
Normal 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.
|
10
HCPBridgeISR/src/README.md
Normal file
10
HCPBridgeISR/src/README.md
Normal 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
44
HCPBridgeISR/src/crc.c
Normal 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
22
HCPBridgeISR/src/crc.h
Normal 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
|
511
HCPBridgeISR/src/hciemulator.c
Normal file
511
HCPBridgeISR/src/hciemulator.c
Normal 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;
|
||||
}
|
113
HCPBridgeISR/src/hciemulator.h
Normal file
113
HCPBridgeISR/src/hciemulator.h
Normal 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
|
1
HCPBridgeISR/src/index_html.h
Normal file
1
HCPBridgeISR/src/index_html.h
Normal file
File diff suppressed because one or more lines are too long
169
HCPBridgeISR/src/main.cpp
Normal file
169
HCPBridgeISR/src/main.cpp
Normal 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;
|
||||
}
|
||||
}
|
128
HCPBridgeISR/src/uart_register.h
Normal file
128
HCPBridgeISR/src/uart_register.h
Normal 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
|
1
HCPBridgeISR/src/webpage/buildindex.cmd
Normal file
1
HCPBridgeISR/src/webpage/buildindex.cmd
Normal file
@ -0,0 +1 @@
|
||||
python compress.py
|
0
HCPBridgeISR/src/webpage/command
Normal file
0
HCPBridgeISR/src/webpage/command
Normal file
35
HCPBridgeISR/src/webpage/compress.py
Normal file
35
HCPBridgeISR/src/webpage/compress.py
Normal 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("};");
|
159
HCPBridgeISR/src/webpage/index.html
Normal file
159
HCPBridgeISR/src/webpage/index.html
Normal 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>
|
1
HCPBridgeISR/src/webpage/runtestserver.cmd
Normal file
1
HCPBridgeISR/src/webpage/runtestserver.cmd
Normal file
@ -0,0 +1 @@
|
||||
python -m http.server
|
9
HCPBridgeISR/src/webpage/status
Normal file
9
HCPBridgeISR/src/webpage/status
Normal 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
11
HCPBridgeISR/test/README
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user