655 lines
19 KiB
C++
655 lines
19 KiB
C++
/*
|
|
* ebusd - daemon for communication with eBUS heating systems.
|
|
* Copyright (C) 2022-2024 John Baier <ebusd@ebusd.eu>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef LIB_KNX_KNXNET_H_
|
|
#define LIB_KNX_KNXNET_H_
|
|
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/ioctl.h>
|
|
#include <net/if.h>
|
|
#ifndef __CYGWIN__
|
|
#include <net/if_arp.h>
|
|
#endif
|
|
#include <netdb.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#ifdef __FreeBSD__
|
|
#include <machine/endian.h>
|
|
#else
|
|
#include <endian.h>
|
|
#endif
|
|
#include <string>
|
|
#include <cstdio>
|
|
#include <ctime>
|
|
#include "lib/knx/knx.h"
|
|
#include "lib/utils/tcpsocket.h"
|
|
|
|
namespace ebusd {
|
|
|
|
/** @file lib/knx/knxnet.h
|
|
* KNXnet/IP implementation of the @a KnxConnection interface based on UDP multicast.
|
|
*/
|
|
|
|
using std::string;
|
|
|
|
// 16 bit unsigned big endian
|
|
typedef union __attribute__ ((packed)) {
|
|
uint16_t raw;
|
|
struct {
|
|
uint8_t high;
|
|
uint8_t low;
|
|
};
|
|
} uint16be_t;
|
|
|
|
// 32 bit unsigned big endian
|
|
typedef union __attribute__ ((packed)) {
|
|
uint32_t raw;
|
|
struct {
|
|
uint8_t msb1;
|
|
uint8_t msb2;
|
|
uint8_t msb3;
|
|
uint8_t lsb;
|
|
};
|
|
} uint32be_t;
|
|
|
|
// KNXnet/IP header
|
|
typedef struct __attribute__ ((packed)) {
|
|
uint8_t headerLength; // =6
|
|
uint8_t protocolVersion; // =0x10
|
|
uint16be_t serviceTypeIdentifier;
|
|
uint16be_t totalLength; // complete length including header
|
|
} knxnet_header_t;
|
|
|
|
/** service types. */
|
|
typedef enum {
|
|
SERVICE_TYPE_SEARCH_REQ = 0x0201,
|
|
SERVICE_TYPE_SEARCH_RES = 0x0202,
|
|
SERVICE_TYPE_DESC_REQ = 0x0203,
|
|
SERVICE_TYPE_DESC_RES = 0x0204,
|
|
// SERVICE_TYPE_CONN_REQ = 0x0205,
|
|
// SERVICE_TYPE_CONN_RES = 0x0206,
|
|
// SERVICE_TYPE_CONNSTATE_REQ = 0x0207,
|
|
// SERVICE_TYPE_CONNSTATE_RES = 0x0208,
|
|
// SERVICE_TYPE_DISCONN_REQ = 0x0209,
|
|
// SERVICE_TYPE_DISCONN_RES = 0x020A,
|
|
// SERVICE_TYPE_DEVICE_CFG_REQ = 0x0310,
|
|
// SERVICE_TYPE_DEVICE_CFG_ACK = 0x0311,
|
|
// SERVICE_TYPE_TUNNEL_REQ = 0x0420,
|
|
// SERVICE_TYPE_TUNNEL_ACK = 0x0421,
|
|
SERVICE_TYPE_ROUTE_IND = 0x0530,
|
|
SERVICE_TYPE_ROUTE_LOST = 0x0531,
|
|
SERVICE_TYPE_ROUTE_BUSY = 0x0532,
|
|
} knxnet_service_type_t;
|
|
|
|
// cEMI frame header (external message interface)
|
|
typedef struct __attribute__ ((packed)) {
|
|
uint8_t messageCode;
|
|
// optional immediately following additional bytes, usually =0. fixed to 0 in cEMI management messages
|
|
uint8_t additionalInfoLength;
|
|
} knxnet_cemi_header_t;
|
|
|
|
/* cEMI message codes. */
|
|
typedef enum {
|
|
// MESSAGE_CODE_BUSMON_IND = 0x2B,
|
|
MESSAGE_CODE_DATA_REQ = 0x11,
|
|
MESSAGE_CODE_DATA_CON = 0x2E,
|
|
MESSAGE_CODE_DATA_IND = 0x29,
|
|
// MESSAGE_CODE_RAW_REQ = 0x10,
|
|
// MESSAGE_CODE_RAW_CON = 0x2D,
|
|
// MESSAGE_CODE_RAW_IND = 0x2F,
|
|
// MESSAGE_CODE_POLLDATA_REQ = 0x13,
|
|
// MESSAGE_CODE_POLLDATA_CON = 0x25,
|
|
// MESSAGE_CODE_DATACONN_REQ = 0x41,
|
|
// MESSAGE_CODE_DATACONN_IND = 0x89,
|
|
// MESSAGE_CODE_DATAIND_REQ = 0x4A,
|
|
// MESSAGE_CODE_DATAIND_IND = 0x94,
|
|
// MESSAGE_CODE_PROPREAD_REQ = 0xFC,
|
|
// MESSAGE_CODE_PROPREAD_CON = 0xFB,
|
|
// MESSAGE_CODE_PROPWRITE_REQ = 0xF6,
|
|
// MESSAGE_CODE_PROPWRITE_CON = 0xF5,
|
|
// MESSAGE_CODE_PROPINFO_IND = 0xF7,
|
|
// MESSAGE_CODE_FUNCPROPCMD_REQ = 0xF8,
|
|
// MESSAGE_CODE_FUNCPROPSTATEREAD_REQ = 0xF9,
|
|
// MESSAGE_CODE_FUNCPROP_CON = 0xFA,
|
|
// MESSAGE_CODE_RESET_IND = 0xF0,
|
|
// MESSAGE_CODE_RESET_REQ = 0xF1,
|
|
} knxnet_message_code_t;
|
|
|
|
// L_Data services header
|
|
typedef struct __attribute__ ((packed)) {
|
|
union {
|
|
uint8_t raw;
|
|
struct {
|
|
#if __BYTE_ORDER == __BIG_ENDIAN
|
|
bool frameType: 1; // 0=extended, 1=standard
|
|
bool reserved: 1;
|
|
bool repeat: 1; // 0=repeat, 1=do not repeat
|
|
bool systemBroadcast: 1; // 0=system broadcast, 1=broadcast
|
|
uint8_t priority: 2; // 0=system, 1=normal, 2=urgent, 3=low
|
|
bool acknowledgeRequest: 1; // 1=ack requested
|
|
bool confirm: 1; // 0=no error, 1=error
|
|
#else
|
|
bool confirm: 1; // 0=no error, 1=error
|
|
bool acknowledgeRequest: 1; // 1=ack requested
|
|
uint8_t priority: 2; // 0=system, 1=normal, 2=urgent, 3=low
|
|
bool systemBroadcast: 1; // 0=system broadcast, 1=broadcast
|
|
bool repeat: 1; // 0=repeat, 1=do not repeat
|
|
bool reserved: 1;
|
|
bool frameType: 1; // 0=extended, 1=standard
|
|
#endif
|
|
};
|
|
} controlField1;
|
|
union {
|
|
uint8_t raw;
|
|
struct {
|
|
#if __BYTE_ORDER == __BIG_ENDIAN
|
|
bool addressType: 1; // 0=individual, 1=group
|
|
uint8_t hopCount: 3;
|
|
uint8_t extendedFrameFormat: 4; // 0=standard frame, 0xf=escape
|
|
#else
|
|
uint8_t extendedFrameFormat: 4; // 0=standard frame, 0xf=escape
|
|
uint8_t hopCount: 3;
|
|
bool addressType: 1; // 0=individual, 1=group
|
|
#endif
|
|
};
|
|
} controlField2;
|
|
uint16be_t sourceAddress;
|
|
uint16be_t destinationAddress;
|
|
uint8_t informationLength; // number of NPDU octets (not including the TPCI/APCI octet)
|
|
} knxnet_l_data_header_t;
|
|
|
|
|
|
typedef union __attribute__ ((packed)) {
|
|
uint8_t raw;
|
|
struct {
|
|
#if __BYTE_ORDER == __BIG_ENDIAN
|
|
bool controlFlag: 1; // 0=data, 1=control
|
|
bool numbered: 1; // 1=has sequence, 0=no sequence
|
|
uint8_t sequence: 4; // optional sequence number
|
|
uint8_t apci: 2; // highest 2 bits of APCI
|
|
#else
|
|
uint8_t apci: 2; // highest 2 bits of APCI
|
|
uint8_t sequence: 4; // optional sequence number
|
|
bool numbered: 1; // 1=has sequence, 0=no sequence
|
|
bool controlFlag: 1; // 0=data, 1=control
|
|
#endif
|
|
};
|
|
} knxnet_tpci_apci_t;
|
|
|
|
typedef struct __attribute__ ((packed)) {
|
|
uint8_t length;
|
|
uint8_t protocolCode; // 0x01=UDP over IPv4
|
|
uint32be_t ipAddressV4;
|
|
uint16be_t port;
|
|
} knxnet_hpai_t;
|
|
|
|
#define PROTOCOL_CODE_IPV4_UDP 0x01
|
|
|
|
typedef struct __attribute__ ((packed)) {
|
|
uint8_t length;
|
|
uint8_t descriptionCode; // 0x01=device info
|
|
uint8_t medium; // 0x20=IP
|
|
uint8_t status; // bit 0=programming mode
|
|
uint16be_t individualAddress;
|
|
uint16be_t projInstId;
|
|
uint8_t serial[6];
|
|
in_addr_t multicastAddress;
|
|
uint8_t macAddress[6];
|
|
unsigned char name[30];
|
|
} knxnet_dib_devinfo_t;
|
|
|
|
typedef struct __attribute__ ((packed)) {
|
|
uint8_t length;
|
|
uint8_t descriptionCode; // 0x02=services
|
|
struct {
|
|
uint8_t familyId;
|
|
uint8_t familyVersion;
|
|
}; // just one for now
|
|
} knxnet_dib_services_t;
|
|
|
|
// the default system port
|
|
#define SYSTEM_MULTICAST_PORT 3671
|
|
|
|
// the default system multicast address 224.0.23.12
|
|
#define SYSTEM_MULTICAST_IP_STR "224.0.23.12"
|
|
|
|
#define LAST_FRAME_TIMEOUT 2
|
|
|
|
class LastFrame {
|
|
friend class LastFrames;
|
|
|
|
public:
|
|
void set(uint8_t* data, size_t len, size_t lOffset, time_t now) {
|
|
if (len >= sizeof(m_data)) {
|
|
return;
|
|
}
|
|
memcpy(m_data, data, len);
|
|
m_len = len;
|
|
m_lOffset = lOffset;
|
|
m_time = now;
|
|
}
|
|
|
|
bool isValid(time_t now) {
|
|
return m_len && m_time >= now-LAST_FRAME_TIMEOUT;
|
|
}
|
|
|
|
bool isSameAs(uint8_t* data, size_t len, size_t lOffset, time_t now, bool isSend = false) {
|
|
if (!m_len || len != m_len || lOffset != m_lOffset) {
|
|
return false;
|
|
}
|
|
if (memcmp(data, m_data, len) == 0) {
|
|
m_time = now;
|
|
return true;
|
|
}
|
|
int oldHopCount = (m_data[lOffset+1]&0x70)>>4;
|
|
int newHopCount = (data[lOffset+1]&0x70)>>4;
|
|
if (newHopCount < 6 // top hop count is always tolerated TODO bad idea?
|
|
&& memcmp(data, m_data, lOffset+1) == 0 // including first byte of l_data header
|
|
&& (data[lOffset+1]&~0x70) == (m_data[lOffset+1]&~0x70) // ignore hop count
|
|
&& (isSend ? newHopCount <= oldHopCount : newHopCount < oldHopCount) // decremented hop count?
|
|
&& memcmp(data+lOffset+2, m_data+lOffset+2, len-(lOffset+2)) == 0
|
|
) {
|
|
m_time = now;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void reset() {
|
|
m_time = 0;
|
|
}
|
|
|
|
private:
|
|
/** the last data. */
|
|
uint8_t m_data[256];
|
|
|
|
/** the length of the last data, or 0 for none. */
|
|
size_t m_len;
|
|
|
|
/** the offset to the L_Data. */
|
|
size_t m_lOffset;
|
|
|
|
/** the time of the last data, or 0 for none. */
|
|
time_t m_time;
|
|
};
|
|
|
|
|
|
#define CHECK_REPETITION_COUNT 4
|
|
|
|
class LastFrames {
|
|
public:
|
|
bool isRepetition(uint8_t* data, size_t len, size_t lOffset, time_t now, bool isSend = false) {
|
|
for (int i=0; i < CHECK_REPETITION_COUNT; i++) {
|
|
if (m_lastFrames[i].isValid(now)
|
|
&& m_lastFrames[i].isSameAs(data, len, lOffset, now, isSend)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void add(uint8_t* data, size_t len, size_t lOffset, time_t now) {
|
|
int oldestPos = -1;
|
|
time_t oldestAge = 0;
|
|
for (int i=0; i < CHECK_REPETITION_COUNT; i++) {
|
|
if (!m_lastFrames[i].isValid(now)) {
|
|
m_lastFrames[i].set(data, len, lOffset, now);
|
|
return;
|
|
}
|
|
if (oldestPos < 0 || m_lastFrames[i].m_time < oldestAge) {
|
|
oldestPos = i;
|
|
oldestAge = m_lastFrames[i].m_time;
|
|
}
|
|
}
|
|
m_lastFrames[oldestPos].set(data, len, lOffset, now);
|
|
}
|
|
|
|
void reset() {
|
|
for (int i=0; i < CHECK_REPETITION_COUNT; i++) {
|
|
m_lastFrames[i].reset();
|
|
}
|
|
}
|
|
|
|
private:
|
|
/** the list of the last telegrams. */
|
|
LastFrame m_lastFrames[CHECK_REPETITION_COUNT];
|
|
};
|
|
|
|
#ifdef DEBUG
|
|
#define PRINTF printf
|
|
|
|
// helper method to log received/sent telegrams
|
|
void logTelegram(bool sent, knxnet_cemi_header_t* c, knxnet_l_data_header_t* l, uint8_t* d) {
|
|
bool isGrp = l->controlField2.addressType;
|
|
PRINTF("%s msgcode=%2.2x, %d.%d.%d > %d%c%d%c%d, repeat=%s, ack=%s, hopcnt=%d, prio=%s, frame=%s, %sbroad, "
|
|
"confirm=%s, tpci/apci=%2.2x",
|
|
sent ? "send" : "recv",
|
|
c->messageCode,
|
|
l->sourceAddress.high>>4,
|
|
l->sourceAddress.high&0xf,
|
|
l->sourceAddress.low,
|
|
isGrp ? l->destinationAddress.high>>3 : l->destinationAddress.high>>4,
|
|
isGrp ? '/' : '.',
|
|
isGrp ? l->destinationAddress.high&0x1f : l->destinationAddress.high&0xf,
|
|
isGrp ? '/' : '.',
|
|
l->destinationAddress.low,
|
|
l->controlField1.repeat ? "yes" : "no",
|
|
l->controlField1.acknowledgeRequest ? "yes" : "no",
|
|
l->controlField2.hopCount,
|
|
l->controlField1.priority == 1 ? "normal" : l->controlField1.priority == 2 ? "urgent" :
|
|
l->controlField1.priority == 3 ? "low" : "system",
|
|
l->controlField1.frameType ? "std" : "ext",
|
|
l->controlField1.systemBroadcast ? "" : "sys ",
|
|
l->controlField1.confirm ? "error" : "no err",
|
|
d[0]);
|
|
if (d) {
|
|
PRINTF(", data=");
|
|
for (int i=0; i < l->informationLength; i++) {
|
|
PRINTF("%2.2x ", d[1+i]);
|
|
}
|
|
}
|
|
PRINTF("\n");
|
|
}
|
|
#else
|
|
#define PRINTF(...)
|
|
#define logTelegram(...)
|
|
#endif
|
|
|
|
|
|
/**
|
|
* A KnxConnection based on IP multicast as alternative to using libeibclient.
|
|
* This is still an incomplete KNXnet/IP implementation.
|
|
*/
|
|
class KnxNetConnection : public KnxConnection {
|
|
public:
|
|
/**
|
|
* Construct a new instance.
|
|
*/
|
|
explicit KnxNetConnection(const char* url)
|
|
: KnxConnection(), m_url(url), m_sock(0), m_programmingMode(false), m_addr(0) {}
|
|
|
|
/**
|
|
* Destructor.
|
|
*/
|
|
virtual ~KnxNetConnection() {
|
|
close();
|
|
}
|
|
|
|
// @copydoc
|
|
const char* getInfo() const override {
|
|
return "KNXnet/IP multicast";
|
|
}
|
|
|
|
// @copydoc
|
|
const char* open() override {
|
|
close();
|
|
int fd = socketConnect(m_url && m_url[0] ? m_url : SYSTEM_MULTICAST_IP_STR,
|
|
SYSTEM_MULTICAST_PORT, IPPROTO_UDP, nullptr, 0x02);
|
|
if (fd < 0) {
|
|
return "create socket";
|
|
}
|
|
// set non-blocking
|
|
if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) {
|
|
::close(fd);
|
|
return "non-blocking";
|
|
}
|
|
|
|
m_sock = fd;
|
|
return nullptr;
|
|
}
|
|
|
|
// @copydoc
|
|
bool isConnected() const override {
|
|
return m_sock != 0;
|
|
}
|
|
|
|
// @copydoc
|
|
void close() override {
|
|
if (m_sock) {
|
|
::close(m_sock);
|
|
m_sock = 0;
|
|
}
|
|
}
|
|
|
|
// @copydoc
|
|
int getPollFd() const override {
|
|
return m_sock;
|
|
}
|
|
|
|
// @copydoc
|
|
knx_transfer_t getPollData(int size, uint8_t* data, int* recvlen, knx_addr_t* src, knx_addr_t* dst) override {
|
|
uint8_t buf[128];
|
|
ssize_t slen = recv(m_sock, buf, sizeof(buf), 0);
|
|
if (slen < 0 || static_cast<unsigned>(slen) < sizeof(knxnet_header_t)) {
|
|
PRINTF("#skip recv short hdr len=%d\n", len);
|
|
return KNX_TRANSFER_NONE;
|
|
}
|
|
size_t len = static_cast<unsigned>(slen);
|
|
auto h = (knxnet_header_t*)buf;
|
|
if (h->headerLength != sizeof(knxnet_header_t) || h->protocolVersion != 0x10) {
|
|
PRINTF("#skip recv short/proto len=%d\n", len);
|
|
return KNX_TRANSFER_NONE;
|
|
}
|
|
switch (htons(h->serviceTypeIdentifier.raw)) {
|
|
case SERVICE_TYPE_ROUTE_IND:
|
|
// expected value
|
|
break;
|
|
// case SERVICE_TYPE_SEARCH_REQ:
|
|
// return KNX_TRANSFER_NONE;
|
|
// case SERVICE_TYPE_DESC_REQ:
|
|
// return KNX_TRANSFER_NONE;
|
|
default:
|
|
PRINTF("#skip recv service=%4.4x\n", htons(h->serviceTypeIdentifier.raw));
|
|
return KNX_TRANSFER_NONE;
|
|
}
|
|
// routing indication
|
|
size_t totalLen = htons(h->totalLength.raw);
|
|
if (len < totalLen || len < sizeof(knxnet_header_t)+sizeof(knxnet_cemi_header_t)) {
|
|
PRINTF("#skip recv short cemi len=%d\n", len);
|
|
return KNX_TRANSFER_NONE;
|
|
}
|
|
auto c = (knxnet_cemi_header_t*)(((uint8_t*)h)+sizeof(knxnet_header_t));
|
|
if (c->messageCode != MESSAGE_CODE_DATA_IND) {
|
|
PRINTF("#skip recv msgcode=%2.2x\n", c->messageCode);
|
|
return KNX_TRANSFER_NONE;
|
|
}
|
|
auto lOffset = sizeof(knxnet_header_t)+sizeof(knxnet_cemi_header_t)+c->additionalInfoLength;
|
|
ssize_t dataLen = totalLen - (lOffset+sizeof(knxnet_l_data_header_t));
|
|
if (dataLen < 0) {
|
|
PRINTF("#skip recv short data len=%d\n", len);
|
|
return KNX_TRANSFER_NONE;
|
|
}
|
|
auto l = (knxnet_l_data_header_t*)(((uint8_t*)h)+lOffset);
|
|
auto d = ((uint8_t*)l)+sizeof(knxnet_l_data_header_t);
|
|
if (!l->controlField1.frameType || !l->controlField1.systemBroadcast) {
|
|
// not a regular standard frame broadcast
|
|
PRINTF("#skip recv irregular frame len=%d\n", len);
|
|
return KNX_TRANSFER_NONE;
|
|
}
|
|
if (m_addr && (!l->controlField2.addressType && htons(l->destinationAddress.raw) != m_addr)) {
|
|
// ignore packets with individual addr destination other than our own
|
|
PRINTF("#skip recv not-own dest len=%d\n", len);
|
|
return KNX_TRANSFER_NONE;
|
|
}
|
|
if (m_addr && !l->controlField2.addressType && htons(l->sourceAddress.raw) == m_addr) {
|
|
// ignore own source packets
|
|
PRINTF("#skip recv own src len=%d\n", len);
|
|
return KNX_TRANSFER_NONE;
|
|
}
|
|
if (dataLen < 0 || dataLen < l->informationLength) {
|
|
PRINTF("#skip recv short payload len=%d\n", len);
|
|
return KNX_TRANSFER_NONE;
|
|
}
|
|
// check repeated frames
|
|
time_t now;
|
|
time(&now);
|
|
// PRINTF("getPoll len=%d, last sent len=%d\n", len, m_lastSentLen);
|
|
if (m_lastRecvFrames.isRepetition(buf, totalLen, lOffset, now)) {
|
|
// last recv packet repeated
|
|
PRINTF("#skip recv last recv len=%d\n", totalLen);
|
|
return KNX_TRANSFER_NONE;
|
|
}
|
|
if (m_lastSentFrames.isRepetition(buf, totalLen, lOffset, now, true)) {
|
|
// last sent packet re-received
|
|
PRINTF("#skip recv last sent len=%d\n", totalLen);
|
|
return KNX_TRANSFER_NONE;
|
|
}
|
|
logTelegram(false, c, l, d);
|
|
m_lastRecvFrames.add(buf, totalLen, lOffset, now);
|
|
// all fine
|
|
int ret = d[0];
|
|
if (l->controlField2.addressType) {
|
|
ret |= 0x100; // address type group
|
|
}
|
|
if (!(ret&0x80)) {
|
|
ret &= ~0x03; // remove two apci bits
|
|
}
|
|
if (ret&0x40) {
|
|
ret &= ~0x3c; // remove sequence number
|
|
}
|
|
*recvlen = size > dataLen ? dataLen : size;
|
|
memcpy(data, d, *recvlen); // including the TPCI/APCI octet 6
|
|
if (src) {
|
|
*src = htons(l->sourceAddress.raw);
|
|
}
|
|
if (dst) {
|
|
*dst = htons(l->destinationAddress.raw);
|
|
}
|
|
return (knx_transfer_t)ret;
|
|
}
|
|
|
|
// @copydoc
|
|
const char* sendGroup(knx_addr_t dst, int len, const uint8_t* data) override {
|
|
return send(KNX_TRANSFER_GROUP, dst, len, data);
|
|
}
|
|
|
|
// @copydoc
|
|
const char* sendTyp(knx_transfer_t typ, knx_addr_t dst, int len, const uint8_t* data) override {
|
|
return send(typ, dst, len, data);
|
|
}
|
|
|
|
// @copydoc
|
|
bool isProgrammable() const override { return true; };
|
|
|
|
private:
|
|
/**
|
|
* Send a message.
|
|
* @param typ the transfer type to send.
|
|
* @param dst the destination address.
|
|
* @param len the APDU length.
|
|
* @param data the APDU data buffer.
|
|
* @return nullptr on success, or an error message.
|
|
*/
|
|
const char* send(knx_transfer_t typ, knx_addr_t dst, int len, const uint8_t* data) {
|
|
uint8_t buf[128];
|
|
auto h = (knxnet_header_t*)buf;
|
|
h->headerLength = sizeof(knxnet_header_t);
|
|
h->protocolVersion = 0x10;
|
|
h->serviceTypeIdentifier.raw = htons(SERVICE_TYPE_ROUTE_IND);
|
|
// first byte of data is expected to hold the APCI upper byte:
|
|
size_t totalLen = sizeof(knxnet_header_t)+sizeof(knxnet_cemi_header_t)+sizeof(knxnet_l_data_header_t)+len;
|
|
h->totalLength.raw = htons(totalLen);
|
|
auto c = (knxnet_cemi_header_t*)(buf+sizeof(knxnet_header_t));
|
|
c->messageCode = MESSAGE_CODE_DATA_IND;
|
|
c->additionalInfoLength = 0;
|
|
auto lOffset = sizeof(knxnet_header_t)+sizeof(knxnet_cemi_header_t)+c->additionalInfoLength;
|
|
auto l = (knxnet_l_data_header_t*)(((uint8_t*)h)+lOffset);
|
|
l->controlField1.raw = 0xbc; // standard frame, no repeat, broadcast, low prio, no ack, no err
|
|
l->controlField2.raw = 0xe0; // group address, hop count 6, standard frame
|
|
l->controlField2.addressType = (typ&0x100) != 0;
|
|
l->sourceAddress.raw = htons(m_addr);
|
|
l->destinationAddress.raw = htons(dst);
|
|
if (typ&0x100) {
|
|
// ensure at least default individual address
|
|
if (!m_addr) {
|
|
l->sourceAddress.raw = 0xffff; // for "unregistered device" in S-Mode
|
|
}
|
|
}
|
|
l->informationLength = len-1; // subtracting the TPCI/APCI
|
|
uint8_t* d = buf+sizeof(knxnet_header_t)+sizeof(knxnet_cemi_header_t)+sizeof(knxnet_l_data_header_t);
|
|
// first byte of data is expected to hold the APCI upper byte, copy remainder:
|
|
memcpy(d, data, len);
|
|
int tpci = typ&0xff; // TPCI/APCI
|
|
if ((typ&0x080) == 0) {
|
|
tpci |= (d[0]&0x03); // highest 2 bits of APCI
|
|
}
|
|
if (typ&0x040) {
|
|
tpci |= d[0]&((0x0f) << 2); // SeqNo
|
|
}
|
|
d[0] = tpci;
|
|
logTelegram(true, c, l, d);
|
|
ssize_t sent = ::send(m_sock, buf, totalLen, MSG_NOSIGNAL);
|
|
if (sent < 0) {
|
|
return "send error";
|
|
}
|
|
time_t now;
|
|
time(&now);
|
|
m_lastSentFrames.add(buf, totalLen, lOffset, now);
|
|
return nullptr;
|
|
}
|
|
|
|
// @copydoc
|
|
knx_addr_t getAddress() const override {
|
|
return m_addr;
|
|
}
|
|
|
|
// @copydoc
|
|
void setAddress(knx_addr_t address) override {
|
|
m_addr = address;
|
|
// flush duplication check buffers
|
|
m_lastRecvFrames.reset();
|
|
m_lastSentFrames.reset();
|
|
}
|
|
|
|
// @copydoc
|
|
bool isProgrammingMode() const override {
|
|
return m_programmingMode;
|
|
}
|
|
|
|
// @copydoc
|
|
void setProgrammingMode(bool on) override {
|
|
m_programmingMode = on;
|
|
}
|
|
|
|
private:
|
|
/** the URL to connect to. */
|
|
const char* m_url;
|
|
|
|
/** the socket if connected, or 0. */
|
|
int m_sock;
|
|
|
|
/** true while in programming mode. */
|
|
bool m_programmingMode;
|
|
|
|
/** the own address, or 0 if not yet set. */
|
|
knx_addr_t m_addr;
|
|
|
|
/** the last received frames. */
|
|
LastFrames m_lastRecvFrames;
|
|
|
|
/** the last sent frames. */
|
|
LastFrames m_lastSentFrames;
|
|
};
|
|
|
|
} // namespace ebusd
|
|
|
|
#endif // LIB_KNX_KNXNET_H_
|