initial commit
This commit is contained in:
commit
10894d2a6e
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
HCPBridge/.vscode
|
5
HCPBridge/.gitignore
vendored
Normal file
5
HCPBridge/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
39
HCPBridge/include/README
Normal file
39
HCPBridge/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
HCPBridge/lib/README
Normal file
46
HCPBridge/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
HCPBridge/platformio.ini
Normal file
17
HCPBridge/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
|
425
HCPBridge/src/hciemulator.cpp
Normal file
425
HCPBridge/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;
|
||||
}
|
130
HCPBridge/src/hciemulator.h
Normal file
130
HCPBridge/src/hciemulator.h
Normal file
@ -0,0 +1,130 @@
|
||||
#ifndef __hciemulator_h
|
||||
#define __hciemulator_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
HCPBridge/src/index_html.h
Normal file
1
HCPBridge/src/index_html.h
Normal file
File diff suppressed because one or more lines are too long
163
HCPBridge/src/main.cpp
Normal file
163
HCPBridge/src/main.cpp
Normal file
@ -0,0 +1,163 @@
|
||||
#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);
|
||||
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
// setup mcu
|
||||
void setup(){
|
||||
|
||||
//setup modbus
|
||||
RS485.begin(57600,SERIAL_8E1);
|
||||
#ifdef SWAPUART
|
||||
RS485.swap();
|
||||
#endif
|
||||
|
||||
//setup wifi
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
WiFi.setAutoReconnect(true);
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
emulator.poll();
|
||||
}
|
||||
|
||||
// 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;
|
||||
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(){
|
||||
emulator.poll();
|
||||
}
|
1
HCPBridge/src/webpage/buildindex.cmd
Normal file
1
HCPBridge/src/webpage/buildindex.cmd
Normal file
@ -0,0 +1 @@
|
||||
python compress.py
|
0
HCPBridge/src/webpage/command
Normal file
0
HCPBridge/src/webpage/command
Normal file
42
HCPBridge/src/webpage/compress.py
Normal file
42
HCPBridge/src/webpage/compress.py
Normal file
@ -0,0 +1,42 @@
|
||||
#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') 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)
|
||||
|
||||
|
||||
#with gzip.open('htmltest.html.gz', 'wb') as f:
|
||||
# f.write(content.encode("UTF-8"))
|
||||
|
||||
#with open('htmltest.html.z','wb') as f:
|
||||
# f.write(zlib.compress(content.encode("UTF-8"),9))
|
||||
|
||||
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
HCPBridge/src/webpage/index.html
Normal file
159
HCPBridge/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
HCPBridge/src/webpage/runtestserver.cmd
Normal file
1
HCPBridge/src/webpage/runtestserver.cmd
Normal file
@ -0,0 +1 @@
|
||||
python -m http.server
|
9
HCPBridge/src/webpage/status
Normal file
9
HCPBridge/src/webpage/status
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"valid" : true,
|
||||
"doorstate" : 1,
|
||||
"doorposition" : 0,
|
||||
"doortarget" : 0,
|
||||
"lamp" : true,
|
||||
"debug" : 0,
|
||||
"lastresponse" : 0
|
||||
}
|
11
HCPBridge/test/README
Normal file
11
HCPBridge/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
|
71
Investigation/ProtocolAnalyse.txt
Normal file
71
Investigation/ProtocolAnalyse.txt
Normal file
@ -0,0 +1,71 @@
|
||||
0011: 02 17 9C B9 00 08 9C 41 00 02 04 3E 03 00 00 EB CC (Lese 9CB9:08), (Sende) (zähler)3E (cmd)03 (Data) 0000
|
||||
0015: 02 17 10 3E 00 03 01 00 00 00 00 00 00 00 00 00 00 00 00 74 1B Default response
|
||||
-----------
|
||||
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18
|
||||
0015: 02 17 10 70 00 03 01 02 10 00 00 00 00 00 00 00 00 00 00 6C 88 <- Tor auf pressed
|
||||
0015: 02 17 10 78 00 03 01 01 10 00 00 00 00 00 00 00 00 00 00 6F 4A <- Tor auf released
|
||||
ORG: 00 00
|
||||
0015: 02 17 10 6C 00 03 01 02 20 00 00 00 00 00 00 00 00 00 00 9B 41 <- Tor zu pressed
|
||||
0015: 02 17 10 78 00 03 01 01 20 00 00 00 00 00 00 00 00 00 00 90 4A <- Tor zu released
|
||||
ORG: 00 00
|
||||
0015: 02 17 10 02 00 03 01 02 40 00 00 00 00 00 00 00 00 00 00 C9 0C <- Stop pressed
|
||||
0015: 02 17 10 05 00 03 01 01 40 00 00 00 00 00 00 00 00 00 00 8F 3A <- Stop released
|
||||
ORG 00 00
|
||||
0015: 02 17 10 48 00 03 01 02 00 40 00 00 00 00 00 00 00 00 00 29 4E <- Lüftunsschalter pressed
|
||||
0015: 02 17 10 4C 00 03 01 01 00 40 00 00 00 00 00 00 00 00 00 2F 89 <- Lüftunsschalter released
|
||||
ORG: 00 00
|
||||
0015: 02 17 10 44 00 03 01 02 00 04 00 00 00 00 00 00 00 00 00 6F 4A <- 1/2 pressed
|
||||
0015: 02 17 10 53 00 03 01 01 00 04 00 00 00 00 00 00 00 00 00 24 B0 <- 1/2 released
|
||||
ORG 00 00
|
||||
0015: 02 17 10 0A 00 03 01 10 00 02 00 00 00 00 00 00 00 00 00 02 F9 <- Lichtschalter pressed
|
||||
0015: 02 17 10 0D 00 03 01 08 00 02 00 00 00 00 00 00 00 00 00 60 EB <- Lichtschalter released
|
||||
ORG: 00 00
|
||||
-----------
|
||||
0011: 02 17 9C B9 00 02 9C 41 00 02 04 0F04 1700 7B 21 <- 0F zähler, 04 cmd? data: 1700 (licht an) oder 1701 (licht aus)
|
||||
0009: 02 17 04 0F00 04FD 0A 72 <- 0F00 zähler, 04FD 04 cmd, FD result?
|
||||
|
||||
|
||||
###################################################################################
|
||||
# Broadcast Message
|
||||
###################################################################################
|
||||
001B: 00 10 9D 31 00 09 12 65 00 00 00 40 60 00 00 00 00 00 00 00 10 00 01 00 00 5A 1D <- Licht startet
|
||||
001B: 00 10 9D 31 00 09 12 66 00 00 00 40 60 00 00 00 00 00 00 00 14 00 01 00 00 5B 99 <- Licht an
|
||||
001B: 00 10 9D 31 00 09 12 3A 00 00 00 40 60 00 00 00 00 00 00 00 04 00 01 00 00 48 18 <- Licht geht aus
|
||||
001B: 00 10 9D 31 00 09 12 3D 00 00 00 40 60 00 00 00 00 00 00 00 00 00 01 00 00 08 AD <- Licht ist aus
|
||||
00
|
||||
------------------
|
||||
|
||||
AA: Zielwert?
|
||||
BB: Istwert
|
||||
CC: 0x40 Tor zu, 0x01 Tor öffnet, 0x20 Tor komplett auf, 0x02 Tor schließt, 0x05 1/2 öffnen, 0x80 Teilgeöffnet, 0x00 gestoppt
|
||||
0x42(66) kurz beim schließen
|
||||
|
||||
|
||||
DD: ? Geschwindigkeit / Kraft?
|
||||
|
||||
AA BB CC DD
|
||||
00 01 02 03 04 05 06 07 08 09 00 01 02 03 04 05 06 07 08 09 00 01 02 03 04 05 06
|
||||
001B: 00 10 9D 31 00 09 12 53 00 00 00 40 60 00 00 00 00 00 00 00 14 00 01 00 00 44 5B <- Tor zu
|
||||
001B: 00 10 9D 31 00 09 12 55 00 C8 00 01 60 00 00 00 00 00 00 00 14 00 01 00 00 B2 05 <- Tor öffnet komplett
|
||||
001B: 00 10 9D 31 00 09 12 5B 00 C8 04 01 60 00 00 00 00 0E 00 00 14 00 01 00 00 55 61
|
||||
001B: 00 10 9D 31 00 09 12 5D 00 C8 04 01 60 00 00 00 00 0E 00 00 14 00 01 00 00 B5 E8
|
||||
001B: 00 10 9D 31 00 09 12 60 00 C8 06 01 60 00 00 00 00 0E 00 00 14 00 01 00 00 AA 49
|
||||
001B: 00 10 9D 31 00 09 12 63 00 C8 08 01 60 00 00 00 00 0E 00 00 14 00 01 00 00 D4 09
|
||||
001B: 00 10 9D 31 00 09 12 65 00 C8 0A 01 60 00 00 00 00 0E 00 00 14 00 01 00 00 B6 81
|
||||
001B: 00 10 9D 31 00 09 12 67 00 C8 0C 01 60 00 00 00 00 0E 00 00 14 00 01 00 00 91 3B
|
||||
001B: 00 10 9D 31 00 09 12 68 00 C8 0E 01 60 00 00 00 00 0E 00 00 14 00 01 00 00 20 2D
|
||||
001B: 00 10 9D 31 00 09 12 6A 00 C8 10 01 60 00 00 00 00 0E 00 00 14 00 01 00 00 1F 9D
|
||||
001B: 00 10 9D 31 00 09 12 6C 00 C8 12 01 60 00 00 00 00 0E 00 00 14 00 01 00 00 7D 15
|
||||
..
|
||||
001B: 00 10 9D 31 00 09 12 2F 00 C8 C6 01 60 00 00 00 00 02 00 00 14 00 01 00 00 4C 8F
|
||||
001B: 00 10 9D 31 00 09 12 30 00 C8 C8 20 60 00 00 00 00 00 00 00 14 00 01 00 00 55 A1 <- Tor komplett auf
|
||||
..
|
||||
001B: 00 10 9D 31 00 09 12 11 00 00 38 02 60 00 00 00 00 09 00 00 14 00 01 00 00 4A 0D <- Tor schließt
|
||||
001B: 00 10 9D 31 00 09 12 4F 00 00 16 02 60 00 00 00 00 06 00 00 14 00 01 00 00 D7 AB
|
||||
001B: 00 10 9D 31 00 09 12 53 00 00 14 02 60 00 00 00 00 06 00 00 14 00 01 00 00 92 3C
|
||||
001B: 00 10 9D 31 00 09 12 0A 00 00 00 02 60 00 00 00 00 03 00 00 14 00 01 00 00 84 83
|
||||
001B: 00 10 9D 31 00 09 12 0B 00 00 00 40 60 00 00 00 00 00 00 00 14 00 01 00 00 D7 28 <- Tor komplett zu
|
||||
|
||||
|
||||
001B: 00 10 9D 31 00 09 12 30 00 C8 00 05 60 00 00 00 00 00 00 00 14 00 01 00 00 B9 12 <- 1/2 Öffnung
|
||||
001B: 00 10 9D 31 00 09 12 59 00 18 18 80 60 00 00 00 00 00 00 00 14 00 01 00 00 B5 41 <- 1/2 Teilgeöffnet
|
5
Investigation/bussniffer/.gitignore
vendored
Normal file
5
Investigation/bussniffer/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
39
Investigation/bussniffer/include/README
Normal file
39
Investigation/bussniffer/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
Investigation/bussniffer/lib/README
Normal file
46
Investigation/bussniffer/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
|
14
Investigation/bussniffer/platformio.ini
Normal file
14
Investigation/bussniffer/platformio.ini
Normal file
@ -0,0 +1,14 @@
|
||||
; 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
|
81
Investigation/bussniffer/src/main.cpp
Normal file
81
Investigation/bussniffer/src/main.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
#include <Arduino.h>
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
|
||||
SoftwareSerial RS485(D6,D7);
|
||||
#define RS485IF RS485
|
||||
//#define RS485IF Serial
|
||||
|
||||
|
||||
void setup()
|
||||
{
|
||||
// Hörmann HCP2 based on modbus rtu @57.6kB 8E1
|
||||
RS485IF.begin(57600,SWSERIAL_8E1);
|
||||
Serial.begin(115200,SERIAL_8N1);
|
||||
}
|
||||
|
||||
#define T3_5 1750
|
||||
|
||||
#define BUFFERSIZE 255
|
||||
int rxlen = 0;
|
||||
int txlen = 0;
|
||||
byte rxbbuffer[BUFFERSIZE];
|
||||
byte txbbuffer[BUFFERSIZE];
|
||||
|
||||
unsigned long recvTime =0;
|
||||
unsigned long recvStartTime =0;
|
||||
unsigned long recvGapTime = 0;
|
||||
|
||||
void sendUShort(ushort data){
|
||||
Serial.write(data>>8);
|
||||
Serial.write(data&0xFF);
|
||||
}
|
||||
|
||||
int loopcount = 0;
|
||||
void loop()
|
||||
{
|
||||
if(loopcount==0)
|
||||
{
|
||||
rxlen=0;
|
||||
rxbbuffer[rxlen++] = 0x8;
|
||||
rxbbuffer[rxlen++] = 0xDE;
|
||||
rxbbuffer[rxlen++] = 0xAD;
|
||||
rxbbuffer[rxlen++] = 0xBE;
|
||||
rxbbuffer[rxlen++] = 0xEF;
|
||||
rxbbuffer[rxlen++] = 0xDE;
|
||||
rxbbuffer[rxlen++] = 0xAD;
|
||||
rxbbuffer[rxlen++] = 0xBE;
|
||||
rxbbuffer[rxlen++] = 0xEF;
|
||||
|
||||
Serial.write(rxbbuffer,rxlen);
|
||||
|
||||
rxlen=0;
|
||||
loopcount=1;
|
||||
}
|
||||
|
||||
if(RS485IF.available() >0)
|
||||
{
|
||||
if(rxlen==0){
|
||||
recvStartTime = micros();
|
||||
recvGapTime = recvStartTime-recvTime;
|
||||
}
|
||||
rxlen+= RS485IF.readBytes((char*)(rxbbuffer+rxlen), _min(BUFFERSIZE-rxlen,RS485IF.available()));
|
||||
recvTime = micros();
|
||||
|
||||
}
|
||||
|
||||
if(rxlen>0 && (micros()-recvTime > T3_5 || rxlen > 0x80))
|
||||
{
|
||||
Serial.write(rxlen); // Len
|
||||
sendUShort((short)recvGapTime); // Gap
|
||||
sendUShort((short)recvTime-recvStartTime); // Packagelen
|
||||
Serial.write(rxbbuffer,rxlen);
|
||||
rxlen=0;
|
||||
}
|
||||
|
||||
if(Serial.available() >0)
|
||||
{
|
||||
rxlen= Serial.readBytes((char*)(rxbbuffer), _min(BUFFERSIZE,Serial.available()));
|
||||
RS485IF.write(rxbbuffer,rxlen);
|
||||
}
|
||||
}
|
11
Investigation/bussniffer/test/README
Normal file
11
Investigation/bussniffer/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
|
BIN
Investigation/oszi/busscan.png
Normal file
BIN
Investigation/oszi/busscan.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
53
Investigation/readme.txt
Normal file
53
Investigation/readme.txt
Normal file
@ -0,0 +1,53 @@
|
||||
Hardware/Protocol:
|
||||
####################
|
||||
RS 485 Modbus 57,6 kB - 8E1 LSB
|
||||
IOLevel 0-3,2 V
|
||||
|
||||
|
||||
Protocol:
|
||||
##########
|
||||
https://modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf
|
||||
|
||||
Request (Master):
|
||||
92 SlaveID
|
||||
17 Function code: Read/Write Multiple registers
|
||||
9C B9 Read Starting Address
|
||||
00 05 Quantity to Read
|
||||
9C 41 Write Starting Address
|
||||
00 03 Quantity to Write
|
||||
06 Write Byte Count
|
||||
00 02 00 00 01 00
|
||||
15 D9 CRC ModBUS
|
||||
|
||||
Response (Slave)
|
||||
92 SlaveID
|
||||
17 Function code
|
||||
05 Byte Count
|
||||
XX XX XX XX XX
|
||||
YY YY CRC ModBus
|
||||
|
||||
|
||||
Antwortzeitverhalten Master<> Slave:
|
||||
min 3974 ms
|
||||
max 5916 ms
|
||||
|
||||
|
||||
Kommunikation (siehe ProtocolAnalyse.txt):
|
||||
nach dem BUS-Scan wird folgendes Muster immer wieder durchlaufen:
|
||||
|
||||
Master: Statusabfrage
|
||||
02 17 9C B9 00 08 9C 41 00 02 04 3E 03 00 00 EB CC
|
||||
Slave: Status des Erweiterungsboards
|
||||
02 17 10 3E 00 03 01 00 00 00 00 00 00 00 00 00 00 00 00 74 1B
|
||||
Master (Broadcast): Status des Antriebs
|
||||
00 10 9D 31 00 09 12 16 00 C8 C8 20 60 00 00 00 00 00 00 00 14 00 01 00 00 BE E2
|
||||
|
||||
|
||||
Besondere Nachrichten:
|
||||
1. Beim Lichtschalten:
|
||||
Master: 02 17 9C B9 00 02 9C 41 00 02 04 0F 04 17 00 7B 21 <- 0F zähler, 04 cmd? data: 1700 (licht an) oder 1701 (licht aus)
|
||||
Slave: 02 17 04 0F00 04FD 0A 72 <- 0F00 zähler, 04FD 04 cmd, FD result? Konstante Nachricht?
|
||||
|
||||
2. Busscan
|
||||
Master: 02 17 9C B9 00 05 9C 41 00 03 06 00 02 00 00 01 02 f8 35
|
||||
Slave: 02 17 0a 00 00 02 05 04 30 10 ff a8 45 0e df
|
Loading…
Reference in New Issue
Block a user