added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,696 @@
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* This application listening data from Nibe F1145/F1245/F1155/F1255 heat pumps (RS485 bus)
|
||||
* and send valid frames to configurable IP/port address by UDP packets.
|
||||
* Application also acknowledge the valid packets to heat pump.
|
||||
*
|
||||
* Serial settings: 9600 baud, 8 bits, Parity: none, Stop bits 1
|
||||
*
|
||||
* MODBUS module support should be turned ON from the heat pump.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* If heat pump does not receive acknowledge in certain time period,
|
||||
* pump will raise an alarm and alarm mode is activated.
|
||||
*
|
||||
* Author: pauli.anttila@gmail.com
|
||||
*
|
||||
* Build: gcc -std=gnu99 -o nibegw nibegw.c
|
||||
*
|
||||
* 3.2.2013 v1.00 Initial version
|
||||
* 5.2.2013 v1.01
|
||||
* 4.11.2013 v1.02 Support cheksum and data special cases.
|
||||
* 20.12.2013 v1.03 Fixed compiling error.
|
||||
* 3.6.2014 v1.04
|
||||
* 4.6.2014 v1.10 More options.
|
||||
* 10.9.2014 v1.20 Bidirectional support.
|
||||
* 30.6.2015 v1.21 Some fixes.
|
||||
* 20.2.2017 v1.22 Separated read and write token support.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#define VERSION "1.22"
|
||||
|
||||
#define FALSE 0
|
||||
#define TRUE 1
|
||||
|
||||
#define MIN(a,b) (((a)<(b))?(a):(b))
|
||||
#define MAX(a,b) (((a)>(b))?(a):(b))
|
||||
|
||||
int verbose = 0;
|
||||
int testmode = FALSE;
|
||||
|
||||
void signalCallbackHandler(int signum)
|
||||
{
|
||||
if (verbose) printf("\nExit...caught by signal %d\n", signum);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int initSerialPort(int fd, int hwflowctrl)
|
||||
{
|
||||
struct termios options;
|
||||
|
||||
// Get the current options for the port...
|
||||
|
||||
tcgetattr(fd, &options);
|
||||
|
||||
// Set the baud rates
|
||||
cfsetispeed(&options, B9600);
|
||||
cfsetospeed(&options, B9600);
|
||||
|
||||
// Enable the receiver and set local mode...
|
||||
options.c_cflag |= (CLOCAL | CREAD);
|
||||
|
||||
// 8 data bits, no parity, 1 stop bit
|
||||
options.c_cflag &= ~PARENB;
|
||||
options.c_cflag &= ~CSTOPB;
|
||||
options.c_cflag &= ~CSIZE;
|
||||
options.c_cflag |= CS8;
|
||||
|
||||
// Flow control
|
||||
options.c_iflag &= ~(IXON | IXOFF | IXANY);
|
||||
|
||||
if (hwflowctrl)
|
||||
options.c_cflag |= CRTSCTS; // Enable hardware flow control
|
||||
else
|
||||
options.c_cflag &= ~CRTSCTS; // Disable hardware flow control
|
||||
|
||||
options.c_cc[VMIN] = 1; // Min character to be read
|
||||
options.c_cc[VTIME] = 1; // Time to wait for data (tenth of seconds)
|
||||
|
||||
// Set the new options
|
||||
if (tcsetattr(fd, TCSANOW, &options) < 0 )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void printMessage(const unsigned char* const message, int msglen)
|
||||
{
|
||||
printf("Data: ");
|
||||
for (int l = 0; l < msglen; l++)
|
||||
printf("%02X", message[l]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
int writeDataToSerialPort(int fd, const unsigned char* const message, int msglen)
|
||||
{
|
||||
int retval = -1;
|
||||
|
||||
if (verbose > 2) printf("Write data to serial port\n");
|
||||
if (verbose > 2) printMessage(message, msglen);
|
||||
|
||||
if( write( fd, message, msglen) == msglen)
|
||||
{
|
||||
tcdrain (fd);
|
||||
retval = 0;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int forwardUdpMsgToSerial(int udpfd, int serialfd)
|
||||
{
|
||||
#define MAX_UDP_MSG_SIZE 50
|
||||
unsigned char udp_packet[MAX_UDP_MSG_SIZE];
|
||||
int udplen;
|
||||
|
||||
if ((udplen = recv(udpfd, udp_packet, MAX_UDP_MSG_SIZE, 0)) > 0)
|
||||
{
|
||||
if (verbose > 1) printf("Received UDP message...relay message to serial port\n");
|
||||
if (verbose > 2) printMessage( udp_packet, udplen);
|
||||
|
||||
writeDataToSerialPort(serialfd, udp_packet, udplen);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int sendAck(int fd)
|
||||
{
|
||||
unsigned char ack = 0x06;
|
||||
if (verbose > 1) printf("Send ACK (0x06)\n");
|
||||
return writeDataToSerialPort(fd, &ack, 1);
|
||||
}
|
||||
|
||||
int sendNak(int fd)
|
||||
{
|
||||
unsigned char nack = 0x15;
|
||||
if (verbose > 1) printf("Send NAK (0x15)\n");
|
||||
return writeDataToSerialPort(fd, &nack, 1);
|
||||
}
|
||||
|
||||
char* getTimeStamp(char* buffer)
|
||||
{
|
||||
struct timeval tv;
|
||||
struct timezone tz;
|
||||
struct tm* tm;
|
||||
|
||||
gettimeofday(&tv, &tz);
|
||||
tm = localtime(&tv.tv_sec);
|
||||
|
||||
sprintf(buffer, "%d.%d.%d %d:%02d:%02d:%d",
|
||||
tm->tm_year + 1900,
|
||||
tm->tm_mon + 1,
|
||||
tm->tm_mday,
|
||||
tm->tm_hour,
|
||||
tm->tm_min, tm->tm_sec, tv.tv_usec
|
||||
);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
ssize_t readData(int fildes, void *buf, size_t nbyte)
|
||||
{
|
||||
if (testmode)
|
||||
{
|
||||
unsigned char testdata[] =
|
||||
/* Junk */
|
||||
"\x01\x02" \
|
||||
|
||||
/* Frame from MODBUS40 */
|
||||
"\x5C\x00\x20\x6B\x00\x4B" \
|
||||
|
||||
/* Frame from RMU40 */
|
||||
"\x5C\x00\x19\x60\x00\x79" \
|
||||
|
||||
/* Frame from RMU40 */
|
||||
"\x5C\x00\x19\x62\x18\x00\x80\x00\x80\x00\x00\x00\x00\x00"
|
||||
"\x80\x00\x00\x00\x00\x00\x0B\x0B\x00\x00\x00\x01\x00\x00"
|
||||
"\x05\xE7" \
|
||||
|
||||
/* Data frame from MODBUS40 */
|
||||
"\x5C\x00\x20\x68\x50\x01\xA8\x1F\x01\x00\xA8\x64\x00\xFD" \
|
||||
"\xA7\xD0\x03\x44\x9C\x1E\x00\x4F\x9C\xA0\x00\x50\x9C\x78" \
|
||||
"\x00\x51\x9C\x03\x01\x52\x9C\x1B\x01\x87\x9C\x14\x01\x4E" \
|
||||
"\x9C\xC6\x01\x47\x9C\x01\x01\x15\xB9\xB0\xFF\x3A\xB9\x4B" \
|
||||
"\x00\xC9\xAF\x00\x00\x48\x9C\x0D\x01\x4C\x9C\xE7\x00\x4B" \
|
||||
"\x9C\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00" \
|
||||
"\x00\x45" \
|
||||
|
||||
/* Token Frame from MODBUS40 */
|
||||
"\x5C\x00\x20\x69\x00\x49" \
|
||||
;
|
||||
|
||||
int len = sizeof(testdata);
|
||||
|
||||
if (len > nbyte)
|
||||
{
|
||||
fprintf(stderr, "Too much test data, limiting %u to %u\n", len, (unsigned int)nbyte);
|
||||
}
|
||||
|
||||
len = MIN(len, nbyte);
|
||||
memcpy( buf, testdata, len);
|
||||
|
||||
static int delay = FALSE;
|
||||
if (delay)
|
||||
sleep(2); // slow down little bit after first round
|
||||
|
||||
delay = TRUE;
|
||||
return len;
|
||||
}
|
||||
|
||||
return read(fildes, buf, nbyte);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 checkMessage(const unsigned char* const data, int len)
|
||||
{
|
||||
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;
|
||||
|
||||
unsigned char calc_checksum = 0;
|
||||
|
||||
// calculate XOR checksum
|
||||
for(int i = 2; i < (datalen + 5); i++)
|
||||
calc_checksum ^= data[i];
|
||||
|
||||
unsigned char msg_checksum = data[datalen + 5];
|
||||
|
||||
if (verbose) printf("(calc/recv checksum %02X/%02X = ", calc_checksum, msg_checksum);
|
||||
|
||||
if (calc_checksum != msg_checksum)
|
||||
{
|
||||
// check special case, if checksum is 0x5C (start character),
|
||||
// heat pump seems to send 0xC5 checksum
|
||||
if (calc_checksum != 0x5C && msg_checksum != 0xC5)
|
||||
{
|
||||
if (verbose) printf("ERROR)\n");
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose) printf("OK)\n");
|
||||
return datalen + 6;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void printUsage(char* appname)
|
||||
{
|
||||
char* usage = "%s usage:\n\n" \
|
||||
"\t-h Print help\n" \
|
||||
"\t-v Print debug information\n" \
|
||||
"\t-d <device name> Serial port device (default: /dev/ttyS0)\n" \
|
||||
"\t-a <address> Remote host address (default: 127.0.0.1)\n" \
|
||||
"\t-p <port> Remote UDP port (default: 9999)\n" \
|
||||
"\t-f Disable flow control (default: HW)\n" \
|
||||
"\t-r <address> RS-485 address to listen (default: 0x20)\n" \
|
||||
"\t-i Send all messages by UDP (default: only modbus data)\n" \
|
||||
"\t-n Don't send acknowledge at all\n" \
|
||||
"\t-o Send acknowledge to all addresses\n" \
|
||||
"\t-t Test mode\n" \
|
||||
"\t-l <port> Local UDP port for read commands (default: 10000)\n" \
|
||||
"\t-w <port> Local UDP port for write commands (default: 10001)\n" \
|
||||
"\t-q Print data in log format\n" \
|
||||
;
|
||||
|
||||
fprintf (stderr, usage, appname);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
char *device = "/dev/ttySO";
|
||||
char *remoteHost = "127.0.0.1";
|
||||
int remotePort = 9999;
|
||||
int localPort4readCmds = 10000;
|
||||
int localPort4writeCmds = 10001;
|
||||
unsigned char rs485addr = 0x20;
|
||||
int sendall = FALSE;
|
||||
int sendack = TRUE;
|
||||
int ackall = FALSE;
|
||||
int hwflowctrl = TRUE;
|
||||
int log = FALSE;
|
||||
|
||||
int c;
|
||||
opterr = 0;
|
||||
|
||||
while ((c = getopt (argc, argv, "hvd:a:p:r:infotql:w:")) != -1)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
sendall = TRUE;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
sendack = FALSE;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
hwflowctrl = FALSE;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
ackall = TRUE;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
testmode = TRUE;
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
log = TRUE;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
device = optarg;
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
remoteHost = optarg;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
remotePort = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
localPort4readCmds = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
localPort4writeCmds = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
rs485addr = atoi(optarg);
|
||||
break;
|
||||
|
||||
case '?':
|
||||
if (optopt == 'd' || optopt == 'a' || optopt == 'p')
|
||||
fprintf (stderr, "Option -%c requires an argument.\n", optopt);
|
||||
else if (isprint (optopt))
|
||||
fprintf (stderr, "Unknown option `-%c'.\n", optopt);
|
||||
else
|
||||
fprintf (stderr,
|
||||
"Unknown option character `\\x%x'.\n",
|
||||
optopt);
|
||||
return 1;
|
||||
|
||||
case 'h':
|
||||
default:
|
||||
printUsage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
printf("NibeGW version: %s\n", VERSION);
|
||||
printf("Verbose level: %i\n", verbose);
|
||||
printf("Test mode: %s\n", testmode ? "TRUE" : "FALSE");
|
||||
printf("Serial port: %s\n", device);
|
||||
printf("Flow control: %s\n", hwflowctrl ? "HW" : "None");
|
||||
printf("remote UDP address: %s:%u\n", remoteHost, remotePort);
|
||||
printf("server UDP address for read cmds: %u\n", localPort4readCmds);
|
||||
printf("server UDP address for write cmds: %u\n", localPort4writeCmds);
|
||||
printf("RS-485 address to listen: 0x%02X\n", rs485addr);
|
||||
printf("Send all messages by UDP: %s\n", sendall ? "TRUE" : "FALSE");
|
||||
printf("Send acknowledge: %s\n", sendack ? "TRUE" : "FALSE");
|
||||
printf("Send acknowledge to all addresses: %s\n", ackall ? "TRUE" : "FALSE");
|
||||
}
|
||||
|
||||
// Install signal handlers
|
||||
signal(SIGINT, signalCallbackHandler);
|
||||
|
||||
int serialport_fd = -1;
|
||||
int udp_fd = -1;
|
||||
int udp4writeCmds_fd = -1;
|
||||
|
||||
// Initialize destination address
|
||||
struct sockaddr_in dest;
|
||||
memset((char *)&dest, 0, sizeof(dest));
|
||||
dest.sin_family = AF_INET;
|
||||
dest.sin_addr.s_addr = inet_addr(remoteHost);
|
||||
dest.sin_port = htons(remotePort);
|
||||
|
||||
// Initialize server address for read commands
|
||||
struct sockaddr_in server4read;
|
||||
memset((char *)&server4read, 0, sizeof(server4read));
|
||||
server4read.sin_family = AF_INET;
|
||||
server4read.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
server4read.sin_port = htons(localPort4readCmds);
|
||||
|
||||
// Initialize server address for write commands
|
||||
struct sockaddr_in server4write;
|
||||
memset((char *)&server4write, 0, sizeof(server4write));
|
||||
server4write.sin_family = AF_INET;
|
||||
server4write.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
server4write.sin_port = htons(localPort4writeCmds);
|
||||
|
||||
int maxdatalen = 200;
|
||||
|
||||
if (testmode)
|
||||
{
|
||||
maxdatalen = 1000;
|
||||
}
|
||||
|
||||
unsigned char buffer[maxdatalen];
|
||||
unsigned char message[maxdatalen];
|
||||
|
||||
for (;;)
|
||||
{
|
||||
|
||||
if (testmode == FALSE && serialport_fd < 0)
|
||||
{
|
||||
if (strncmp(device, "stdin" , 5) == 0)
|
||||
{
|
||||
if (verbose) printf("Use stdin as virtual serial port\n");
|
||||
serialport_fd = STDIN_FILENO;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Open the serial port
|
||||
if (verbose) printf("Open serial port: %s\n", device);
|
||||
serialport_fd = open(device, O_RDWR | O_NOCTTY); // | O_NDELAY
|
||||
|
||||
if (serialport_fd < 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to open %s: %s\n", device, strerror(errno));
|
||||
}
|
||||
|
||||
// Initialize serial port
|
||||
if (initSerialPort(serialport_fd, hwflowctrl) == -1)
|
||||
{
|
||||
fprintf(stderr, "Failed to set serial port: %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ( udp_fd < 0 )
|
||||
{
|
||||
// Open UDP socket
|
||||
udp_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
|
||||
if (udp_fd < 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to open UDP socket for read commands: %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
if (verbose) printf("Initialize UDP server\n");
|
||||
|
||||
// Set non blocking flag to UDP socket
|
||||
int flags = fcntl(udp_fd, F_GETFL, 0);
|
||||
fcntl(udp_fd, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
//bind socket to port
|
||||
if( bind(udp_fd, (struct sockaddr*)&server4read, sizeof(server4read) ) == -1)
|
||||
{
|
||||
fprintf(stderr, "Failed to bind UDP port for read commands: %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
if ( udp4writeCmds_fd < 0 )
|
||||
{
|
||||
// Open UDP socket
|
||||
udp4writeCmds_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
|
||||
if (udp4writeCmds_fd < 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to open UDP socket for write commands: %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
if (verbose) printf("Initialize UDP server\n");
|
||||
|
||||
// Set non blocking flag to UDP socket
|
||||
int flags = fcntl(udp4writeCmds_fd, F_GETFL, 0);
|
||||
fcntl(udp4writeCmds_fd, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
//bind socket to port
|
||||
if( bind(udp4writeCmds_fd, (struct sockaddr*)&server4write, sizeof(server4write) ) == -1)
|
||||
{
|
||||
fprintf(stderr, "Failed to bind UDP port for write commands: %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
if (testmode || serialport_fd >= 0)
|
||||
{
|
||||
char timestamp[80];
|
||||
ssize_t len = 0;
|
||||
|
||||
int startfound = FALSE;
|
||||
int index = 0;
|
||||
|
||||
// read all available bytes from serial port
|
||||
while ((len = readData(serialport_fd, buffer, maxdatalen)) > 0)
|
||||
{
|
||||
// go throw all bytes read from serial port
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
if (log) printf("\\x%02X", buffer[i]);
|
||||
|
||||
if (startfound == FALSE)
|
||||
{
|
||||
if (verbose) printf("\n%s: ", getTimeStamp(timestamp));
|
||||
}
|
||||
|
||||
if (verbose) printf("%02X ", buffer[i]);
|
||||
if (verbose > 3) printf("(%c) ", buffer[i]);
|
||||
|
||||
if (startfound == FALSE && buffer[i] == 0x5C)
|
||||
{
|
||||
startfound = TRUE;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
if (startfound)
|
||||
{
|
||||
if ((index+1) >= maxdatalen)
|
||||
{
|
||||
// too long message, try to find new start char
|
||||
startfound = FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
message[index++] = buffer[i];
|
||||
|
||||
int msglen = checkMessage(message, index);
|
||||
|
||||
switch (msglen)
|
||||
{
|
||||
case 0: // Message ok so far, but not ready
|
||||
break;
|
||||
|
||||
case -1: // Invalid message
|
||||
startfound = FALSE;
|
||||
break;
|
||||
|
||||
case -2: // Checksum error
|
||||
if (message[2] == rs485addr || ackall)
|
||||
{
|
||||
if (sendack) sendNak(serialport_fd);
|
||||
}
|
||||
startfound = FALSE;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (verbose > 1) printf("Valid message received, len=%u\n", msglen);
|
||||
|
||||
if (message[2] == rs485addr || ackall)
|
||||
{
|
||||
// send ack to nibe or read/write messages if token received
|
||||
|
||||
int nothingToSend = TRUE;
|
||||
|
||||
if (message[3] == 0x69 && message[4] == 0x00)
|
||||
{
|
||||
if (verbose > 1) printf("Read token received\n");
|
||||
nothingToSend = forwardUdpMsgToSerial(udp_fd, serialport_fd);
|
||||
}
|
||||
else if (message[3] == 0x6b && message[4] == 0x00) {
|
||||
if (verbose > 1) printf("Write token received\n");
|
||||
nothingToSend = forwardUdpMsgToSerial(udp4writeCmds_fd, serialport_fd);
|
||||
}
|
||||
|
||||
if (nothingToSend)
|
||||
{
|
||||
if (verbose > 1) printf("Nothing to send...");
|
||||
if (sendack) sendAck(serialport_fd);
|
||||
}
|
||||
}
|
||||
|
||||
if (message[2] == rs485addr || sendall)
|
||||
{
|
||||
// send message to remote
|
||||
|
||||
if (verbose > 1) printf("Send UDP data to %s:%u\n", remoteHost, remotePort);
|
||||
if (verbose > 2) printMessage( message, msglen);
|
||||
|
||||
if (sendto(udp_fd, message, msglen + 1, 0 , (struct sockaddr *)&dest, sizeof(dest)) == -1)
|
||||
{
|
||||
fprintf(stderr, "Failed to send udp packet: %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
// Wait new message
|
||||
startfound = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (len < 0)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
{
|
||||
if (verbose) printf("Interrupted\n");
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Read failed: %s\n", strerror(errno));
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (log) fflush(stdout);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
close(serialport_fd);
|
||||
close(udp_fd);
|
||||
close(udp4writeCmds_fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
# Useful commands to create files used by the binding from Nibe ModbusManager CSV files
|
||||
|
||||
Command to convert Nibe CSV file character set to UTF-8:
|
||||
```iconv -f iso-8859-1 -t utf-8 F1X45.csv > F1X45_utf8.csv```
|
||||
|
||||
Command to create channel-types from CSV file (all channels are marked as advanced, so remove it manually from desired channels):
|
||||
```awk -f create_channel_types.awk F1X45_utf8.csv > F1X45.xml```
|
||||
|
||||
Command to create all channels from CSV file:
|
||||
```awk -f create_channels.awk F1X45_utf8.csv > F1X45-all-channels.xml```
|
||||
|
||||
Command to create dedicated channels from CSV file:
|
||||
```awk -f create_sensor_channels.awk F1X45_utf8.csv > F1X45-sensor-channels.xml```
|
||||
```awk -f create_settings_channels.awk F1X45_utf8.csv > F1X45-settings-channels.xml```
|
||||
|
||||
Command to create java variableInformation from CSV file:
|
||||
```awk -f create_java_variable_information.awk F1X45_utf8.csv > F1X45-variable-information.txt```
|
||||
|
||||
Command to crate READ.md channel tables:
|
||||
```awk -f create_readme_channel_table.awk F1X45_utf8.csv > F1X45-readme-channel-table.txt```
|
||||
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/awk -f
|
||||
|
||||
function ltrim(s) { sub(/^[ \t\r\n]+/, "", s); return s }
|
||||
function rtrim(s) { sub(/[ \t\r\n]+$/, "", s); return s }
|
||||
function trim(s) { return rtrim(ltrim(s)); }
|
||||
|
||||
BEGIN{
|
||||
FS=";"
|
||||
|
||||
print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
||||
print "<thing:thing-descriptions bindingId=\"nibeheatpump\""
|
||||
print "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
|
||||
print "xmlns:thing=\"https://openhab.org/schemas/thing-description/v1.0.0\" "
|
||||
print "xsi:schemaLocation=\"https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd\">"
|
||||
print ""
|
||||
}
|
||||
NR>5{
|
||||
|
||||
title=$1
|
||||
info=$2
|
||||
id=$3
|
||||
unit=$4
|
||||
size=$5
|
||||
factor=$6
|
||||
min=$7
|
||||
max=$8
|
||||
default=$9
|
||||
mode=$10
|
||||
|
||||
gsub("\"", "", title)
|
||||
gsub("&","\\&", title)
|
||||
gsub(/</,"\\<", title)
|
||||
gsub(">","\\>", title)
|
||||
|
||||
gsub("\"", "", unit)
|
||||
|
||||
gsub("\"", "", info)
|
||||
gsub("&","\\&", info)
|
||||
gsub(/</,"\\<", info)
|
||||
gsub(">","\\>", info)
|
||||
|
||||
optionsData=info
|
||||
delete keys
|
||||
delete values
|
||||
|
||||
# parse options from info field
|
||||
if (index(optionsData, "=") > 0)
|
||||
{
|
||||
gsub(",", "=", optionsData)
|
||||
gsub(" ", "=", optionsData)
|
||||
|
||||
key=""
|
||||
value=""
|
||||
keyCount=1
|
||||
|
||||
count = split(optionsData, t, "=")
|
||||
|
||||
for (i = 0; ++i <= count;)
|
||||
{
|
||||
if (t[i] ~ /^[0-9]+$/)
|
||||
{
|
||||
if (key != "")
|
||||
{
|
||||
if (trim(value) == "")
|
||||
{
|
||||
values[keyCount-1] = values[keyCount-1] " " key
|
||||
}
|
||||
else
|
||||
{
|
||||
keys[keyCount] = trim(key)
|
||||
values[keyCount] = trim(value)
|
||||
keyCount++
|
||||
key=""
|
||||
value=""
|
||||
}
|
||||
}
|
||||
|
||||
key = t[i]
|
||||
}
|
||||
else
|
||||
{
|
||||
if (key != "")
|
||||
value = value " " t[i]
|
||||
}
|
||||
}
|
||||
|
||||
if (key != "")
|
||||
{
|
||||
keys[keyCount] = trim(key)
|
||||
values[keyCount] = trim(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
printf(" <channel-type id=\"type-%s\" advanced=\"true\">\n", id)
|
||||
|
||||
if (min == 0 && max == 1)
|
||||
{
|
||||
printf(" <item-type>Switch</item-type>\n")
|
||||
printf(" <label>%s</label>\n", title)
|
||||
printf(" <description>%s</description>\n", info)
|
||||
|
||||
if (mode == "R")
|
||||
readOnly="true"
|
||||
else
|
||||
readOnly="false"
|
||||
|
||||
printf(" <state readOnly=\"%s\"></state>\n", readOnly)
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (keys[1] != "")
|
||||
printf(" <item-type>String</item-type>\n")
|
||||
else
|
||||
printf(" <item-type>Number</item-type>\n")
|
||||
|
||||
printf(" <label>%s</label>\n", title)
|
||||
printf(" <description>%s</description>\n", info)
|
||||
|
||||
if (min == 0 && max == 0)
|
||||
{
|
||||
min="0";
|
||||
if (size == "u8")
|
||||
max="255"
|
||||
if (size == "u16")
|
||||
max="65535"
|
||||
if (size == "u32")
|
||||
max="4294967295"
|
||||
|
||||
if (size == "s8")
|
||||
{
|
||||
min="-128"
|
||||
max="127"
|
||||
}
|
||||
if (size == "s16")
|
||||
{
|
||||
min="-32767"
|
||||
max="32767"
|
||||
}
|
||||
if (size == "s32")
|
||||
{
|
||||
min="-2147483648"
|
||||
max="2147483647"
|
||||
}
|
||||
}
|
||||
|
||||
if (factor == 1)
|
||||
pattern = "%d"
|
||||
if (factor == 10)
|
||||
pattern = "%.1f"
|
||||
if (factor == 100)
|
||||
pattern = "%.2f"
|
||||
if (factor == 1000)
|
||||
pattern = "%.3f"
|
||||
|
||||
if (unit != "")
|
||||
pattern = pattern " " unit
|
||||
|
||||
if (mode == "R")
|
||||
printf(" <state pattern=\"%s\" readOnly=\"true\">\n", pattern)
|
||||
else
|
||||
printf(" <state min=\"%s\" max=\"%s\" step=\"1\" pattern=\"%s\" readOnly=\"false\">\n", min, max, pattern)
|
||||
|
||||
if (keys[1] != "")
|
||||
{
|
||||
printf(" <options>\n")
|
||||
for (x = 1; x <= keyCount; x++)
|
||||
printf(" <option value=\"%s\">%s</option>\n", keys[x], values[x])
|
||||
printf(" </options>\n")
|
||||
}
|
||||
printf(" </state>\n")
|
||||
}
|
||||
|
||||
printf(" </channel-type>\n")
|
||||
}
|
||||
END{
|
||||
print ""
|
||||
print "</thing:thing-descriptions>"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/awk -f
|
||||
|
||||
function ltrim(s) { sub(/^[ \t\r\n]+/, "", s); return s }
|
||||
function rtrim(s) { sub(/[ \t\r\n]+$/, "", s); return s }
|
||||
function trim(s) { return rtrim(ltrim(s)); }
|
||||
|
||||
BEGIN{
|
||||
FS=";"
|
||||
}
|
||||
NR>5{
|
||||
|
||||
title=$1
|
||||
info=$2
|
||||
id=$3
|
||||
unit=$4
|
||||
size=$5
|
||||
factor=$6
|
||||
min=$7
|
||||
max=$8
|
||||
default=$9
|
||||
mode=$10
|
||||
|
||||
gsub("\"", "", title)
|
||||
gsub("\"", "", info)
|
||||
gsub("\"", "", unit)
|
||||
|
||||
printf(" <channel id=\"%s\" typeId=\"type-%s\"/>\n", id, id)
|
||||
}
|
||||
END{
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/awk -f
|
||||
|
||||
function ltrim(s) { sub(/^[ \t\r\n]+/, "", s); return s }
|
||||
function rtrim(s) { sub(/[ \t\r\n]+$/, "", s); return s }
|
||||
function trim(s) { return rtrim(ltrim(s)); }
|
||||
|
||||
BEGIN{
|
||||
FS=";"
|
||||
}
|
||||
NR>5{
|
||||
|
||||
title=$1
|
||||
info=$2
|
||||
id=$3
|
||||
unit=$4
|
||||
size=$5
|
||||
factor=$6
|
||||
min=$7
|
||||
max=$8
|
||||
default=$9
|
||||
mode=$10
|
||||
|
||||
gsub("\"", "", title)
|
||||
gsub("\"", "", info)
|
||||
gsub("\"", "", unit)
|
||||
|
||||
if (mode == "R")
|
||||
type="Sensor"
|
||||
else
|
||||
type="Setting"
|
||||
|
||||
printf "put(%s, new VariableInformation(%4s, NibeDataType.%-3s, Type.%-8s, \"%s\"));\n", id, factor, toupper(size), type, title
|
||||
}
|
||||
END{
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/awk -f
|
||||
|
||||
function ltrim(s) { sub(/^[ \t\r\n]+/, "", s); return s }
|
||||
function rtrim(s) { sub(/[ \t\r\n]+$/, "", s); return s }
|
||||
function trim(s) { return rtrim(ltrim(s)); }
|
||||
|
||||
BEGIN{
|
||||
FS=";"
|
||||
|
||||
print "| Channel Type ID | Item Type | Min | Max | Type | Description | Values |"
|
||||
print "|-----------------|--------------|--------------|--------------|---------|-------------------------------------|--------------------------------|"
|
||||
}
|
||||
NR>5{
|
||||
|
||||
title=$1
|
||||
info=$2
|
||||
id=$3
|
||||
unit=$4
|
||||
size=$5
|
||||
factor=$6
|
||||
min=$7
|
||||
max=$8
|
||||
default=$9
|
||||
mode=$10
|
||||
|
||||
gsub("\"", "", title)
|
||||
gsub("\"", "", info)
|
||||
gsub("\"", "", unit)
|
||||
|
||||
optionsData=info
|
||||
delete keys
|
||||
delete values
|
||||
|
||||
# parse options from info field
|
||||
if (index(optionsData, "=") > 0)
|
||||
{
|
||||
gsub(",", "=", optionsData)
|
||||
gsub(" ", "=", optionsData)
|
||||
|
||||
key=""
|
||||
value=""
|
||||
keyCount=1
|
||||
|
||||
count = split(optionsData, t, "=")
|
||||
|
||||
for (i = 0; ++i <= count;)
|
||||
{
|
||||
if (t[i] ~ /^[0-9]+$/)
|
||||
{
|
||||
if (key != "")
|
||||
{
|
||||
if (trim(value) == "")
|
||||
{
|
||||
values[keyCount-1] = values[keyCount-1] " " key
|
||||
}
|
||||
else
|
||||
{
|
||||
keys[keyCount] = trim(key)
|
||||
values[keyCount] = trim(value)
|
||||
keyCount++
|
||||
key=""
|
||||
value=""
|
||||
}
|
||||
}
|
||||
|
||||
key = t[i]
|
||||
}
|
||||
else
|
||||
{
|
||||
if (key != "")
|
||||
value = value " " t[i]
|
||||
}
|
||||
}
|
||||
|
||||
if (key != "")
|
||||
{
|
||||
keys[keyCount] = trim(key)
|
||||
values[keyCount] = trim(value)
|
||||
}
|
||||
}
|
||||
|
||||
if (min == 0 && max == 1)
|
||||
{
|
||||
channelType="Switch"
|
||||
|
||||
if (mode == "R")
|
||||
readOnly="true"
|
||||
else
|
||||
readOnly="false"
|
||||
}
|
||||
else
|
||||
{
|
||||
if (keys[1] != "")
|
||||
channelType="String"
|
||||
else
|
||||
channelType="Number"
|
||||
|
||||
if (min == 0 && max == 0)
|
||||
{
|
||||
min="0";
|
||||
if (size == "u8")
|
||||
max="255"
|
||||
if (size == "u16")
|
||||
max="65535"
|
||||
if (size == "u32")
|
||||
max="4294967295"
|
||||
|
||||
if (size == "s8")
|
||||
{
|
||||
min="-128"
|
||||
max="127"
|
||||
}
|
||||
if (size == "s16")
|
||||
{
|
||||
min="-32767"
|
||||
max="32767"
|
||||
}
|
||||
if (size == "s32")
|
||||
{
|
||||
min="-2147483648"
|
||||
max="2147483647"
|
||||
}
|
||||
}
|
||||
|
||||
if (factor == 1)
|
||||
pattern = "%d"
|
||||
if (factor == 10)
|
||||
pattern = "%.1f"
|
||||
if (factor == 100)
|
||||
pattern = "%.2f"
|
||||
if (factor == 1000)
|
||||
pattern = "%.3f"
|
||||
|
||||
if (unit != "")
|
||||
pattern = pattern " " unit
|
||||
|
||||
vals = ""
|
||||
|
||||
if (keys[1] != "")
|
||||
{
|
||||
for (x = 1; x <= keyCount; x++)
|
||||
{
|
||||
if (vals != "")
|
||||
vals = vals ", "
|
||||
|
||||
vals = vals keys[x] "=" values[x]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (readOnly == "true")
|
||||
type="Sensor"
|
||||
else
|
||||
type="Setting"
|
||||
|
||||
printf("| %s | %s | %d | %d | %s | %s | %s |\n", id, channelType, min, max, type, title, vals)
|
||||
}
|
||||
END{
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/awk -f
|
||||
|
||||
function ltrim(s) { sub(/^[ \t\r\n]+/, "", s); return s }
|
||||
function rtrim(s) { sub(/[ \t\r\n]+$/, "", s); return s }
|
||||
function trim(s) { return rtrim(ltrim(s)); }
|
||||
|
||||
BEGIN{
|
||||
FS=";"
|
||||
print "<channel-group-type id=\"Sensors\">"
|
||||
print " <label>Sensors</label>"
|
||||
print " <channels>"
|
||||
}
|
||||
NR>5{
|
||||
|
||||
title=$1
|
||||
info=$2
|
||||
id=$3
|
||||
unit=$4
|
||||
size=$5
|
||||
factor=$6
|
||||
min=$7
|
||||
max=$8
|
||||
default=$9
|
||||
mode=$10
|
||||
|
||||
gsub("\"", "", title)
|
||||
gsub("\"", "", info)
|
||||
gsub("\"", "", unit)
|
||||
|
||||
if (mode == "R")
|
||||
printf(" <channel id=\"%s\" typeId=\"type-%s\"/>\n", id, id)
|
||||
}
|
||||
END{
|
||||
print " </channels>"
|
||||
print "</channel-group-type>"
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/awk -f
|
||||
|
||||
function ltrim(s) { sub(/^[ \t\r\n]+/, "", s); return s }
|
||||
function rtrim(s) { sub(/[ \t\r\n]+$/, "", s); return s }
|
||||
function trim(s) { return rtrim(ltrim(s)); }
|
||||
|
||||
BEGIN{
|
||||
FS=";"
|
||||
print "<channel-group-type id=\"Settings\">"
|
||||
print " <label>Settings</label>"
|
||||
print " <channels>"
|
||||
}
|
||||
NR>5{
|
||||
|
||||
title=$1
|
||||
info=$2
|
||||
id=$3
|
||||
unit=$4
|
||||
size=$5
|
||||
factor=$6
|
||||
min=$7
|
||||
max=$8
|
||||
default=$9
|
||||
mode=$10
|
||||
|
||||
gsub("\"", "", title)
|
||||
gsub("\"", "", info)
|
||||
gsub("\"", "", unit)
|
||||
|
||||
if (mode != "R")
|
||||
printf(" <channel id=\"%s\" typeId=\"type-%s\"/>\n", id, id)
|
||||
}
|
||||
END{
|
||||
print " </channels>"
|
||||
print "</channel-group-type>"
|
||||
}
|
||||
Reference in New Issue
Block a user