mirror of
https://github.com/thelsing/knx.git
synced 2025-08-13 13:46:20 +02:00
Save work
This commit is contained in:
parent
f6ace390ee
commit
dfeb087db8
@ -51,6 +51,10 @@ InterfaceObject* Bau27B0::getInterfaceObject(uint8_t idx)
|
||||
return nullptr;
|
||||
case 6:
|
||||
return &_rfMediumObj;
|
||||
#ifdef USE_USBIF
|
||||
case 7:
|
||||
return &_cemiServerObject;
|
||||
#endif
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -20,8 +20,13 @@ class Bau27B0 : public BauSystemB
|
||||
RfMediumObject _rfMediumObj;
|
||||
|
||||
uint8_t _descriptor[2] = {0x27, 0xB0};
|
||||
#ifdef USE_USBIF
|
||||
const uint32_t _ifObjs[8] = { 7, // length
|
||||
OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG, OT_RF_MEDIUM, OT_CEMI_SERVER};
|
||||
#else
|
||||
const uint32_t _ifObjs[7] = { 6, // length
|
||||
OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG, OT_RF_MEDIUM};
|
||||
#endif
|
||||
|
||||
void domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA,
|
||||
uint8_t* knxSerialNumber);
|
||||
|
@ -15,6 +15,10 @@ BauSystemB::BauSystemB(Platform& platform): _memory(platform), _addrTable(platfo
|
||||
_assocTable(platform), _groupObjTable(platform), _appProgram(platform),
|
||||
_platform(platform), _appLayer(_assocTable, *this),
|
||||
_transLayer(_appLayer, _addrTable), _netLayer(_transLayer)
|
||||
#ifdef USE_USBIF
|
||||
, _dlLayerUsb(_deviceObj, _addrTable, _netLayer, _platform),
|
||||
_cemiServer(_dlLayerUsb, *this)
|
||||
#endif
|
||||
{
|
||||
_appLayer.transportLayer(_transLayer);
|
||||
_transLayer.networkLayer(_netLayer);
|
||||
@ -23,11 +27,17 @@ BauSystemB::BauSystemB(Platform& platform): _memory(platform), _addrTable(platfo
|
||||
_memory.addSaveRestore(&_addrTable);
|
||||
_memory.addSaveRestore(&_assocTable);
|
||||
_memory.addSaveRestore(&_groupObjTable);
|
||||
#ifdef USE_USBIF
|
||||
_memory.addSaveRestore(&_cemiServerObject);
|
||||
#endif
|
||||
}
|
||||
|
||||
void BauSystemB::loop()
|
||||
{
|
||||
dataLinkLayer().loop();
|
||||
#ifdef USE_USBIF
|
||||
_dlLayerUsb.loop();
|
||||
#endif
|
||||
_transLayer.loop();
|
||||
sendNextGroupTelegram();
|
||||
nextRestartState();
|
||||
@ -41,6 +51,9 @@ bool BauSystemB::enabled()
|
||||
void BauSystemB::enabled(bool value)
|
||||
{
|
||||
dataLinkLayer().enabled(value);
|
||||
#ifdef USE_USBIF
|
||||
_dlLayerUsb.enabled(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
void BauSystemB::sendNextGroupTelegram()
|
||||
|
@ -12,6 +12,11 @@
|
||||
#include "tpuart_data_link_layer.h"
|
||||
#include "platform.h"
|
||||
#include "memory.h"
|
||||
#ifdef USE_USBIF
|
||||
#include "usb_data_link_layer.h"
|
||||
#include "cemi_server.h"
|
||||
#include "cemi_server_object.h"
|
||||
#endif
|
||||
|
||||
class BauSystemB : protected BusAccessUnit
|
||||
{
|
||||
@ -88,4 +93,9 @@ class BauSystemB : protected BusAccessUnit
|
||||
bool _configured = true;
|
||||
RestartState _restartState = Idle;
|
||||
uint32_t _restartDelay = 0;
|
||||
#ifdef USE_USBIF
|
||||
UsbDataLinkLayer _dlLayerUsb;
|
||||
CemiServer _cemiServer;
|
||||
CemiServerObject _cemiServerObject;
|
||||
#endif
|
||||
};
|
178
src/knx/cemi_server.cpp
Normal file
178
src/knx/cemi_server.cpp
Normal file
@ -0,0 +1,178 @@
|
||||
#include "cemi_server.h"
|
||||
#include "cemi_frame.h"
|
||||
#include "bau.h"
|
||||
#include "usb_data_link_layer.h"
|
||||
#include "string.h"
|
||||
#include "bits.h"
|
||||
#include <stdio.h>
|
||||
|
||||
CemiServer::CemiServer(UsbDataLinkLayer& dlLayer, BusAccessUnit& bau):
|
||||
_dlLayer(dlLayer),
|
||||
_bau(bau)
|
||||
{
|
||||
}
|
||||
|
||||
#pragma region DLL Callbacks
|
||||
|
||||
void CemiServer::propertyValueReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
|
||||
uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex)
|
||||
{
|
||||
CemiFrame frame(5);
|
||||
APDU& apdu = frame.apdu();
|
||||
apdu.type(PropertyValueRead);
|
||||
uint8_t* data = apdu.data();
|
||||
data += 1;
|
||||
data = pushByte(objectIndex, data);
|
||||
data = pushByte(propertyId, data);
|
||||
pushWord(startIndex & 0xfff, data);
|
||||
*data &= ((numberOfElements & 0xf) << 4);
|
||||
|
||||
individualSend(ack, hopType, priority, asap, apdu);
|
||||
}
|
||||
|
||||
void CemiServer::propertyValueReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
|
||||
uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length)
|
||||
{
|
||||
propertyDataSend(PropertyValueResponse, ack, priority, hopType, asap, objectIndex, propertyId, numberOfElements,
|
||||
startIndex, data, length);
|
||||
}
|
||||
|
||||
void CemiServer::propertyValueWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
|
||||
uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t * data, uint8_t length)
|
||||
{
|
||||
propertyDataSend(PropertyValueWrite, ack, priority, hopType, asap, objectIndex, propertyId, numberOfElements,
|
||||
startIndex, data, length);
|
||||
}
|
||||
|
||||
void CemiServer::propertyDescriptionReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
|
||||
uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex)
|
||||
{
|
||||
CemiFrame frame(4);
|
||||
APDU& apdu = frame.apdu();
|
||||
apdu.type(PropertyDescriptionRead);
|
||||
uint8_t* data = apdu.data();
|
||||
data[1] = objectIndex;
|
||||
data[2] = propertyId;
|
||||
data[3] = propertyIndex;
|
||||
individualSend(ack, hopType, priority, asap, apdu);
|
||||
}
|
||||
|
||||
void CemiServer::propertyDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
|
||||
uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type,
|
||||
uint16_t maxNumberOfElements, uint8_t access)
|
||||
{
|
||||
CemiFrame frame(8);
|
||||
APDU& apdu = frame.apdu();
|
||||
apdu.type(PropertyDescriptionResponse);
|
||||
uint8_t* data = apdu.data();
|
||||
data[1] = objectIndex;
|
||||
data[2] = propertyId;
|
||||
data[3] = propertyIndex;
|
||||
if (writeEnable)
|
||||
data[4] |= 0x80;
|
||||
data[4] |= (type & 0x3f);
|
||||
pushWord(maxNumberOfElements & 0xfff, data + 5);
|
||||
data[7] = access;
|
||||
individualSend(ack, hopType, priority, asap, apdu);
|
||||
}
|
||||
|
||||
|
||||
void CemiServer::propertyDataSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
|
||||
uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length)
|
||||
{
|
||||
CemiFrame frame(5 + length);
|
||||
APDU& apdu = frame.apdu();
|
||||
apdu.type(type);
|
||||
uint8_t* apduData = apdu.data();
|
||||
apduData += 1;
|
||||
apduData = pushByte(objectIndex, apduData);
|
||||
apduData = pushByte(propertyId, apduData);
|
||||
pushWord(startIndex & 0xfff, apduData);
|
||||
*apduData |= ((numberOfElements & 0xf) << 4);
|
||||
apduData += 2;
|
||||
if (length > 0)
|
||||
memcpy(apduData, data, length);
|
||||
}
|
||||
|
||||
void CemiServer::individualIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU & apdu)
|
||||
{
|
||||
uint8_t* data = apdu.data();
|
||||
switch (apdu.type())
|
||||
{
|
||||
case PropertyValueRead:
|
||||
{
|
||||
uint16_t startIndex;
|
||||
popWord(startIndex, data + 3);
|
||||
startIndex &= 0xfff;
|
||||
_bau.propertyValueReadIndication(priority, hopType, tsap, data[1], data[2], data[3] >> 4, startIndex);
|
||||
break;
|
||||
}
|
||||
case PropertyValueResponse:
|
||||
{
|
||||
uint16_t startIndex;
|
||||
popWord(startIndex, data + 3);
|
||||
startIndex &= 0xfff;
|
||||
_bau.propertyValueReadAppLayerConfirm(priority, hopType, tsap, data[1], data[2], data[3] >> 4,
|
||||
startIndex, data + 5, apdu.length() - 5);
|
||||
break;
|
||||
}
|
||||
case PropertyValueWrite:
|
||||
{
|
||||
uint16_t startIndex;
|
||||
popWord(startIndex, data + 3);
|
||||
startIndex &= 0xfff;
|
||||
_bau.propertyValueWriteIndication(priority, hopType, tsap, data[1], data[2], data[3] >> 4,
|
||||
startIndex, data + 5, apdu.length() - 5);
|
||||
break;
|
||||
}
|
||||
case PropertyDescriptionRead:
|
||||
_bau.propertyDescriptionReadIndication(priority, hopType, tsap, data[1], data[2], data[3]);
|
||||
break;
|
||||
case PropertyDescriptionResponse:
|
||||
_bau.propertyDescriptionReadAppLayerConfirm(priority, hopType, tsap, data[1], data[2], data[3],
|
||||
(data[4] & 0x80) > 0, data[4] & 0x3f, getWord(data + 5) & 0xfff, data[7]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CemiServer::individualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU & apdu, bool status)
|
||||
{
|
||||
uint8_t* data = apdu.data();
|
||||
switch (apdu.type())
|
||||
{
|
||||
case PropertyValueRead:
|
||||
{
|
||||
uint16_t startIndex;
|
||||
popWord(startIndex, data + 3);
|
||||
startIndex &= 0xfff;
|
||||
_bau.propertyValueReadLocalConfirm(ack, priority, hopType, tsap, data[1], data[2], data[3] >> 4,
|
||||
startIndex, status);
|
||||
break;
|
||||
}
|
||||
case PropertyValueResponse:
|
||||
{
|
||||
uint16_t startIndex;
|
||||
popWord(startIndex, data + 3);
|
||||
startIndex &= 0xfff;
|
||||
_bau.propertyValueReadResponseConfirm(ack, priority, hopType, tsap, data[1], data[2], data[3] >> 4,
|
||||
startIndex, data + 5, apdu.length() - 5, status);
|
||||
break;
|
||||
}
|
||||
case PropertyValueWrite:
|
||||
{
|
||||
uint16_t startIndex;
|
||||
popWord(startIndex, data + 3);
|
||||
startIndex &= 0xfff;
|
||||
_bau.propertyValueWriteLocalConfirm(ack, priority, hopType, tsap, data[1], data[2], data[3] >> 4,
|
||||
startIndex, data + 5, apdu.length() - 5, status);
|
||||
break;
|
||||
}
|
||||
case PropertyDescriptionRead:
|
||||
_bau.propertyDescriptionReadLocalConfirm(ack, priority, hopType, tsap, data[1], data[2], data[3], status);
|
||||
break;
|
||||
case PropertyDescriptionResponse:
|
||||
_bau.propertyDescriptionReadResponseConfirm(ack, priority, hopType, tsap, data[1], data[2], data[3],
|
||||
(data[4] & 0x80) > 0, data[4] & 0x3f, getWord(data + 5) & 0xfff, data[7], status);
|
||||
break;
|
||||
}
|
||||
}
|
54
src/knx/cemi_server.h
Normal file
54
src/knx/cemi_server.h
Normal file
@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "knx_types.h"
|
||||
|
||||
class UsbDataLinkLayer;
|
||||
class BusAccessUnit;
|
||||
/**
|
||||
* This is an implementation of the cEMI server as specified in @cite knx:3/6/3.
|
||||
* It provides methods for the BusAccessUnit to do different things and translates this
|
||||
* call to an cEMI frame and calls the correct method of the data link layer.
|
||||
* It also takes calls from data link layer, decodes the submitted cEMI frames and calls the corresponding
|
||||
* methods of the BusAccessUnit class.
|
||||
*/
|
||||
class CemiServer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* The constructor.
|
||||
* @param assocTable The AssociationTable is used to translate between asap (i.e. group objects) and group addresses.
|
||||
* @param bau methods are called here depending of the content of the APDU
|
||||
*/
|
||||
CemiServer(UsbDataLinkLayer& dlLayer, BusAccessUnit& bau);
|
||||
|
||||
// from data link layer
|
||||
#pragma region Data - Link - Layer - Callbacks
|
||||
void dataIndividualIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu);
|
||||
void dataIndividualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, bool status);
|
||||
#pragma endregion
|
||||
|
||||
#pragma region from bau
|
||||
void propertyValueReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
|
||||
uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex);
|
||||
void propertyValueReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t objectIndex,
|
||||
uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length);
|
||||
void propertyValueWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t objectIndex,
|
||||
uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length);
|
||||
void propertyDescriptionReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
|
||||
uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex);
|
||||
void propertyDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
|
||||
uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type,
|
||||
uint16_t maxNumberOfElements, uint8_t access);
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
void propertyDataSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
|
||||
uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data,
|
||||
uint8_t length);
|
||||
void individualIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu);
|
||||
void individualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, bool status);
|
||||
|
||||
UsbDataLinkLayer& _dlLayer;
|
||||
BusAccessUnit& _bau;
|
||||
};
|
104
src/knx/cemi_server_object.cpp
Normal file
104
src/knx/cemi_server_object.cpp
Normal file
@ -0,0 +1,104 @@
|
||||
#include <cstring>
|
||||
#include "cemi_server_object.h"
|
||||
#include "bits.h"
|
||||
|
||||
void CemiServerObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t& count, uint8_t* data)
|
||||
{
|
||||
switch (propertyId)
|
||||
{
|
||||
case PID_OBJECT_TYPE:
|
||||
pushWord(OT_CEMI_SERVER, data);
|
||||
break;
|
||||
case PID_MEDIUM_TYPE: // PDT_BITSET16
|
||||
#if MEDIUM_TYPE==0
|
||||
data[0] = 2; // TP1 supported
|
||||
#elif MEDIUM_TYPE==2
|
||||
data[0] = 16; // RF supported
|
||||
#elif MEDIUM_TYPE==5
|
||||
data[0] = 32; // IP supported
|
||||
#endif
|
||||
break;
|
||||
case PID_COMM_MODE: // PDT_ENUM8
|
||||
// See KNX spec. cEMI 3/6/3 p.110
|
||||
data[0] = 0x00; // Only Data Link Layer mode supported and we do not allow switching (read-only)
|
||||
break;
|
||||
case PID_MEDIUM_AVAILABILITY: // PDT_BITSET16
|
||||
#if MEDIUM_TYPE==0
|
||||
data[0] = 2; // TP1 active
|
||||
#elif MEDIUM_TYPE==2
|
||||
data[0] = 16; // RF active
|
||||
#elif MEDIUM_TYPE==5
|
||||
data[0] = 32; // IP active
|
||||
#endif
|
||||
break;
|
||||
case PID_ADD_INFO_TYPES: // PDT_ENUM8[]
|
||||
pushByteArray((uint8_t*)_addInfoTypesTable, sizeof(_addInfoTypesTable), data);
|
||||
break;
|
||||
// Not supported yet
|
||||
break;
|
||||
default:
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CemiServerObject::writeProperty(PropertyID id, uint8_t start, uint8_t* data, uint8_t count)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case PID_COMM_MODE:
|
||||
//_commMode = data[0]; // TODO: only Data Link Layer supported for now
|
||||
// Property is also marked as read-only, normally it is read/write.
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t CemiServerObject::propertySize(PropertyID id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case PID_COMM_MODE:
|
||||
return 1;
|
||||
case PID_OBJECT_TYPE:
|
||||
case PID_MEDIUM_TYPE:
|
||||
case PID_MEDIUM_AVAILABILITY:
|
||||
return 2;
|
||||
case PID_ADD_INFO_TYPES:
|
||||
return sizeof(_addInfoTypesTable);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t* CemiServerObject::save(uint8_t* buffer)
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
|
||||
uint8_t* CemiServerObject::restore(uint8_t* buffer)
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static PropertyDescription _propertyDescriptions[] =
|
||||
{
|
||||
{ PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0 },
|
||||
{ PID_MEDIUM_TYPE, false, PDT_BITSET16, 1, ReadLv3 | WriteLv0 },
|
||||
{ PID_COMM_MODE, false, PDT_ENUM8, 1, ReadLv3 | WriteLv0 },
|
||||
{ PID_MEDIUM_AVAILABILITY, false, PDT_BITSET16, 1, ReadLv3 | WriteLv0 },
|
||||
{ PID_ADD_INFO_TYPES, false, PDT_ENUM8, 1, ReadLv3 | WriteLv0 }
|
||||
};
|
||||
static uint8_t _propertyCount = sizeof(_propertyDescriptions) / sizeof(PropertyDescription);
|
||||
|
||||
uint8_t CemiServerObject::propertyCount()
|
||||
{
|
||||
return _propertyCount;
|
||||
}
|
||||
|
||||
PropertyDescription* CemiServerObject::propertyDescriptions()
|
||||
{
|
||||
return _propertyDescriptions;
|
||||
}
|
23
src/knx/cemi_server_object.h
Normal file
23
src/knx/cemi_server_object.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "interface_object.h"
|
||||
|
||||
class CemiServerObject: public InterfaceObject
|
||||
{
|
||||
public:
|
||||
void readProperty(PropertyID id, uint32_t start, uint32_t& count, uint8_t* data);
|
||||
void writeProperty(PropertyID id, uint8_t start, uint8_t* data, uint8_t count);
|
||||
uint8_t propertySize(PropertyID id);
|
||||
uint8_t* save(uint8_t* buffer);
|
||||
uint8_t* restore(uint8_t* buffer);
|
||||
void readPropertyDescription(uint8_t propertyId, uint8_t& propertyIndex, bool& writeEnable, uint8_t& type, uint16_t& numberOfElements, uint8_t& access);
|
||||
|
||||
protected:
|
||||
uint8_t propertyCount();
|
||||
PropertyDescription* propertyDescriptions();
|
||||
private:
|
||||
// cEMI additional info types supported by this cEMI server: only 0x02 (RF Control Octet and Serial Number or DoA)
|
||||
uint8_t _addInfoTypesTable[1] = { 0x02 };
|
||||
uint8_t _commMode = 0x00;
|
||||
|
||||
};
|
@ -135,6 +135,16 @@ enum PropertyID
|
||||
PID_MSG_TRANSMIT_TO_KNX = 75,
|
||||
PID_FRIENDLY_NAME = 76,
|
||||
PID_ROUTING_BUSY_WAIT_TIME = 78,
|
||||
|
||||
/** cEMI Server Object */
|
||||
PID_MEDIUM_TYPE = 51,
|
||||
PID_COMM_MODE = 52,
|
||||
PID_MEDIUM_AVAILABILITY = 53,
|
||||
PID_ADD_INFO_TYPES = 54,
|
||||
PID_TIME_BASE = 55,
|
||||
PID_TRANSP_ENABLE = 56,
|
||||
PID_CLIENT_SNA = 57,
|
||||
PID_CLIENT_DEVICE_ADDRESS = 58
|
||||
};
|
||||
|
||||
enum LoadState
|
||||
|
374
src/knx/usb_data_link_layer.cpp
Normal file
374
src/knx/usb_data_link_layer.cpp
Normal file
@ -0,0 +1,374 @@
|
||||
#include "usb_data_link_layer.h"
|
||||
|
||||
#include "bits.h"
|
||||
#include "platform.h"
|
||||
#include "device_object.h"
|
||||
#include "address_table_object.h"
|
||||
#include "cemi_frame.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MIN(a, b) ((a < b) ? (a) : (b))
|
||||
|
||||
Adafruit_USBD_HID usb_hid;
|
||||
|
||||
// HID report descriptor using TinyUSB's template
|
||||
// Generic In Out with 64 bytes report (max)
|
||||
uint8_t const desc_hid_report[] =
|
||||
{
|
||||
//TUD_HID_REPORT_DESC_GENERIC_INOUT(64)
|
||||
0x06, 0xA0, 0xFF, // Usage Page (Vendor Defined 0xFFA0)
|
||||
0x09, 0x01, // Usage (0x01)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x09, 0x01, // Usage (0x01)
|
||||
0xA1, 0x00, // Collection (Physical)
|
||||
0x06, 0xA1, 0xFF, // Usage Page (Vendor Defined 0xFFA1)
|
||||
0x09, 0x03, // Usage (0x03)
|
||||
0x09, 0x04, // Usage (0x04)
|
||||
0x15, 0x80, // Logical Minimum (-128)
|
||||
0x25, 0x7F, // Logical Maximum (127)
|
||||
0x35, 0x00, // Physical Minimum (0)
|
||||
0x45, 0xFF, // Physical Maximum (-1)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x85, 0x01, // Report ID (1)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x09, 0x05, // Usage (0x05)
|
||||
0x09, 0x06, // Usage (0x06)
|
||||
0x15, 0x80, // Logical Minimum (-128)
|
||||
0x25, 0x7F, // Logical Maximum (127)
|
||||
0x35, 0x00, // Physical Minimum (0)
|
||||
0x45, 0xFF, // Physical Maximum (-1)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x85, 0x01, // Report ID (1)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0xC0, // End Collection
|
||||
0xC0 // End Collection
|
||||
};
|
||||
|
||||
uint16_t parseReport(uint8_t* data, uint16_t dataLength)
|
||||
{
|
||||
int8_t respDataSize = 0;
|
||||
bool forceSend = false;
|
||||
uint16_t length = 0;
|
||||
|
||||
popWord(length, &data[5]);
|
||||
length = MIN(dataLength, length);
|
||||
|
||||
Serial1.print("RX HID report: len: ");
|
||||
Serial1.println(length, DEC);
|
||||
|
||||
for (int i = 0; i < (length + data[4] + 3); i++)
|
||||
{
|
||||
if (data[i] < 16)
|
||||
Serial1.print("0");
|
||||
Serial1.print(data[i], HEX);
|
||||
Serial1.print(" ");
|
||||
}
|
||||
Serial1.println("");
|
||||
|
||||
if ( data[0] == 0x01 && // ReportID (fixed 0x01)
|
||||
data[1] == 0x13 && // PacketInfo must be 0x13 (SeqNo: 1, Type: 3)
|
||||
data[3] == 0x00 && // Protocol version (fixed 0x00)
|
||||
data[4] == 0x08 && // USB KNX Transfer Protocol Header Length (fixed 0x08)
|
||||
data[7] == 0x0F ) // Bus Access Server Feature (0x0F)
|
||||
{
|
||||
uint8_t serviceId = data[8];
|
||||
switch (serviceId)
|
||||
{
|
||||
case 0x01: // Device Feature Get
|
||||
{
|
||||
data[8] = 0x02; // Device Feature Response
|
||||
|
||||
uint8_t featureId = data[11];
|
||||
switch (featureId)
|
||||
{
|
||||
case 0x01: // Supported EMI types
|
||||
Serial1.println("Device Feature Get: Supported EMI types");
|
||||
respDataSize = 2;
|
||||
data[12] = 0x00; // USB KNX Transfer Protocol Body: Feature Data
|
||||
data[13] = 0x04; // USB KNX Transfer Protocol Body: Feature Data -> only cEMI supported
|
||||
break;
|
||||
case 0x02: // Host Device Descriptor Type 0
|
||||
Serial1.println("Device Feature Get: Host Device Descriptor Type 0");
|
||||
respDataSize = 2;
|
||||
data[12] = 0x00; // USB KNX Transfer Protocol Body: Feature Data
|
||||
data[13] = 0x00; // USB KNX Transfer Protocol Body: Feature Data
|
||||
break;
|
||||
case 0x03: // Bus connection status
|
||||
Serial1.println("Device Feature Get: Bus connection status");
|
||||
respDataSize = 1;
|
||||
data[12] = 1; // USB KNX Transfer Protocol Body: Feature Data
|
||||
break;
|
||||
case 0x04: // KNX manufacturer code
|
||||
Serial1.println("Device Feature Get: KNX manufacturer code");
|
||||
respDataSize = 2;
|
||||
data[12] = 0x00; // USB KNX Transfer Protocol Body: Feature Data
|
||||
data[13] = 0x00; // USB KNX Transfer Protocol Body: Feature Data -> Manufacturer Code
|
||||
break;
|
||||
case 0x05: // Active EMI type
|
||||
Serial1.println("Device Feature Get: Active EMI type");
|
||||
respDataSize = 1;
|
||||
data[12] = 0x03; // USB KNX Transfer Protocol Body: Feature Data -> cEMI ID
|
||||
break;
|
||||
default:
|
||||
respDataSize = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x03: // Device Feature Set
|
||||
{
|
||||
uint8_t featureId = data[11];
|
||||
switch (featureId)
|
||||
{
|
||||
case 0x05: // Active EMI type
|
||||
Serial1.print("Device Feature Set: Active EMI type: ");
|
||||
if (data[12] < 16)
|
||||
Serial1.print("0");
|
||||
Serial1.println(data[12], HEX); // USB KNX Transfer Protocol Body: Feature Data -> EMI TYPE ID
|
||||
break;
|
||||
// All other featureIds must not be set
|
||||
case 0x01: // Supported EMI types
|
||||
case 0x02: // Host Device Descriptor Type 0
|
||||
case 0x03: // Bus connection status
|
||||
case 0x04: // KNX manufacturer code
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// These are only sent from the device to the host
|
||||
case 0x02: // Device Feature Response
|
||||
case 0x04: // Device Feature Info
|
||||
case 0xEF: // reserved, not used
|
||||
case 0xFF: // reserved (ESCAPE for future extensions)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if ( data[0] == 0x01 && // ReportID (fixed 0x01)
|
||||
data[1] == 0x13 && // PacketInfo must be 0x13 (SeqNo: 1, Type: 3)
|
||||
data[3] == 0x00 && // Protocol version (fixed 0x00)
|
||||
data[4] == 0x08 && // USB KNX Transfer Protocol Header Length (fixed 0x08)
|
||||
data[7] == 0x01 && // KNX Tunneling (0x01)
|
||||
data[8] == 0x03 ) // EMI ID: 3 -> cEMI
|
||||
{
|
||||
uint8_t messageCode = data[11];
|
||||
switch(messageCode)
|
||||
{
|
||||
case 0xFC: // M_PropRead.req
|
||||
{
|
||||
data[11] = 0xFB; // M_PropRead.con
|
||||
if (data[15] == 0x34)
|
||||
{
|
||||
data[18] = 00; // PID_COMM_MODE: 0: Data Link Layer
|
||||
}
|
||||
else
|
||||
{
|
||||
data[16] = 0; // Number of elements must be 0 if negative response
|
||||
data[18] = 7; // Error code 7 (Void DP)
|
||||
}
|
||||
respDataSize = 1;
|
||||
break;
|
||||
}
|
||||
case 0xF6: // M_PropWrite.req
|
||||
{
|
||||
data[11] = 0xF5; // M_PropWrite.con
|
||||
if ((data[15] == 0x34) && (data[18] == 0x00))
|
||||
{
|
||||
respDataSize = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
data[16] = 0; // Number of elements must be 0 if negative response
|
||||
data[18] = 6; // Error code 6 (illegal command)
|
||||
respDataSize = 0;
|
||||
forceSend = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((respDataSize != 0) || forceSend)
|
||||
{
|
||||
data[2] += respDataSize; // HID Report Header: Packet Length
|
||||
data[6] += respDataSize; // USB KNX Transfer Protocol Header: Body Length
|
||||
|
||||
Serial1.print("TX HID report: len: ");
|
||||
Serial1.println((length + data[4] + 3) + respDataSize, DEC);
|
||||
|
||||
for (int i = 0; i < ((length + data[4] + 3) + respDataSize); i++)
|
||||
{
|
||||
if (data[i] < 16)
|
||||
Serial1.print("0");
|
||||
Serial1.print(data[i], HEX);
|
||||
Serial1.print(" ");
|
||||
}
|
||||
Serial1.println("");
|
||||
}
|
||||
|
||||
return respDataSize;
|
||||
}
|
||||
|
||||
// Invoked when received SET_REPORT control request or
|
||||
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
|
||||
void set_report_callback(uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize)
|
||||
{
|
||||
// we don't use multiple report and report ID
|
||||
(void) report_id;
|
||||
(void) report_type;
|
||||
|
||||
uint8_t tmpbuf[bufsize];
|
||||
memcpy(tmpbuf, buffer, bufsize);
|
||||
|
||||
if (parseReport(tmpbuf, bufsize) > 0)
|
||||
{
|
||||
usb_hid.sendReport(tmpbuf[0], &tmpbuf[1], bufsize);
|
||||
}
|
||||
}
|
||||
|
||||
void UsbDataLinkLayer::loop()
|
||||
{
|
||||
if (!_enabled)
|
||||
return;
|
||||
}
|
||||
|
||||
bool UsbDataLinkLayer::sendFrame(CemiFrame& frame)
|
||||
{
|
||||
if (!_enabled)
|
||||
return false;
|
||||
|
||||
// TODO: Is queueing really required?
|
||||
// According to the spec. the upper layer may only send a new L_Data.req if it received
|
||||
// the L_Data.con for the previous L_Data.req.
|
||||
addFrameTxQueue(frame);
|
||||
|
||||
// TODO: For now L_data.req is confirmed immediately (L_Data.con)
|
||||
// see 3.6.3 p.80: L_Data.con shall be generated AFTER transmission of the corresponsing frame
|
||||
// RF sender will never generate L_Data.con with C=1 (Error), but only if the TX buffer overflows
|
||||
// The RF sender cannot detect if the RF frame was transmitted successfully or not according to the spec.
|
||||
dataConReceived(frame, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
UsbDataLinkLayer::UsbDataLinkLayer(DeviceObject& devObj, AddressTableObject& addrTab,
|
||||
NetworkLayer& layer, Platform& platform)
|
||||
: DataLinkLayer(devObj, addrTab, layer, platform)
|
||||
{
|
||||
}
|
||||
|
||||
void UsbDataLinkLayer::frameBytesReceived(uint8_t* _buffer, uint16_t length)
|
||||
{
|
||||
// Prepare the cEMI frame
|
||||
CemiFrame frame(_buffer, length);
|
||||
/*
|
||||
frame.frameType(ExtendedFrame); // KNX RF uses only extended frame format
|
||||
frame.priority(SystemPriority); // Not used in KNX RF
|
||||
frame.ack(AckDontCare); // Not used in KNX RF
|
||||
frame.systemBroadcast(systemBroadcast); // Mapped from flag AddrExtensionType (KNX serial(0) or Domain Address(1))
|
||||
frame.hopCount(hopCount); // Hop count from routing counter
|
||||
frame.addressType(addressType); // Group address or individual address
|
||||
frame.rfSerialOrDoA(&rfPacketBuf[4]); // Copy pointer to field Serial or Domain Address (check broadcast flag what it is exactly)
|
||||
frame.rfInfo(rfPacketBuf[3]); // RF-info field (1 byte)
|
||||
frame.rfLfn(lfn); // Data link layer frame number (LFN field)
|
||||
*/
|
||||
|
||||
/*
|
||||
print(" len: ");
|
||||
print(newLength);
|
||||
|
||||
print(" data: ");
|
||||
printHex(" data: ", _buffer, newLength);
|
||||
*/
|
||||
frameRecieved(frame);
|
||||
}
|
||||
|
||||
void UsbDataLinkLayer::enabled(bool value)
|
||||
{
|
||||
if (value && !_enabled)
|
||||
{
|
||||
usb_hid.enableOutEndpoint(true);
|
||||
usb_hid.setPollInterval(2);
|
||||
usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report));
|
||||
usb_hid.setReportCallback(NULL, set_report_callback);
|
||||
|
||||
usb_hid.begin();
|
||||
|
||||
// wait until device mounted
|
||||
while( !USBDevice.mounted() ) delay(1);
|
||||
|
||||
_enabled = true;
|
||||
print("ownaddr ");
|
||||
println(_deviceObject.induvidualAddress(), HEX);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value && _enabled)
|
||||
{
|
||||
println("USB data link layer cannot be disabled once enabled!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool UsbDataLinkLayer::enabled() const
|
||||
{
|
||||
return _enabled;
|
||||
}
|
||||
|
||||
void UsbDataLinkLayer::addFrameTxQueue(CemiFrame& frame)
|
||||
{
|
||||
_tx_queue_frame_t* tx_frame = new _tx_queue_frame_t;
|
||||
|
||||
tx_frame->length = frame.totalLenght();
|
||||
tx_frame->data = new uint8_t[tx_frame->length];
|
||||
tx_frame->next = NULL;
|
||||
|
||||
/*
|
||||
print(" len: ");
|
||||
print(totalLength);
|
||||
|
||||
printHex(" data:", tx_frame->data, totalLength);
|
||||
*/
|
||||
if (_tx_queue.back == NULL)
|
||||
{
|
||||
_tx_queue.front = _tx_queue.back = tx_frame;
|
||||
}
|
||||
else
|
||||
{
|
||||
_tx_queue.back->next = tx_frame;
|
||||
_tx_queue.back = tx_frame;
|
||||
}
|
||||
}
|
||||
|
||||
bool UsbDataLinkLayer::isTxQueueEmpty()
|
||||
{
|
||||
if (_tx_queue.front == NULL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UsbDataLinkLayer::loadNextTxFrame(uint8_t** sendBuffer, uint16_t* sendBufferLength)
|
||||
{
|
||||
if (_tx_queue.front == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_tx_queue_frame_t* tx_frame = _tx_queue.front;
|
||||
*sendBuffer = tx_frame->data;
|
||||
*sendBufferLength = tx_frame->length;
|
||||
_tx_queue.front = tx_frame->next;
|
||||
|
||||
if (_tx_queue.front == NULL)
|
||||
{
|
||||
_tx_queue.back = NULL;
|
||||
}
|
||||
delete tx_frame;
|
||||
}
|
52
src/knx/usb_data_link_layer.h
Normal file
52
src/knx/usb_data_link_layer.h
Normal file
@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
#include "data_link_layer.h"
|
||||
|
||||
#define MAX_KNX_TELEGRAM_SIZE 263
|
||||
|
||||
extern Adafruit_USBD_HID usb_hid;
|
||||
|
||||
class UsbDataLinkLayer : public DataLinkLayer
|
||||
{
|
||||
using DataLinkLayer::_deviceObject;
|
||||
using DataLinkLayer::_groupAddressTable;
|
||||
using DataLinkLayer::_platform;
|
||||
|
||||
public:
|
||||
UsbDataLinkLayer(DeviceObject& devObj, AddressTableObject& addrTab, NetworkLayer& layer,
|
||||
Platform& platform);
|
||||
|
||||
void loop();
|
||||
void enabled(bool value);
|
||||
bool enabled() const;
|
||||
|
||||
private:
|
||||
bool _enabled = false;
|
||||
uint8_t _loopState = 0;
|
||||
|
||||
uint8_t _buffer[512];
|
||||
|
||||
uint8_t _frameNumber = 0;
|
||||
|
||||
struct _tx_queue_frame_t
|
||||
{
|
||||
uint8_t* data;
|
||||
uint16_t length;
|
||||
_tx_queue_frame_t* next;
|
||||
};
|
||||
|
||||
struct _tx_queue_t
|
||||
{
|
||||
_tx_queue_frame_t* front = NULL;
|
||||
_tx_queue_frame_t* back = NULL;
|
||||
} _tx_queue;
|
||||
|
||||
|
||||
void addFrameTxQueue(CemiFrame& frame);
|
||||
bool isTxQueueEmpty();
|
||||
void loadNextTxFrame(uint8_t** sendBuffer, uint16_t* sendBufferLength);
|
||||
bool sendFrame(CemiFrame& frame);
|
||||
void frameBytesReceived(uint8_t* buffer, uint16_t length);
|
||||
};
|
Loading…
Reference in New Issue
Block a user