This binding for openHAB has ability to connect directly DIY devices (based on Arduino or whatever else). Binding uses serial communication or network communication over implemented TCP server.
Used protocol is easy to implement. Implementation examples for Arduino over serial, STM8, ESP8266 over TCP is part of repository. Arduino IDE library is also available.
Compiled binding is inserted into release branch: https://github.com/docbender/openHAB-SimpleBinary/releases
Binding is working with openHAB 3.2.
It is possible to configure several ports not only one. At one line it is possible to connect several devices (ready for RS422/RS485).
Binding sets the serial port speed according to user configuration. Other parameters are set as follows: 8 data bits, 1 stop bit, without parity and without flow control.
Flow control (especially for RS-485 converters) can be provided through RTS pin. This can be turned on by adding forceRTS parameter in binding port configuration. If it is required inverted function of RTS pin, forceRTSInv parameter should be added in configuration instead forceRTS.
Communication can operate in 2 data read modes - OnScan and OnChange.
At binding startup TCP server start listening on user defined port. In basic 256 clients are supported. Every connected client gets actual items states after device ID verification.
Known limitations: max.256 clients(protocol limitation).
Communication can operate it 2 modes reading data from connected devices - OnScan and OnChange. In OnScan mode all data are reading cyclically. In OnChange mode only new data are sent to openHAB. Each device is polled whether has new data and then sends new data to openHAB. One of his modes must be choose in serial port connection. TCP server connection not use any of these modes as default. Because TCP connection is full-duplex, spontaneous states send is preferred and expected.
Copy binding release in ${OPENHAB_HOME}/addons folder. Binding depends on openhab-transport-serial. It must be installed on target openHAB system. Installation can be done in console by running command:
feature:install openhab-transport-serial
A SimpleBinary Bridge is needed to connect to device. It provides communication channel to all device connected to concreate UART port or TCP server.
Basic bridge configuration requires to know serial port name and communication speed. Additional parameters can be defined.
Parameter | Required | Notes |
port | Yes | UART port name (COM1 or /dev/ttyUSB1) |
baudRate | Yes | Communication speed [bits/s]. |
pollControl | No | Communication mode. OnChange(default) or OnScan. |
pollRate | No | Read period [ms]. Default is 1000ms. |
forceRTS | No | Communication port force RTS pin activation. Since OH3.2 |
invertedRTS | No | Invert RTS pin state. Since OH3.2 |
charset | No | Define code page for communicated strings (e.g. ISO-8859-1, cp1250). If blank or wrong code page is defined, system code page is used. Used code page is printed into log file as INFO. |
timeout | No | Time to wait for answer [ms]. |
degradeMaxFailuresCount | No | Number of failures that lead to off-scan of the device. Zero means scan every period. |
degradeTime | No | Determine time in off-scan before next connection attempt. |
discardCommand | No | If is set to true, commands for offline device will be discarded. |
syncCommand | No | If is set to true, previously published commands will be resent to the device. |
Bridge definition together with things can be defined in text files. See generic bridge configuration for details. In short text files .things are located in ${OPENHAB_CONF}/things folder. Basic bridge configuration with required parameters looks like this:
Bridge simplebinary:uart_bridge:<device_id> "Label" @ "Location" [ port="<port ID>", baudRate="<baud rate>" ]
Example with optional parameter:
Bridge simplebinary:uart_bridge:device1 "My Device" [ port="/dev/ttyUSB1", baudRate="9600", charset="ISO-8859-1" ]
Basic bridge is very simple. No parameters are required.
Parameter | Required | Notes |
address | No | IP address to listen. Empty or 0.0.0.0 means listen for client from everywhere. |
port | No | TCP port number. Default 43243. |
charset | No | Define code page for communicated strings (e.g. ISO-8859-1, cp1250). If blank or wrong code page is defined, system code page is used. Used code page is printed into log file as INFO. |
timeout | No | Time to wait for answer [ms]. |
degradeMaxFailuresCount | No | Number of failures that lead to off-scan of the device. Zero means scan every period. |
degradeTime | No | Determine time in off-scan before next connection attempt. |
discardCommand | No | If is set to true, commands for offline device will be discarded. |
syncCommand | No | If is set to true, previously published commands will be resent to the device. |
Bridge definition together with things can be defined in text files. See generic bridge configuration for details. In short text files .things are located in ${OPENHAB_CONF}/things folder. Basic bridge configuration with required parameters looks like this:
Bridge simplebinary:tcp_bridge:<device_id> "Label" @ "Location"
Example with optional parameter:
Bridge simplebinary:tcp_bridge:device1 "My Device" [ charset="ISO-8859-1" ]
To any individual bridge things could be added. Things are user defined so only generic_device thing is available.
Things definition can be defined in text files. Easiest way is to put it inside bridge definition:
Bridge simplebinary:<bridge>:<device_id> "Label" @ "Location" [ port="<port ID>", baudRate="<baud rate>" ]
Thing generic_device <thing_id> "Thing label" {
}
}
For generic_device thing binding supports channels types listed in the table below. Individual channel type are converted to simple data types such as bit, byte, word, double word, float, array.
Channel type | Data type | Address example | Notes |
chNumber | byte, word, dword, float | 1:1:byte, 1:1:word, 1:1:dword, 1:1:float | Numbers are represent in signed form. Their maximum value depends on the used type. Floating point number is stored in 4 bytes formatted according to IEEE 754. |
chColor | hsb, rgb, rgbw | 1:1:rgb | All color parts are transferred in one double word. Each color component corresponds to one byte. Bytes assignment: RGB - byte 0 - Red, 1 - Green, 2 - Blue, 3 - Not used |
chString | byte array | 1:1:32 | Array of bytes represent null terminated string. Length of array is specified behind address definition. |
chContact | byte | 1:1 | 0 - off, 1 - on |
chSwitch | byte | 1:1 | 0 - off, 1 - on |
chDimmer | byte | 1:1 | Value range is 0-100 |
chRollershutter | word | 1:1 | State specifies position (0-100%). Command sends Move/Stop/Up/Down (1-Move, 2-Stop, 4-Up, 8-Down) in second byte or eventually target position (0-100%) in first byte. |
Every channel has two parameters stateAddress and commandAddress. At least one must have a defined value.
Channel text file definition as part of thing:
Bridge simplebinary:<bridge>:<device_id> "Label" @ "Location" [ port="<port ID>", baudRate="<baud rate>" ] {
Thing generic_device <thing_id> "Thing label" {
Channels:
Type <channel_type> : <channel_id> [ stateAddress="<address>", commandAddress="<address>" ]
}
}
Bridge simplebinary:uart_bridge:Device1 "My Device" [ port="/dev/ttyUSB1", baudRate="9600", charset="ISO-8859-1" ] {
Thing generic_device devState "Device state" {
Channels:
Type chNumber: watchdog1 [ stateAddress="1:0:byte" ]
Type chNumber: watchdog2 [ stateAddress="2:0:byte" ]
Type chNumber: watchdog3 [ stateAddress="3:0:byte" ]
}
Thing generic_device weather "Weather station" {
Channels:
Type chNumber: temperature [ stateAddress="1:1:float" ]
Type chNumber: pressure [ stateAddress="1:2:word" ]
Type chNumber: humidity [ stateAddress="1:3:word" ]
}
Thing generic_device hall "Hall" {
Channels:
Type chContact: door_contact [ stateAddress="2:4" ]
Type chSwitch: light_switch [ stateAddress="2:5", commandAddress="2:5" ]
Type chNumber: humidity [ stateAddress="2:6:word" ]
}
Thing generic_device device "Special device" {
Channels:
Type chSwitch: run [ stateAddress="3:1", commandAddress="3:2" ]
Type chDimmer: rate [ stateAddress="3:3", commandAddress="3:4" ]
Type chString: text [ stateAddress="3:5:20", commandAddress="3:5:20" ]
}
}
Bridge simplebinary:tcp_bridge:Device2 "My second Device" {
Thing generic_device rollers "Rollershutters" {
Channels:
Type chRollershutter: rs1 [ stateAddress="1:0", commandAddress="1:1" ]
Type chRollershutter: rs2 [ stateAddress="1:2", commandAddress="1:3" ]
}
}
Binding implements master/slave communication model for serial communication. OpenHAB binding is master and connected devices are slaves. Master sends command and waits for response from slave. Answer should arrived in order tens of milliseconds. However bindings has 2000ms timeout to receive answer. Communication protocol itself depends on requested operating mode of the device (OnScan / OnChange). Of course device can support both operating modes.
Network TCP connection use same protocol as serial communication but in producer/consumer communication model. When client is connected, "Hi" message must be send. Then server can verify and register device ID.
Every packet start with device address and is followed by message type. Minimum packet length is 4 bytes. Packets are secured with CRC8 which should be enough for short packets.
On this packet master expecting as answer "data" packet or "no data" packet (message type 0xE2).
Byte | Value | Description |
0 | 0xXX | Device slave address |
1 | 0xD0 | Message type |
2 | 0xXX | Control byte. Supported values: 0 - standard (data check only) 1 - force all data request |
3 | CRC8 |
On this packet master expecting as answer "data" packet or "invalid address" packet (message type 0xE4).
Byte | Value | Description |
0 | 0xXX | Device slave address |
1 | 0xD1 | Message type |
2 | 0xXX | Data address - low byte |
3 | 0xXX | Data address - high byte |
4 | CRC8 |
This "data" packet could be send by master to write data into slave or by slave as answer for data request. Answer from the slave should be "done" when everything is right.
Byte | Value | Description |
0 | 0xXX | Device slave address |
1 | 0xDA | Message type |
2 | 0xXX | Data address - low byte |
3 | 0xXX | Data address - high byte |
4 | 0xXX | Data |
5 | CRC8 |
This "data" packet could be send by master to write data into slave or by slave as answer for data request. Answer from the slave should be "done" when everything is right.
Byte | Value | Description |
0 | 0xXX | Device slave address |
1 | 0xDB | Message type |
2 | 0xXX | Data address - low byte |
3 | 0xXX | Data address - high byte |
4 | 0xXX | Data - low byte |
5 | 0xXX | Data - high byte |
6 | CRC8 |
This "data" packet could be send by master to write data into slave or by slave as answer for data request. Answer from the slave should be "done" when everything is right.
Byte | Value | Description |
0 | 0xXX | Device slave address |
1 | 0xDC | Message type |
2 | 0xXX | Data address - low byte |
3 | 0xXX | Data address - high byte |
4 | 0xXX | Data - 1. byte (lowest) |
5 | 0xXX | Data - 2. byte |
6 | 0xXX | Data - 3. byte |
7 | 0xXX | Data - 4. byte (highest) |
8 | CRC8 |
This "data" packet could be send by master to write data into slave or by slave as answer for data request. Answer from the slave should be "done" when everything is right.
Byte | Value | Description |
0 | 0xXX | Device slave address |
1 | 0xDD | Message type |
2 | 0xXX | Data address - low byte |
3 | 0xXX | Data address - high byte |
4 | 0xXX | Data - 1. byte (lowest) |
5 | 0xXX | Data - 2. byte |
6 | 0xXX | Data - 3. byte |
7 | 0xXX | Data - 4. byte (highest) |
8 | CRC8 |
This "data" packet could be send by master to write data into slave or by slave as answer for data request. Answer from the slave should be "done" when everything is right.
Byte | Value | Description |
0 | 0xXX | Device slave address |
1 | 0xDE | Message type |
2 | 0xXX | Data address - low byte |
3 | 0xXX | Data address - high byte |
4 | 0xXX | Data length - low byte |
5 | 0xXX | Data length - high byte |
6 | 0xXX | Data |
... | 0xXX | Data |
n-1 | CRC8 |
Answer from slave that data write from master was accepted.
Byte | Value | Description |
0 | 0xXX | Device slave address |
1 | 0xE0 | Message type |
2 | 0x00 | |
3 | CRC8 |
Answer that received packet had wrong CRC. On this packet master react by sending last packet again (max. 3 time).
Byte | Value | Description |
0 | 0xXX | Device slave address |
1 | 0xE1 | Message type |
2 | 0xXX | Here is expected CRC8 calculated by slave (master logged it for comparison). |
3 | CRC8 |
Slave react with this packet on query for new data in OnChange mode in case that there's no data to send.
Byte | Value | Description |
0 | 0xXX | Device slave address |
1 | 0xE2 | Message type |
2 | 0x00 | |
3 | CRC8 |
Slave react with this packet if received message had unknown/unsupported type.
Byte | Value | Description |
0 | 0xXX | Device slave address |
1 | 0xE3 | Message type |
2 | 0x00 | |
3 | CRC8 |
Slave react with this packet if received message had unknown data item address.
Byte | Value | Description |
0 | 0xXX | Device slave address |
1 | 0xE4 | Message type |
2 | 0x00 | |
3 | CRC8 |
Slave send this packet when he could not save received data from master.
Byte | Value | Description |
0 | 0xXX | Device slave address |
1 | 0xE5 | Message type |
2 | 0x00 | |
3 | CRC8 |
This packet is for connection model producer/consumer (TCP client/server connection). Client sends this packet after he is connected to server.
Byte | Value | Description |
0 | 0xXX | Device address |
1 | 0xE6 | Message type |
2 | 0x00 | |
3 | CRC8 |
This packet is for connection model producer/consumer (TCP client/server connection). Server sends this packet as Hi packet response.
Byte | Value | Description |
0 | 0xXX | Device address |
1 | 0xD2 | Message type |
2 | 0xXX | Assigned address |
3 | CRC8 |
This packet is for connection model producer/consumer (TCP client/server connection). Server sends this packet as Hi packet response.
Byte | Value | Description |
0 | 0xXX | Device address |
1 | 0xD3 | Message type |
2 | 0x00 | |
3 | CRC8 |
This packet is sent by device to tell that he want's all his data. In master/slave configuration it could be send as response for new data request (0xD0). Note: all data are normally sent when device is connected.
Byte | Value | Description |
0 | 0xXX | Device address |
1 | 0xE7 | Message type |
2 | 0x00 | |
3 | CRC8 |
The extent of implementation depends on the required features and data types (see chapter Protocol).
At first device should check if message is correctly received (CRC check). Secondly device should check if message is for him by compare message address and his own assigned address. If the address is different device must not respond! Otherwise device must response in corresponding way (see chapter Protocol).
Implementation example for Arduino can be found in Arduino library repo folder.
It consists of two main classes. Class simpleBinary which contains protocol implementation itself and class itemData which provides item data storage and handling with item.
Class simpleBinary - public methods
simpleBinary(int uartAddress, int size) Constructor. Parameters: uartAddress - device address at communication line, size - number of exchanged items | |
void | initItem(int indexAndAddress, itemType type, void (*pFce)(itemData*)) Initilize item. Parameters: indexAndAddress - item index in configuration array and also item address used during communication, type - item data type, last parameter is pointer to function that is executed on data receive(NULL for no action). |
void | processSerial() Process data received by UART. |
bool | checkAddress(int address) Check if address exist in items array. Parameters: address - item address used during communication |
void | readData(int address) Read data on given address and send it to master. Parameters: address - item address used during communication |
bool | saveByte(int address, char* data) Save byte from given data pointer to addresed item. Return false if target is not expected data type. Parameters: address - item address used during communication, data - pointer to data to save. |
bool | saveWord(int address, char* data) Save word from given data pointer to addresed item. Return false if target is not expected data type. Parameters: address - item address used during communication, data - pointer to data to save. |
bool | saveDword(int address, char* data) Save double word from given data pointer to addresed item. Return false if target is not expected data type. Parameters: address - item address used during communication, data - pointer to data to save. |
bool | saveArray(int address, char *pData, int len) Save byte array from given data pointer to addresed item. Return false if target is not expected data type. Parameters: address - item address used during communication, pData - pointer to data to save, len - array length (byte count) |
int | size() Return items count. |
void | enableRTS(int pinNumber) Enable RTS handling. RTS pin is passed as parameter. |
void | setSendDelay(unsigned int delayms) Sets delay between receive and transmit message (in ms). Good for communication line stabilization. |
Class itemData - public methods
itemData() Constructor. | |
itemData(int address, itemType type, void (*pFce)(itemData*) ) Constructor with item initialization . Parameters: address - item address used during communication, type - item data type, last parameter is pointer to function that is executed on data receive(NULL for no action). | |
itemData(int address, itemType type, int size, void (*pFce)(itemData*) ) Constructor with item initialization for arrays. Parameters: address - item address used during communication, type - item data type, size - data size in bytes, last parameter is pointer to function that is executed on data receive(NULL for no action). | |
void | init(int address, itemType type, void (*pFce)(itemData*)) Item initialization . Parameters: address - item address used during communication, type - item data type, last parameter is pointer to function that is executed on data receive(NULL for no action). |
bool | saveByte(char* data) Save byte from given data pointer. Return false if target is not expected data type. Parameters: data - pointer to data to save. |
bool | saveWord(char* data) Save word from given data pointer. Return false if target is not expected data type. Parameters: data - pointer to data to save. |
bool | saveDword(char* data) Save double word from given data pointer. Return false if target is not expected data type. Parameters: data - pointer to data to save/td> |
bool | saveArray(char *pData, int len) Save array from given data pointer. Return false if target is not expected data type. Parameters: data - pointer to data to save, len - array length (byte count) |
void | save(int value) Save int value. Parameters: value - value to save |
void | save(float value) Save float value. Parameters: value - value to save |
int | getAddress() Return item address. |
void | setNewData() Set item "new data" flag. |
bool | hasNewData() Check if item has "new data" flag set. |
char* | getData() Return item data. |
char* | readNewData() Return item data and reset "new data" flag. |
itemType | getType() Return item data type. |
int | getDataLength() Return length of data. |
void | executeAction() Execute action connected at item initialization. |
char CRC8::evalCRC(char *data, int length)
{
int crc = 0;
int i, j;
for(j=0; j < length; j++)
{
crc ^= (data[j] << 8);
for(i=0;i<8;i++)
{
if((crc & 0x8000) != 0)
crc ^= (0x1070 << 3);
crc <<= 1;
}
}
return (char)(crc >> 8);
}