mirror of
https://github.com/thelsing/knx.git
synced 2025-10-12 11:15:54 +02:00
349 lines
13 KiB
C++
349 lines
13 KiB
C++
#include "cemi_server.h"
|
|
#include "cemi_frame.h"
|
|
#include "bau_systemB.h"
|
|
#include "usb_tunnel_interface.h"
|
|
#include "data_link_layer.h"
|
|
#include "string.h"
|
|
#include "bits.h"
|
|
#include <stdio.h>
|
|
|
|
CemiServer::CemiServer(BauSystemB& bau)
|
|
: _bau(bau),
|
|
_usbTunnelInterface(*this,
|
|
_bau.deviceObject().maskVersion(),
|
|
_bau.deviceObject().manufacturerId())
|
|
{
|
|
// The cEMI server will hand out the device address + 1 to the cEMI client (e.g. ETS),
|
|
// so that the device and the cEMI client/server connection(tunnel) can operate simultaneously.
|
|
_clientAddress = _bau.deviceObject().induvidualAddress() + 1;
|
|
}
|
|
|
|
void CemiServer::dataLinkLayer(DataLinkLayer& layer)
|
|
{
|
|
_dataLinkLayer = &layer;
|
|
}
|
|
|
|
uint16_t CemiServer::clientAddress() const
|
|
{
|
|
return _clientAddress;
|
|
}
|
|
|
|
void CemiServer::clientAddress(uint16_t value)
|
|
{
|
|
_clientAddress = value;
|
|
}
|
|
|
|
void CemiServer::dataConfirmationToTunnel(CemiFrame& frame)
|
|
{
|
|
print("L_data_con: src: ");
|
|
print(frame.sourceAddress(), HEX);
|
|
print(" dst: ");
|
|
print(frame.destinationAddress(), HEX);
|
|
|
|
printHex(" frame: ", frame.data(), frame.dataLength());
|
|
|
|
_usbTunnelInterface.sendCemiFrame(frame);
|
|
}
|
|
|
|
void CemiServer::dataIndicationToTunnel(CemiFrame& frame)
|
|
{
|
|
#if MEDIUM_TYPE == 2
|
|
|
|
uint8_t data[frame.dataLength() + 10];
|
|
data[0] = L_data_ind; // Message Code
|
|
data[1] = 0x0A; // Total additional info length
|
|
data[2] = 0x02; // RF add. info: type
|
|
data[3] = 0x08; // RF add. info: length
|
|
data[4] = frame.rfInfo(); // RF add. info: info field (batt ok, bidir)
|
|
pushByteArray(frame.rfSerialOrDoA(), 6, &data[5]); // RF add. info:Serial or Domain Address
|
|
data[11] = frame.rfLfn(); // RF add. info: link layer frame number
|
|
memcpy(&data[12], &((frame.data())[2]), frame.dataLength() - 2);
|
|
#else
|
|
uint8_t data[frame.dataLength()];
|
|
memcpy(&data[0], frame.data(), frame.dataLength());
|
|
#endif
|
|
|
|
CemiFrame tmpFrame(data, sizeof(data));
|
|
|
|
print("L_data_ind: src: ");
|
|
print(tmpFrame.sourceAddress(), HEX);
|
|
print(" dst: ");
|
|
print(tmpFrame.destinationAddress(), HEX);
|
|
|
|
printHex(" frame: ", tmpFrame.data(), tmpFrame.dataLength());
|
|
tmpFrame.apdu().type();
|
|
|
|
_usbTunnelInterface.sendCemiFrame(tmpFrame);
|
|
}
|
|
|
|
/*
|
|
void CemiServer::localManagmentRequestFromTunnel(CemiFrame& frame)
|
|
{
|
|
// called from KNXNET/IP for Management Services
|
|
|
|
// Send response to IP data link layer
|
|
_dataLinkLayer->localManagmentResponseToTunnel();
|
|
}
|
|
*/
|
|
|
|
void CemiServer::frameReceived(CemiFrame& frame)
|
|
{
|
|
switch(frame.messageCode())
|
|
{
|
|
case L_data_req:
|
|
{
|
|
// Fill in the cEMI client address if the client sets
|
|
// source address to 0.
|
|
if(frame.sourceAddress() == 0x0000)
|
|
{
|
|
frame.sourceAddress(_clientAddress);
|
|
}
|
|
|
|
#if MEDIUM_TYPE == 2
|
|
// Check if we have additional info for RF
|
|
if (((frame.data())[1] == 0x0A) && // Additional info total length: we only handle one additional info of type RF
|
|
((frame.data())[2] == 0x02) && // Additional info type: RF
|
|
((frame.data())[3] == 0x08) ) // Additional info length of type RF: 8 bytes (fixed)
|
|
{
|
|
frame.rfInfo((frame.data())[4]);
|
|
// Use the values provided in the RF additonal info
|
|
if ( ((frame.data())[5] != 0x00) || ((frame.data())[6] != 0x00) || ((frame.data())[7] != 0x00) ||
|
|
((frame.data())[8] != 0x00) || ((frame.data())[9] != 0x00) || ((frame.data())[10] != 0x00) )
|
|
{
|
|
frame.rfSerialOrDoA(&((frame.data())[5]));
|
|
} // else leave the nullptr as it is
|
|
frame.rfLfn((frame.data())[11]);
|
|
}
|
|
|
|
// If the cEMI client does not provide a link layer frame number (LFN),
|
|
// we use our own counter.
|
|
// Note: There is another link layer frame number counter inside the RF data link layer class!
|
|
// That counter is solely for the local application!
|
|
// If we set a LFN here, the data link layer counter is NOT used!
|
|
if (frame.rfLfn() == 0xFF)
|
|
{
|
|
// Set Data Link Layer Frame Number
|
|
frame.rfLfn(_frameNumber);
|
|
// Link Layer frame number counts 0..7
|
|
_frameNumber = (_frameNumber + 1) & 0x7;
|
|
}
|
|
#endif
|
|
|
|
print("L_data_req: src: ");
|
|
print(frame.sourceAddress(), HEX);
|
|
print(" dst: ");
|
|
print(frame.destinationAddress(), HEX);
|
|
|
|
printHex(" frame: ", frame.data(), frame.dataLength());
|
|
|
|
_dataLinkLayer->dataRequestFromTunnel(frame);
|
|
break;
|
|
}
|
|
|
|
case M_PropRead_req:
|
|
{
|
|
print("M_PropRead_req: ");
|
|
|
|
uint16_t objectType;
|
|
popWord(objectType, &frame.data()[1]);
|
|
uint8_t objectInstance = frame.data()[3];
|
|
uint8_t propertyId = frame.data()[4];
|
|
uint32_t numberOfElements = frame.data()[5] >> 4;
|
|
uint16_t startIndex = frame.data()[6] | ((frame.data()[5]&0x0F)<<8);
|
|
uint8_t* data = nullptr;
|
|
uint32_t dataSize = 0;
|
|
|
|
print("ObjType: ");
|
|
print(objectType, DEC);
|
|
print(" ObjInst: ");
|
|
print(objectInstance, DEC);
|
|
print(" PropId: ");
|
|
print(propertyId, DEC);
|
|
print(" NoE: ");
|
|
print(numberOfElements, DEC);
|
|
print(" startIdx: ");
|
|
print(startIndex, DEC);
|
|
|
|
// propertyValueRead() allocates memory for the data! Needs to be deleted again!
|
|
_bau.propertyValueRead((ObjectType)objectType, objectInstance, propertyId, numberOfElements, startIndex, &data, dataSize);
|
|
|
|
// Patch result for device address in device object
|
|
// The cEMI server will hand out the device address + 1 to the cEMI client (e.g. ETS),
|
|
// so that the device and the cEMI client/server connection(tunnel) can operate simultaneously.
|
|
// KNX IP Interfaces which offer multiple simultaneous tunnel connections seem to operate the same way.
|
|
// Each tunnel has its own cEMI client address which is based on the main device address.
|
|
if (((ObjectType) objectType == OT_DEVICE) &&
|
|
(propertyId == PID_DEVICE_ADDR) &&
|
|
(numberOfElements == 1))
|
|
{
|
|
data[0] = (uint8_t) (_clientAddress & 0xFF);
|
|
}
|
|
else if (((ObjectType) objectType == OT_DEVICE) &&
|
|
(propertyId == PID_SUBNET_ADDR) &&
|
|
(numberOfElements == 1))
|
|
{
|
|
data[0] = (uint8_t) ((_clientAddress >> 8) & 0xFF);
|
|
}
|
|
|
|
if (data && dataSize && numberOfElements)
|
|
{
|
|
printHex(" <- data: ", data, dataSize);
|
|
println("");
|
|
|
|
// Prepare positive response
|
|
uint8_t responseData[7 + dataSize];
|
|
memcpy(responseData, frame.data(), 7);
|
|
memcpy(&responseData[7], data, dataSize);
|
|
|
|
CemiFrame responseFrame(responseData, sizeof(responseData));
|
|
responseFrame.messageCode(M_PropRead_con);
|
|
_usbTunnelInterface.sendCemiFrame(responseFrame);
|
|
|
|
delete[] data;
|
|
}
|
|
else
|
|
{
|
|
// Prepare negative response
|
|
uint8_t responseData[7 + 1];
|
|
memcpy(responseData, frame.data(), sizeof(responseData));
|
|
responseData[7] = Void_DP; // Set cEMI error code
|
|
responseData[5] = 0; // Set Number of elements to zero
|
|
|
|
printHex(" <- error: ", &responseData[7], 1);
|
|
println("");
|
|
|
|
CemiFrame responseFrame(responseData, sizeof(responseData));
|
|
responseFrame.messageCode(M_PropRead_con);
|
|
_usbTunnelInterface.sendCemiFrame(responseFrame);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case M_PropWrite_req:
|
|
{
|
|
print("M_PropWrite_req: ");
|
|
|
|
uint16_t objectType;
|
|
popWord(objectType, &frame.data()[1]);
|
|
uint8_t objectInstance = frame.data()[3];
|
|
uint8_t propertyId = frame.data()[4];
|
|
uint32_t numberOfElements = frame.data()[5] >> 4;
|
|
uint16_t startIndex = frame.data()[6] | ((frame.data()[5]&0x0F)<<8);
|
|
uint8_t* requestData = &frame.data()[7];
|
|
uint32_t requestDataSize = frame.dataLength() - 7;
|
|
|
|
print("ObjType: ");
|
|
print(objectType, DEC);
|
|
print(" ObjInst: ");
|
|
print(objectInstance, DEC);
|
|
print(" PropId: ");
|
|
print(propertyId, DEC);
|
|
print(" NoE: ");
|
|
print(numberOfElements, DEC);
|
|
print(" startIdx: ");
|
|
print(startIndex, DEC);
|
|
|
|
printHex(" -> data: ", requestData, requestDataSize);
|
|
|
|
// Patch request for device address in device object
|
|
if (((ObjectType) objectType == OT_DEVICE) &&
|
|
(propertyId == PID_DEVICE_ADDR) &&
|
|
(numberOfElements == 1))
|
|
{
|
|
// Temporarily store new cEMI client address in memory
|
|
// We also be sent back if the client requests it again
|
|
_clientAddress = (_clientAddress & 0xFF00) | requestData[0];
|
|
print("cEMI client address: ");
|
|
println(_clientAddress, HEX);
|
|
}
|
|
else if (((ObjectType) objectType == OT_DEVICE) &&
|
|
(propertyId == PID_SUBNET_ADDR) &&
|
|
(numberOfElements == 1))
|
|
{
|
|
// Temporarily store new cEMI client address in memory
|
|
// We also be sent back if the client requests it again
|
|
_clientAddress = (_clientAddress & 0x00FF) | (requestData[0] << 8);
|
|
print("cEMI client address: ");
|
|
println(_clientAddress, HEX);
|
|
}
|
|
else
|
|
{
|
|
_bau.propertyValueWrite((ObjectType)objectType, objectInstance, propertyId, numberOfElements, startIndex, requestData, requestDataSize);
|
|
}
|
|
|
|
if (numberOfElements)
|
|
{
|
|
// Prepare positive response
|
|
uint8_t responseData[7];
|
|
memcpy(responseData, frame.data(), sizeof(responseData));
|
|
|
|
println(" <- no error");
|
|
|
|
CemiFrame responseFrame(responseData, sizeof(responseData));
|
|
responseFrame.messageCode(M_PropWrite_con);
|
|
_usbTunnelInterface.sendCemiFrame(responseFrame);
|
|
}
|
|
else
|
|
{
|
|
// Prepare negative response
|
|
uint8_t responseData[7 + 1];
|
|
memcpy(responseData, frame.data(), sizeof(responseData));
|
|
responseData[7] = Illegal_Command; // Set cEMI error code
|
|
responseData[5] = 0; // Set Number of elements to zero
|
|
|
|
printHex(" <- error: ", &responseData[7], 1);
|
|
println("");
|
|
|
|
CemiFrame responseFrame(responseData, sizeof(responseData));
|
|
responseFrame.messageCode(M_PropWrite_con);
|
|
_usbTunnelInterface.sendCemiFrame(responseFrame);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case M_FuncPropCommand_req:
|
|
{
|
|
println("M_FuncPropCommand_req not implemented");
|
|
break;
|
|
}
|
|
|
|
case M_FuncPropStateRead_req:
|
|
{
|
|
println("M_FuncPropStateRead_req not implemented");
|
|
break;
|
|
}
|
|
|
|
case M_Reset_req:
|
|
{
|
|
println("M_Reset_req: sending M_Reset_ind");
|
|
// A real device reset does not work for USB or KNXNET/IP.
|
|
// Thus, M_Reset_ind is NOT mandatory for USB and KNXNET/IP.
|
|
// We just save all data to the EEPROM
|
|
_bau.writeMemory();
|
|
// Prepare response
|
|
uint8_t responseData[1];
|
|
CemiFrame responseFrame(responseData, sizeof(responseData));
|
|
responseFrame.messageCode(M_Reset_ind);
|
|
_usbTunnelInterface.sendCemiFrame(responseFrame);
|
|
break;
|
|
}
|
|
|
|
// we should never receive these: server -> client
|
|
case L_data_con:
|
|
case L_data_ind:
|
|
case M_PropInfo_ind:
|
|
case M_PropRead_con:
|
|
case M_PropWrite_con:
|
|
case M_FuncPropCommand_con:
|
|
//case M_FuncPropStateRead_con: // same value as M_FuncPropCommand_con
|
|
case M_Reset_ind:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CemiServer::loop()
|
|
{
|
|
_usbTunnelInterface.loop();
|
|
}
|