LiveData: Initial implementation

Add support for live data subscription via Device::subscribeLiveData() and
Device::unsubscribeLiveData(). The live data API can be used to subscribe to
individual "signals", a full list of which can be found in LiveDataValueType.
This commit is contained in:
Kyle Johannes
2023-08-22 16:20:48 -04:00
committed by Kyle Schwarz
parent 018f1fac8e
commit 8d704b1bbb
20 changed files with 894 additions and 1 deletions
+5
View File
@@ -14,6 +14,7 @@
#include "icsneo/communication/message/linmessage.h"
#include "icsneo/communication/message/mdiomessage.h"
#include "icsneo/communication/message/extendeddatamessage.h"
#include "icsneo/communication/message/livedatamessage.h"
#include "icsneo/communication/command.h"
#include "icsneo/device/device.h"
#include "icsneo/communication/packet/canpacket.h"
@@ -32,6 +33,7 @@
#include "icsneo/communication/packet/supportedfeaturespacket.h"
#include "icsneo/communication/packet/mdiopacket.h"
#include "icsneo/communication/packet/genericbinarystatuspacket.h"
#include "icsneo/communication/packet/livedatapacket.h"
#include <iostream>
using namespace icsneo;
@@ -279,6 +281,9 @@ bool Decoder::decode(std::shared_ptr<Message>& result, const std::shared_ptr<Pac
case ExtendedCommand::GenericReturn:
result = std::make_shared<ExtendedResponseMessage>(resp.command, resp.returnCode);
return true;
case ExtendedCommand::LiveData:
result = HardwareLiveDataPacket::DecodeToMessage(packet->data, report);
return true;
default:
// No defined handler, treat this as a RawMessage
break;
+13
View File
@@ -1,6 +1,8 @@
#include "icsneo/communication/encoder.h"
#include "icsneo/communication/message/ethernetmessage.h"
#include "icsneo/communication/message/livedatamessage.h"
#include "icsneo/communication/message/main51message.h"
#include "icsneo/communication/packet/livedatapacket.h"
#include "icsneo/communication/packet/ethernetpacket.h"
#include "icsneo/communication/packet/iso9141packet.h"
#include "icsneo/communication/packet/canpacket.h"
@@ -197,6 +199,17 @@ bool Encoder::encode(const Packetizer& packetizer, std::vector<uint8_t>& result,
return false;
break;
}
case Message::Type::LiveData: {
auto liveDataMsg = std::dynamic_pointer_cast<LiveDataMessage>(message);
if(!liveDataMsg) {
report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error);
return false; // The message was not a properly formed LiveDataMessage
}
if(!HardwareLiveDataPacket::EncodeFromMessage(*liveDataMsg, result, report))
return false;
result = packetizer.packetWrap(result, false);
return true;
}
break;
}
+22
View File
@@ -0,0 +1,22 @@
#include "icsneo/communication/livedata.h"
namespace icsneo {
namespace LiveDataUtil {
LiveDataHandle getNewHandle() {
static LiveDataHandle currentHandle = 0;
++currentHandle;
if(currentHandle == std::numeric_limits<LiveDataHandle>::max()) {
EventManager::GetInstance().add(APIEvent::Type::LiveDataInvalidHandle, APIEvent::Severity::Error);
currentHandle = 1;
}
return currentHandle;
}
double liveDataValueToDouble(const LiveDataValue& val) {
constexpr double liveDataFixedPointToDouble = 0.00000000023283064365386962890625;
return val.value * liveDataFixedPointToDouble;
}
} // namespace LiveDataUtil
} // namespace icsneo
+15
View File
@@ -0,0 +1,15 @@
#include "icsneo/communication/message/livedatamessage.h"
#include "icsneo/communication/livedata.h"
namespace icsneo
{
void LiveDataCommandMessage::appendSignalArg(LiveDataValueType valueType) {
auto& arg = args.emplace_back(std::make_shared<LiveDataArgument>());
arg->objectType = LiveDataObjectType::MISC;
arg->objectIndex = 0u;
arg->signalIndex = 0u;
arg->valueType = valueType;
}
} // namespace icsneo
+129
View File
@@ -0,0 +1,129 @@
#include "icsneo/communication/packet/livedatapacket.h"
#include "icsneo/communication/message/livedatamessage.h"
#include <cstring>
#include <vector>
namespace icsneo {
std::shared_ptr<Message> HardwareLiveDataPacket::DecodeToMessage(const std::vector<uint8_t>& bytes, const device_eventhandler_t& report) {
if(bytes.empty() || (bytes.size() < (sizeof(LiveDataHeader) + sizeof(ExtResponseHeader)))) {
report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error);
return nullptr;
}
const auto header = reinterpret_cast<const ExtResponseHeader*>(bytes.data());
if(ExtendedCommand::LiveData != static_cast<ExtendedCommand>(header->command)) {
report(APIEvent::Type::LiveDataInvalidCommand, APIEvent::Severity::Error);
return nullptr;
}
const auto ldHeader = reinterpret_cast<const LiveDataHeader*>(bytes.data() + sizeof(ExtResponseHeader));
// Versioning check to avoid bad data interpretation between disparate libicsneo and firmware versions
if(icsneo::LiveDataUtil::LiveDataVersion != ldHeader->version) {
report(APIEvent::Type::LiveDataVersionMismatch, APIEvent::Severity::Error);
return nullptr;
}
switch(LiveDataCommand(ldHeader->cmd)) {
case LiveDataCommand::RESPONSE: {
auto retMsg = std::make_shared<LiveDataValueMessage>();
const auto responseBytes = reinterpret_cast<const LiveDataValueResponse*>(ldHeader);
retMsg->handle = responseBytes->handle;
retMsg->cmd = static_cast<LiveDataCommand>(responseBytes->cmd);
retMsg->numArgs = responseBytes->numArgs;
for(uint32_t i = 0; i < retMsg->numArgs; ++i) {
retMsg->values.emplace_back(std::make_shared<LiveDataValue>(responseBytes->values[i]));
}
return retMsg;
}
case LiveDataCommand::STATUS: {
auto retMsg = std::make_shared<LiveDataStatusMessage>();
const auto responseBytes = reinterpret_cast<const LiveDataStatusResponse*>(ldHeader);
retMsg->handle = responseBytes->handle;
retMsg->cmd = static_cast<LiveDataCommand>(responseBytes->cmd);
retMsg->status = responseBytes->status;
retMsg->requestedCommand = static_cast<LiveDataCommand>(responseBytes->requestedCommand);
return retMsg;
}
default: {
report(APIEvent::Type::LiveDataInvalidCommand, APIEvent::Severity::Error);
break;
}
}
return nullptr;
}
bool HardwareLiveDataPacket::EncodeFromMessage(LiveDataMessage& message, std::vector<uint8_t>& bytestream, const device_eventhandler_t& report) {
uint16_t payloadSize = 0;
switch(message.cmd) {
case LiveDataCommand::SUBSCRIBE: {
auto commandMsg = reinterpret_cast<LiveDataCommandMessage*>(&message);
const auto numArgs = commandMsg->args.size();
if(numArgs) {
payloadSize = static_cast<uint16_t>(sizeof(LiveDataSubscribe) + (sizeof(LiveDataArgument) * (numArgs-1)));
bytestream.resize((payloadSize + sizeof(ExtendedCommandHeader)),0);
LiveDataSubscribe* out = reinterpret_cast<LiveDataSubscribe*>(bytestream.data() + sizeof(ExtendedCommandHeader));
out->version = icsneo::LiveDataUtil::LiveDataVersion;
out->cmd = static_cast<uint32_t>(commandMsg->cmd);
if(!commandMsg->handle)
commandMsg->handle = LiveDataUtil::getNewHandle();
out->handle = commandMsg->handle;
out->numArgs = static_cast<uint32_t>(commandMsg->args.size());
out->freqMs = static_cast<uint32_t>(commandMsg->updatePeriod.count());
out->expireMs = static_cast<uint32_t>(commandMsg->expirationTime.count());
for(size_t i = 0; i < numArgs; ++i) {
out->args[i].objectType = commandMsg->args[i]->objectType;
out->args[i].objectIndex = commandMsg->args[i]->objectIndex;
out->args[i].signalIndex = commandMsg->args[i]->signalIndex;
out->args[i].valueType = commandMsg->args[i]->valueType;
}
} else {
report(APIEvent::Type::LiveDataInvalidArgument, APIEvent::Severity::Error);
return false;
}
break;
}
case LiveDataCommand::UNSUBSCRIBE: {
payloadSize = sizeof(LiveDataHeader);
bytestream.resize((payloadSize + sizeof(ExtendedCommandHeader)),0);
auto ldUnsubMsg = reinterpret_cast<LiveDataHeader*>(bytestream.data() + sizeof(ExtendedCommandHeader));
ldUnsubMsg->version = static_cast<uint32_t>(icsneo::LiveDataUtil::LiveDataVersion);
ldUnsubMsg->cmd = static_cast<uint32_t>(message.cmd);
ldUnsubMsg->handle = static_cast<uint32_t>(message.handle);
break;
}
case LiveDataCommand::CLEAR_ALL: {
payloadSize = sizeof(LiveDataHeader);
bytestream.resize((payloadSize + sizeof(ExtendedCommandHeader)),0);
auto clearMsg = reinterpret_cast<LiveDataHeader*>(bytestream.data() + sizeof(ExtendedCommandHeader));
clearMsg->version = static_cast<uint32_t>(icsneo::LiveDataUtil::LiveDataVersion);
clearMsg->cmd = static_cast<uint32_t>(message.cmd);
break;
}
default: {
report(APIEvent::Type::LiveDataInvalidCommand, APIEvent::Severity::Error);
return false;
}
}
// Send as a long message without setting the NetID to RED first
// Size in long format is the size of the entire packet
// So +1 for AA header, +1 for short format header (only netID)
// +2 for long format size, +1 for the Main51 extended command
// +2 for the extended subcommand, +2 for the payload length
uint16_t fullSize = static_cast<uint16_t>(1 + sizeof(ExtendedCommandHeader) + payloadSize);
ExtendedCommandHeader* header = reinterpret_cast<ExtendedCommandHeader*>(bytestream.data());
if(!header) {
report(APIEvent::Type::LiveDataEncoderError, APIEvent::Severity::Error);
return false;
}
header->netid = static_cast<uint8_t>(Network::NetID::Main51);
header->fullLength = fullSize;
header->command = static_cast<uint8_t>(Command::Extended);
header->extendedCommand = static_cast<uint16_t>(ExtendedCommand::LiveData);
header->payloadLength = payloadSize;
return true;
}
} // namespace icsneo