added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,292 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* ----------------------------------------------------------------------------
*
* Author: pauli.anttila@gmail.com
*
*
* 2.11.2013 v1.00 Initial version.
* 3.11.2013 v1.01
* 27.6.2014 v1.02 Fixed compile error and added Ethernet initialization delay.
* 29.6.2015 v2.00 Bidirectional support.
* 18.2.2017 v3.00 Redesigned.
*/
// ######### CONFIGURATION #######################
#define VERSION "3.00"
// Enable if you use ProDiNo board
//#define PRODINO_BOARD
// Enable if ENC28J60 LAN module is used
//#define TRANSPORT_ETH_ENC28J60
// Enable debug printouts, listen printouts e.g. via netcat (nc -l -u 50000)
//#define ENABLE_DEBUG
#define VERBOSE_LEVEL 3
#define BOARD_NAME "Arduino NibeGW"
#define BOARD_MAC { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }
#define BOARD_IP { 192, 168, 1, 50 }
#define GATEWAY_IP { 192, 168, 1, 1 }
#define NETWORK_MASK { 255, 255, 255, 0 }
#define INCOMING_PORT_READCMDS TARGET_PORT
#define INCOMING_PORT_WRITECMDS 10000
#define TARGET_IP 192, 168, 1, 19
#define TARGET_PORT 9999
#define TARGER_DEBUG_PORT 50000
// Delay before initialize ethernet on startup in seconds
#define ETH_INIT_DELAY 10
// Used serial port and direction change pin for RS-485 port
#ifdef PRODINO_BOARD
#define RS485_PORT Serial1
#define RS485_DIRECTION_PIN 3
#else
#define RS485_PORT Serial
#define RS485_DIRECTION_PIN 2
#endif
#define ACK_MODBUS40 true
#define ACK_SMS40 false
#define ACK_RMU40 false
#define SEND_ACK true
#define DEBUG_BUFFER_SIZE 80
// ######### INCLUDES #######################
#ifdef TRANSPORT_ETH_ENC28J60
#include <UIPEthernet.h>
#else
#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#endif
#ifdef PRODINO_BOARD
#include "KmpDinoEthernet.h"
#include "KMPCommon.h"
#endif
#include <avr/wdt.h>
#include "NibeGw.h"
// ######### VARIABLES #######################
// The media access control (ethernet hardware) address for the shield
byte mac[] = BOARD_MAC;
//The IP address for the shield
byte ip[] = BOARD_IP;
//The IP address of the gateway
byte gw[] = GATEWAY_IP;
//The network mask
byte mask[] = NETWORK_MASK;
boolean ethernetInitialized = false;
// Target IP address and port where Nibe UDP packets are send
IPAddress targetIp(TARGET_IP);
EthernetUDP udp;
EthernetUDP udp4readCmnds;
EthernetUDP udp4writeCmnds;
NibeGw nibegw(&RS485_PORT, RS485_DIRECTION_PIN);
// ######### DEBUG #######################
#define DEBUG_BUFFER_SIZE 80
#ifdef ENABLE_DEBUG
#define DEBUG_PRINT(level, message) if (verbose >= level) { debugPrint(message); }
#define DEBUG_PRINTDATA(level, message, data) if (verbose >= level) { sprintf(debugBuf, message, data); debugPrint(debugBuf); }
#define DEBUG_PRINTARRAY(level, data, len) if (verbose >= level) { for (int i = 0; i < len; i++) { sprintf(debugBuf, "%02X", data[i]); debugPrint(debugBuf); }}
#else
#define DEBUG_PRINT(level, message)
#define DEBUG_PRINTDATA(level, message, data)
#define DEBUG_PRINTARRAY(level, data, len)
#endif
#ifdef ENABLE_DEBUG
char verbose = VERBOSE_LEVEL;
char debugBuf[DEBUG_BUFFER_SIZE];
void debugPrint(char* data)
{
if (ethernetInitialized)
{
udp.beginPacket(targetIp, TARGER_DEBUG_PORT);
udp.write(data);
udp.endPacket();
}
#ifdef PRODINO_BOARD
Serial.print(data);
#endif
}
#endif
// ######### SETUP #######################
void setup()
{
// Start watchdog
wdt_enable (WDTO_2S);
nibegw.setCallback(nibeCallbackMsgReceived, nibeCallbackTokenReceived);
nibegw.setAckModbus40Address(ACK_MODBUS40);
nibegw.setAckSms40Address(ACK_SMS40);
nibegw.setAckRmu40Address(ACK_RMU40);
nibegw.setSendAcknowledge(SEND_ACK);
#ifdef ENABLE_NIBE_DEBUG
nibegw.setDebugCallback(nibeDebugCallback);
nibegw.setVerboseLevel(VERBOSE_LEVEL);
#endif
#ifdef PRODINO_BOARD
DinoInit();
Serial.begin(115200, SERIAL_8N1);
#endif
DEBUG_PRINTDATA(0, "%s ", BOARD_NAME);
DEBUG_PRINTDATA(0, "version %s\n", VERSION);
DEBUG_PRINT(0, "Started\n");
}
// ######### MAIN LOOP #######################
void loop()
{
wdt_reset();
long now = millis() / 1000;
if (!nibegw.connected())
{
nibegw.connect();
}
else
{
do
{
nibegw.loop();
#ifdef TRANSPORT_ETH_ENC28J60
Ethernet.maintain();
#endif
} while (nibegw.messageStillOnProgress());
}
if (!ethernetInitialized)
{
if (now >= ETH_INIT_DELAY)
{
DEBUG_PRINT(1, "Initializing Ethernet\n");
initializeEthernet();
#ifdef ENABLE_DEBUG
DEBUG_PRINTDATA(0, "%s ", BOARD_NAME);
DEBUG_PRINTDATA(0, "version %s\n", VERSION);
sprintf(debugBuf, "MAC=%02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
DEBUG_PRINT(0, debugBuf);
sprintf(debugBuf, "IP=%d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
DEBUG_PRINT(0, debugBuf);
sprintf(debugBuf, "GW=%d.%d.%d.%d\n", gw[0], gw[1], gw[2], gw[3]);
DEBUG_PRINT(0, debugBuf);
sprintf(debugBuf, "TARGET IP=%d.%d.%d.%d\n", TARGET_IP);
DEBUG_PRINTDATA(0, "TARGET PORT=%d\n", BOARD_NAME);
DEBUG_PRINTDATA(0, "ACK_MODBUS40=%s\n", ACK_MODBUS40 ? "true" : "false");
DEBUG_PRINTDATA(0, "ACK_SMS40=%s\n", ACK_SMS40 ? "true" : "false");
DEBUG_PRINTDATA(0, "ACK_RMU40=%s\n", ACK_RMU40 ? "true" : "false");
DEBUG_PRINTDATA(0, "SEND_ACK=%s\n", SEND_ACK ? "true" : "false");
DEBUG_PRINTDATA(0, "ETH_INIT_DELAY=%d\n", ETH_INIT_DELAY);
DEBUG_PRINTDATA(0, "RS485_DIRECTION_PIN=%d\n", RS485_DIRECTION_PIN);
#endif
}
}
}
// ######### FUNCTIONS #######################
void initializeEthernet()
{
Ethernet.begin(mac, ip, gw, mask);
ethernetInitialized = true;
udp4readCmnds.begin(INCOMING_PORT_READCMDS);
udp4writeCmnds.begin(INCOMING_PORT_WRITECMDS);
}
void nibeCallbackMsgReceived(const byte* const data, int len)
{
if (ethernetInitialized)
{
sendUdpPacket(data, len);
}
}
int nibeCallbackTokenReceived(eTokenType token, byte* data)
{
int len = 0;
if (ethernetInitialized)
{
if (token == READ_TOKEN)
{
DEBUG_PRINT(2, "Read token received\n");
int packetSize = udp4readCmnds.parsePacket();
if (packetSize) {
len = udp4readCmnds.read(data, packetSize);
#ifdef TRANSPORT_ETH_ENC28J60
udp4readCmnds.flush();
udp4readCmnds.stop();
udp4readCmnds.begin(INCOMING_PORT_READCMDS);
#endif
}
}
else if (token == WRITE_TOKEN)
{
DEBUG_PRINT(2, "Write token received\n");
int packetSize = udp4writeCmnds.parsePacket();
if (packetSize) {
len = udp4writeCmnds.read(data, packetSize);
#ifdef TRANSPORT_ETH_ENC28J60
udp4writeCmnds.flush();
udp4writeCmnds.stop();
udp4writeCmnds.begin(INCOMING_PORT_WRITECMDS);
#endif
}
}
}
return len;
}
void nibeDebugCallback(byte verbose, char* data)
{
DEBUG_PRINT(verbose, data);
}
void sendUdpPacket(const byte * const data, int len)
{
DEBUG_PRINTDATA(2, "Sending UDP packet, len=%d\n", len);
DEBUG_PRINTARRAY(2, data, len)
DEBUG_PRINT(2, "\n");
udp.beginPacket(targetIp, TARGET_PORT);
udp.write(data, len);
udp.endPacket();
}

View File

@@ -0,0 +1,380 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* ----------------------------------------------------------------------------
*
* Author: pauli.anttila@gmail.com
*
*/
#include "NibeGw.h"
#include "Arduino.h"
#ifdef HARDWARE_SERIAL
NibeGw::NibeGw(HardwareSerial* serial, int RS485DirectionPin)
#else
NibeGw::NibeGw(Serial_* serial, int RS485DirectionPin)
#endif
{
verbose = 0;
ackModbus40 = true;
ackSms40 = false;
ackRmu40 = false;
sendAcknowledge = true;
state = STATE_WAIT_START;
connectionState = false;
RS485 = serial;
directionPin = RS485DirectionPin;
pinMode(directionPin, OUTPUT);
digitalWrite(directionPin, LOW);
setCallback(NULL, NULL);
}
void NibeGw::connect()
{
if (!connectionState)
{
state = STATE_WAIT_START;
RS485->begin(9600, SERIAL_8N1);
connectionState = true;
}
}
void NibeGw::disconnect()
{
if (connectionState)
{
RS485->end();
connectionState = false;
}
}
boolean NibeGw::connected()
{
return connectionState;
}
void NibeGw::setVerboseLevel(byte level)
{
verbose = level;
}
NibeGw& NibeGw::setCallback(void(*callback_msg_received)(const byte* const data, int len), int(*callback_msg_token_received)(eTokenType token, byte* data))
{
this->callback_msg_received = callback_msg_received;
this->callback_msg_token_received = callback_msg_token_received;
return *this;
}
#ifdef ENABLE_NIBE_DEBUG
NibeGw& NibeGw::setDebugCallback(void(*debug)(byte verbose, char* data))
{
this->debug = debug;
return *this;
}
#endif
void NibeGw::setAckModbus40Address(boolean val)
{
ackModbus40 = val;
}
void NibeGw::setAckSms40Address(boolean val)
{
ackSms40 = val;
}
void NibeGw::setAckRmu40Address(boolean val)
{
ackRmu40 = val;
}
void NibeGw::setSendAcknowledge(boolean val)
{
sendAcknowledge = val;
}
boolean NibeGw::messageStillOnProgress()
{
if (!connectionState)
return false;
if ( RS485->available() > 0)
return true;
if (state == STATE_CRC_FAILURE || state == STATE_OK_MESSAGE_RECEIVED)
return true;
return false;
}
void NibeGw::loop()
{
if (!connectionState)
return;
switch (state)
{
case STATE_WAIT_START:
if (RS485->available() > 0)
{
byte b = RS485->read();
#ifdef ENABLE_NIBE_DEBUG
if (debug)
{
sprintf(debug_buf, "%02X ", b);
debug(3, debug_buf);
}
#endif
if (b == 0x5C)
{
buffer[0] = b;
index = 1;
state = STATE_WAIT_DATA;
#ifdef ENABLE_NIBE_DEBUG
if (debug)
debug(4, "\nFrame start found\n");
#endif
}
}
break;
case STATE_WAIT_DATA:
if (RS485->available() > 0)
{
byte b = RS485->read();
#ifdef ENABLE_NIBE_DEBUG
if (debug)
{
sprintf(debug_buf, "%02X", b);
debug(3, debug_buf);
}
#endif
if (index >= MAX_DATA_LEN)
{
// too long message
state = STATE_WAIT_START;
}
else
{
buffer[index++] = b;
int msglen = checkNibeMessage(buffer, index);
#ifdef ENABLE_NIBE_DEBUG
if (debug)
{
sprintf(debug_buf, "\ncheckMsg=%d\n", msglen);
debug(5, debug_buf);
}
#endif
switch (msglen)
{
case 0: break; // Ok, but not ready
case -1: state = STATE_WAIT_START; break; // Invalid message
case -2: state = STATE_CRC_FAILURE; break; // Checksum error
default: state = STATE_OK_MESSAGE_RECEIVED; break;
}
}
}
break;
case STATE_CRC_FAILURE:
#ifdef ENABLE_NIBE_DEBUG
if (debug)
debug(1, "CRC failure\n");
#endif
if (shouldAckNakSend(buffer[2]))
sendNak();
state = STATE_WAIT_START;
break;
case STATE_OK_MESSAGE_RECEIVED:
if (buffer[0] == 0x5C && buffer[1] == 0x00 &&
buffer[2] == 0x20 && buffer[4] == 0x00 &&
(buffer[3] == 0x69 || buffer[3] == 0x6B) )
{
eTokenType token = buffer[3] == 0x6B ? WRITE_TOKEN : READ_TOKEN;
#ifdef ENABLE_NIBE_DEBUG
if (debug)
debug(1, "Token received\n");
#endif
int msglen = callback_msg_token_received(token, buffer);
if (msglen > 0)
{
sendData(buffer, (byte) msglen);
}
else
{
#ifdef ENABLE_NIBE_DEBUG
if (debug)
debug(2, "No message to send\n");
#endif
if (shouldAckNakSend(buffer[2]))
sendAck();
}
}
else
{
#ifdef ENABLE_NIBE_DEBUG
if (debug)
{
debug(1, "Message received\n");
}
#endif
if (shouldAckNakSend(buffer[2]))
sendAck();
callback_msg_received(buffer, index);
}
state = STATE_WAIT_START;
break;
}
}
/*
Return:
>0 if valid message received (return message len)
0 if ok, but message not ready
-1 if invalid message
-2 if checksum fails
*/
int NibeGw::checkNibeMessage(const byte* const data, byte len)
{
if (len <= 0)
return 0;
if (len >= 1)
{
if (data[0] != 0x5C)
return -1;
if (len >= 2)
{
if (data[1] != 0x00)
return -1;
}
if (len >= 6)
{
int datalen = data[4];
if (len < datalen + 6)
return 0;
byte checksum = 0;
// calculate XOR checksum
for (int i = 2; i < (datalen + 5); i++)
checksum ^= data[i];
byte msg_checksum = data[datalen + 5];
#ifdef ENABLE_NIBE_DEBUG
if (debug) {
sprintf(debug_buf, "\nchecksum=%02X, msg_checksum=%02X\n", checksum, msg_checksum);
debug(4, debug_buf);
}
#endif
if (checksum != msg_checksum)
{
// if checksum is 0x5C (start character),
// heat pump seems to send 0xC5 checksum
if (checksum != 0x5C && msg_checksum != 0xC5)
return -2;
}
return datalen + 6;
}
}
return 0;
}
void NibeGw::sendData(const byte* const data, byte len)
{
#ifdef ENABLE_NIBE_DEBUG
if (debug)
{
debug(1, "Send message to heat pump: ");
for (int i = 0; i < len; i++)
{
sprintf(debug_buf, "%02X", data[i]);
debug(1, debug_buf);
}
debug(1, "\n");
}
#endif
digitalWrite(directionPin, HIGH);
delay(1);
RS485->write(data, len);
RS485->flush();
delay(1);
digitalWrite(directionPin, LOW);
}
void NibeGw::sendAck()
{
#ifdef ENABLE_NIBE_DEBUG
if (debug)
debug(1, "Send ACK\n");
#endif
digitalWrite(directionPin, HIGH);
delay(1);
RS485->write(0x06);
RS485->flush();
delay(1);
digitalWrite(directionPin, LOW);
}
void NibeGw::sendNak()
{
#ifdef ENABLE_NIBE_DEBUG
if (debug)
debug(1, "Send NACK\n");
#endif
digitalWrite(directionPin, HIGH);
delay(1);
RS485->write(0x15);
RS485->flush();
delay(1);
digitalWrite(directionPin, LOW);
}
boolean NibeGw::shouldAckNakSend(byte address)
{
if (sendAcknowledge)
{
if (address == MODBUS40 && ackModbus40)
return true;
else if (address == RMU40 && ackRmu40)
return true;
else if (address == SMS40 && ackSms40)
return true;
}
return false;
}

View File

@@ -0,0 +1,130 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* ----------------------------------------------------------------------------
*
* Frame format:
* +----+----+------+-----+-----+----+----+-----+
* | 5C | 00 | ADDR | CMD | LEN | DATA | CHK |
* +----+----+------+-----+-----+----+----+-----+
* |------------ CHK -----------|
*
* Address:
* 0x16 = SMS40
* 0x19 = RMU40
* 0x20 = MODBUS40
*
* Checksum: XOR
*
* When valid data is received (checksum ok),
* ACK (0x06) should be sent to the heat pump.
* When checksum mismatch,
* NAK (0x15) should be sent to the heat pump.
*
* Author: pauli.anttila@gmail.com
*
*/
#ifndef NibeGw_h
#define NibeGw_h
#include <Arduino.h>
#define HARDWARE_SERIAL
//#define ENABLE_NIBE_DEBUG
// state machine states
enum eState
{
STATE_WAIT_START,
STATE_WAIT_DATA,
STATE_OK_MESSAGE_RECEIVED,
STATE_CRC_FAILURE,
};
enum eTokenType
{
READ_TOKEN,
WRITE_TOKEN
};
// message buffer for RS-485 communication. Max message length is 80 bytes + 6 bytes header
#define MAX_DATA_LEN 128
#define NIBE_CALLBACK_MSG_RECEIVED void (*callback_msg_received)(const byte* const data, int len)
#define NIBE_CALLBACK_MSG_RECEIVED_TOKEN int (*callback_msg_token_received)(eTokenType token, byte* data)
#ifdef ENABLE_NIBE_DEBUG
#define NIBE_CALLBACK_MSG_RECEIVED_DEBUG void (*debug)(byte verbose, char* data)
#endif
#define SMS40 0x16
#define RMU40 0x19
#define MODBUS40 0x20
class NibeGw
{
private:
eState state;
boolean connectionState;
byte directionPin;
byte buffer[MAX_DATA_LEN];
byte index;
#ifdef HARDWARE_SERIAL
HardwareSerial* RS485;
#else
Serial_* RS485;
#endif
NIBE_CALLBACK_MSG_RECEIVED;
NIBE_CALLBACK_MSG_RECEIVED_TOKEN;
byte verbose;
boolean ackModbus40;
boolean ackSms40;
boolean ackRmu40;
boolean sendAcknowledge;
int checkNibeMessage(const byte* const data, byte len);
void sendData(const byte* const data, byte len);
void sendAck();
void sendNak();
boolean shouldAckNakSend(byte address);
#ifdef ENABLE_NIBE_DEBUG
NIBE_CALLBACK_MSG_RECEIVED_DEBUG;
char debug_buf[100];
#endif
public:
#ifdef HARDWARE_SERIAL
NibeGw(HardwareSerial* serial, int RS485DirectionPin);
#else
NibeGw(Serial_* serial, int RS485DirectionPin);
#endif
NibeGw& setCallback(NIBE_CALLBACK_MSG_RECEIVED, NIBE_CALLBACK_MSG_RECEIVED_TOKEN);
#ifdef ENABLE_NIBE_DEBUG
NibeGw& setDebugCallback(NIBE_CALLBACK_MSG_RECEIVED_DEBUG);
#endif
void connect();
void disconnect();
boolean connected();
void setVerboseLevel(byte level);
boolean messageStillOnProgress();
void loop();
void setAckModbus40Address(boolean val);
void setAckSms40Address(boolean val);
void setAckRmu40Address(boolean val);
void setSendAcknowledge(boolean val);
};
#endif