From 99a2ca4f0d147c0cb9aa566850c9a9c35df1922c Mon Sep 17 00:00:00 2001 From: Emily Brooks Date: Wed, 8 Oct 2025 19:49:03 +0000 Subject: [PATCH] Device: WiVI: Add VIN support --- communication/packet/wivicommandpacket.cpp | 48 ++++++- device/device.cpp | 122 +++++++++++++++++- .../message/wiviresponsemessage.h | 2 + .../communication/packet/wivicommandpacket.h | 12 +- include/icsneo/device/device.h | 7 + 5 files changed, 181 insertions(+), 10 deletions(-) diff --git a/communication/packet/wivicommandpacket.cpp b/communication/packet/wivicommandpacket.cpp index b9cc899..77d7ff4 100644 --- a/communication/packet/wivicommandpacket.cpp +++ b/communication/packet/wivicommandpacket.cpp @@ -31,24 +31,46 @@ std::shared_ptr WiVI::CommandPacket::DecodeToMessage(cons break; } case WiVI::Command::GetAll: { - if(bytestream.size() < sizeof(WiVI::CommandPacket::GetAll)) + if(bytestream.size() < sizeof(WiVI::CommandPacket::GetAllHeader)) return {}; - const auto& getAll = *reinterpret_cast(bytestream.data()); + const auto& getAll = *reinterpret_cast(bytestream.data()); msg->responseTo = WiVI::Command::GetAll; msg->info.emplace(); msg->info->sleepRequest = getAll.sleepRequest; msg->info->connectionTimeoutMinutes = getAll.connectionTimeoutMinutes; // Check that we have enough data for the capture infos - if(bytestream.size() < sizeof(WiVI::CommandPacket::GetAll) + (sizeof(WiVI::CaptureInfo) * getAll.numCaptureInfos)) + size_t captureInfosSize = sizeof(WiVI::CaptureInfo) * getAll.numCaptureInfos; + if(bytestream.size() < sizeof(WiVI::CommandPacket::GetAllHeader) + captureInfosSize) return {}; + const WiVI::CaptureInfo* const captureInfos = (const WiVI::CaptureInfo*)(bytestream.data() + sizeof(WiVI::CommandPacket::GetAllHeader)); msg->info->captures.resize(getAll.numCaptureInfos); for(uint16_t i = 0; i < getAll.numCaptureInfos; i++) - msg->info->captures[i] = getAll.captureInfos[i]; + msg->info->captures[i] = captureInfos[i]; + + + // New field vinAvail was added - check if it is present: + if(bytestream.size() >= sizeof(WiVI::CommandPacket::GetAllHeader) + captureInfosSize + 2) { + msg->info->vinAvailable = *(bytestream.data() + sizeof(WiVI::CommandPacket::GetAllHeader) + captureInfosSize); + } else { + msg->info->vinAvailable = 0; + } + break; } + case WiVI::Command::GetVIN: { + if (bytestream.size() < sizeof(WiVI::CommandPacket::GetVIN)) + return {}; + + const auto& getVIN = *reinterpret_cast(bytestream.data()); + msg->responseTo = WiVI::Command::GetVIN; + msg->vin = getVIN.VIN; + + break; + } + default: // Unknown command response return {}; } @@ -78,9 +100,9 @@ std::vector WiVI::CommandPacket::SetSignal::Encode(WiVI::SignalType typ return ret; } -std::vector WiVI::CommandPacket::GetAll::Encode() { - std::vector ret(sizeof(WiVI::CommandPacket::GetAll)); - auto& frame = *reinterpret_cast(ret.data()); +std::vector WiVI::CommandPacket::GetAllHeader::Encode() { + std::vector ret(sizeof(WiVI::CommandPacket::GetAllHeader)); + auto& frame = *reinterpret_cast(ret.data()); frame.header.cmd = WiVI::Command::GetAll; frame.header.length = sizeof(frame) - sizeof(frame.header); @@ -98,3 +120,15 @@ std::vector WiVI::CommandPacket::ClearUploads::Encode(const std::vector return ret; } + +std::vector WiVI::CommandPacket::GetVIN::Encode() +{ + std::vector ret(sizeof(WiVI::CommandPacket::GetVIN)); + auto& frame = *reinterpret_cast(ret.data()); + + frame.header.cmd = WiVI::Command::GetVIN; + frame.header.length = sizeof(frame) - sizeof(frame.header); + + return ret; +} + diff --git a/device/device.cpp b/device/device.cpp index 90e0cf3..8878a4e 100644 --- a/device/device.cpp +++ b/device/device.cpp @@ -1178,7 +1178,7 @@ void Device::wiviThreadBody() { // Use the command GetAll to get a WiVI::Info structure from the device const auto generic = com->waitForMessageSync([this]() { - return com->sendCommand(Command::WiVICommand, WiVI::CommandPacket::GetAll::Encode()); + return com->sendCommand(Command::WiVICommand, WiVI::CommandPacket::GetAllHeader::Encode()); }, filter, std::chrono::milliseconds(1000)); if(!generic || generic->type != Message::Type::WiVICommandResponse) { @@ -1290,6 +1290,22 @@ void Device::wiviThreadBody() { for(auto& cb : sleepRequestedCallbacks) cb.second = false; } + + // Process vin available callbacks + if (resp->info->vinAvailable & 1) { + for (auto& cb : vinAvailableCallbacks) { + if (!cb.second && cb.first) { + cb.second = true; + lk.unlock(); + try { + cb.first(); + } catch(...) { + report(APIEvent::Type::Unknown, APIEvent::Severity::Error); + } + lk.lock(); + } + } + } } } @@ -1464,6 +1480,110 @@ bool Device::allowSleep(bool remoteWakeup) { return true; } +Lifetime Device::addVINAvailableCallback(VINAvailableCallback cb) +{ + if(!isOpen()) { + report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); + return {}; + } + + if(!supportsWiVI()) { + report(APIEvent::Type::WiVINotSupported, APIEvent::Severity::Error); + return {}; + } + + std::lock_guard lk(wiviMutex); + if(!wiviThread.joinable()) { + // Start the thread + stopWiVIThread = false; + wiviThread = std::thread([this]() { wiviThreadBody(); }); + } + + size_t idx = 0; + for(; idx < vinAvailableCallbacks.size(); idx++) { + if(!vinAvailableCallbacks[idx].first) // Empty space (previously erased callback) + break; + } + + if(idx == vinAvailableCallbacks.size()) // Create a new space + vinAvailableCallbacks.emplace_back(std::move(cb), false); + else + vinAvailableCallbacks[idx] = { std::move(cb), false }; + + // Cleanup function to remove this sleep requested callback + return Lifetime([this, idx]() { + // TODO: Hold a weak ptr to the `this` instead of relying on the user to keep `this` valid + std::unique_lock lk2(wiviMutex); + vinAvailableCallbacks[idx].first = VINAvailableCallback(); + stopWiVIThreadIfNecessary(std::move(lk2)); + }); +} + +std::optional Device::isVINEnabled() const +{ + if(!isOpen()) { + report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); + return std::nullopt; + } + + if(!supportsWiVI()) { + report(APIEvent::Type::WiVINotSupported, APIEvent::Severity::Error); + return std::nullopt; + } + + static std::shared_ptr filter = std::make_shared(Message::Type::WiVICommandResponse); + // Hold this lock so the WiVI stack doesn't issue a WiVICommand at the same time as us + std::lock_guard lk(wiviMutex); + const auto generic = com->waitForMessageSync([this]() { + return com->sendCommand(Command::WiVICommand, WiVI::CommandPacket::GetSignal::Encode(WiVI::SignalType::VINEnabled)); + }, filter, std::chrono::milliseconds(1000)); + + if(!generic || generic->type != Message::Type::WiVICommandResponse) { + report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); + return std::nullopt; + } + + const auto resp = std::static_pointer_cast(generic); + if(!resp->success || !resp->value.has_value()) { + report(APIEvent::Type::ValueNotYetPresent, APIEvent::Severity::Error); + return std::nullopt; + } + + return *resp->value; +} + +std::optional Device::getVIN() const +{ + if(!isOpen()) { + report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); + return std::nullopt; + } + + if(!supportsWiVI()) { + report(APIEvent::Type::WiVINotSupported, APIEvent::Severity::Error); + return std::nullopt; + } + + static std::shared_ptr filter = std::make_shared(Message::Type::WiVICommandResponse); + std::lock_guard lk(wiviMutex); + const auto generic = com->waitForMessageSync([this]() { + return com->sendCommand(Command::WiVICommand, WiVI::CommandPacket::GetVIN::Encode()); + }, filter, std::chrono::milliseconds(1000)); + + if(!generic || generic->type != Message::Type::WiVICommandResponse) { + report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); + return std::nullopt; + } + + const auto resp = std::static_pointer_cast(generic); + if(!resp->success || !resp->vin.has_value()) { + report(APIEvent::Type::ValueNotYetPresent, APIEvent::Severity::Error); + return std::nullopt; + } + + return *resp->vin; +} + void Device::scriptStatusThreadBody() { std::unique_lock lk(scriptStatusMutex); diff --git a/include/icsneo/communication/message/wiviresponsemessage.h b/include/icsneo/communication/message/wiviresponsemessage.h index cf3343c..40e542e 100644 --- a/include/icsneo/communication/message/wiviresponsemessage.h +++ b/include/icsneo/communication/message/wiviresponsemessage.h @@ -17,6 +17,7 @@ struct Info { uint8_t sleepRequest; uint16_t connectionTimeoutMinutes; std::vector captures; + uint8_t vinAvailable; }; // The response for Command::WiVICommand @@ -27,6 +28,7 @@ public: std::optional responseTo; std::optional value; std::optional info; + std::optional vin; }; } // namespace WiVI diff --git a/include/icsneo/communication/packet/wivicommandpacket.h b/include/icsneo/communication/packet/wivicommandpacket.h index 54d49d8..d33055e 100644 --- a/include/icsneo/communication/packet/wivicommandpacket.h +++ b/include/icsneo/communication/packet/wivicommandpacket.h @@ -56,6 +56,7 @@ enum class Command : uint16_t { GetSignal = 0x0013, Result = 0x0014, GetPhysicalSignal = 0x0015, + GetVIN = 0x0016, }; enum class SignalType : uint16_t { // enumCoreMiniValueMiscValueType @@ -66,6 +67,7 @@ enum class SignalType : uint16_t { // enumCoreMiniValueMiscValueType ConnectionTimeout = 0x006e, TimeSinceLastMessageMs = 0x006f, UploadsPending = 0x0077, + VINEnabled = 0x007D, }; struct Upload { @@ -125,7 +127,7 @@ struct CommandPacket { CoreMiniFixedPointValue value; }; - struct GetAll { + struct GetAllHeader { static std::vector Encode(); Header header; @@ -133,7 +135,6 @@ struct CommandPacket { uint8_t sleepRequest; uint16_t connectionTimeoutMinutes; uint16_t numCaptureInfos; - CaptureInfo captureInfos[0]; }; struct ClearUploads { @@ -142,6 +143,13 @@ struct CommandPacket { Header header; uint8_t bitmask[0]; }; + + struct GetVIN { + static std::vector Encode(); + + Header header; + char VIN[17]; + }; }; } // namespace WiVI diff --git a/include/icsneo/device/device.h b/include/icsneo/device/device.h index 28fbaf3..a61a4b5 100644 --- a/include/icsneo/device/device.h +++ b/include/icsneo/device/device.h @@ -568,6 +568,12 @@ public: NODISCARD("If the Lifetime is not held, the callback will be immediately removed") Lifetime addLoggingCallback(ScriptStatusCallback cb) { return addScriptStatusCallback(ScriptStatus::Logging, std::move(cb)); } + typedef std::function VINAvailableCallback; + NODISCARD("If the Lifetime is not held, the callback will be immediately removed") + Lifetime addVINAvailableCallback(VINAvailableCallback cb); + std::optional isVINEnabled() const; + std::optional getVIN() const; + virtual std::vector> getFlexRayControllers() const { return {}; } void addExtension(std::shared_ptr&& extension); @@ -931,6 +937,7 @@ private: std::atomic wiviSleepRequested{false}; std::vector newCaptureCallbacks; std::vector< std::pair > sleepRequestedCallbacks; + std::vector> vinAvailableCallbacks; void wiviThreadBody(); void stopWiVIThreadIfNecessary(std::unique_lock lk);