#include #include "icsneo/api/eventmanager.h" #include "icsneo/communication/message/filter/main51messagefilter.h" #include "icsneo/communication/message/extendedresponsemessage.h" #include "icsneo/device/device.h" #include "icsneo/device/extensions/deviceextension.h" #include "icsneo/disk/fat.h" #ifdef _MSC_VER #pragma warning(disable : 4996) // STL time functions #endif using namespace icsneo; struct RTCCTIME { uint8_t FracSec;// --- fractions of seconds (00-99). Note that you can write only 0x00 here! uint8_t Sec;// --- Seconds (00-59) uint8_t Min;// --- (00-59) uint8_t Hour;// --- (00-23) uint8_t DOW;// --- (01-07) uint8_t Day;// --- (01-31) uint8_t Month;// --- (01-12) uint8_t Year;// --- (00-99) }; static const uint8_t fromBase36Table[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 }; static const char toBase36Table[36] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; static const uint32_t toBase36Powers[7] = { 1, 36, 1296, 46656, 1679616, 60466176, 2176782336 }; #define MIN_BASE36_SERIAL (16796160) #define MAX_SERIAL (2176782335) std::string Device::SerialNumToString(uint32_t serial) { if(serial == 0 || serial > MAX_SERIAL) return "0"; std::stringstream ss; if(serial >= MIN_BASE36_SERIAL) { for (auto i = 5; i >= 0; i--) { ss << toBase36Table[serial / toBase36Powers[i]]; serial = serial % toBase36Powers[i]; } } else { ss << serial; } return ss.str(); } uint32_t Device::SerialStringToNum(const std::string& serial) { if(Device::SerialStringIsNumeric(serial)) { try { return std::stoi(serial); } catch(...) { return 0; } } if(serial.length() != 6) return 0; // Non-numeric serial numbers should be 6 characters uint32_t ret = 0; for (auto i = 0; i < 6; i++) { ret *= 36; ret += fromBase36Table[(unsigned char)serial[i]]; } return ret; } bool Device::SerialStringIsNumeric(const std::string& serial) { if(serial.length() == 0) return false; if(serial.length() == 1) return isdigit(serial[0]); // Check the first two characters, at least one should be a character if we need to do a base36 conversion return isdigit(serial[0]) && isdigit(serial[1]); } Device::~Device() { if(isMessagePollingEnabled()) disableMessagePolling(); if(isOpen()) close(); } uint16_t Device::getTimestampResolution() const { return com->decoder->timestampResolution; } std::string Device::describe() const { std::stringstream ss; ss << getProductName() << ' ' << getSerial(); return ss.str(); } bool Device::enableMessagePolling() { if(isMessagePollingEnabled()) {// We are already polling report(APIEvent::Type::DeviceCurrentlyPolling, APIEvent::Severity::Error); return false; } messagePollingCallbackID = com->addMessageCallback(std::make_shared([this](std::shared_ptr message) { pollingContainer.enqueue(message); enforcePollingMessageLimit(); })); return true; } bool Device::disableMessagePolling() { if(!isMessagePollingEnabled()) { report(APIEvent::Type::DeviceNotCurrentlyPolling, APIEvent::Severity::Error); return false; // Not currently polling } auto ret = com->removeMessageCallback(messagePollingCallbackID); getMessages(); // Flush any messages still in the container messagePollingCallbackID = 0; return ret; } // Returns a pair of {vector, bool}, where the vector contains shared_ptrs to the returned msgs and the bool is whether or not the call was successful. std::pair>, bool> Device::getMessages() { std::vector> ret; bool retBool = getMessages(ret); return std::make_pair(ret, retBool); } bool Device::getMessages(std::vector>& container, size_t limit, std::chrono::milliseconds timeout) { if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return false; } if(!isOnline()) { report(APIEvent::Type::DeviceCurrentlyOffline, APIEvent::Severity::Error); return false; } if(!isMessagePollingEnabled()) { report(APIEvent::Type::DeviceNotCurrentlyPolling, APIEvent::Severity::Error); return false; } // A limit of zero indicates no limit if(limit == 0) limit = (size_t)-1; if(limit > (pollingContainer.size_approx() + 4)) limit = (pollingContainer.size_approx() + 4); if(container.size() < limit) container.resize(limit); size_t actuallyRead; if(timeout != std::chrono::milliseconds(0)) actuallyRead = pollingContainer.wait_dequeue_bulk_timed(container.data(), limit, timeout); else actuallyRead = pollingContainer.try_dequeue_bulk(container.data(), limit); if(container.size() > actuallyRead) container.resize(actuallyRead); return true; } void Device::enforcePollingMessageLimit() { while(pollingContainer.size_approx() > pollingMessageLimit) { std::shared_ptr throwAway; pollingContainer.try_dequeue(throwAway); report(APIEvent::Type::PollingMessageOverflow, APIEvent::Severity::EventWarning); } } bool Device::open(OpenFlags flags, OpenStatusHandler handler) { if(!com) { report(APIEvent::Type::Unknown, APIEvent::Severity::Error); return false; } if(!com->open()) { return false; } APIEvent::Type attemptErr = attemptToBeginCommunication(); if(attemptErr != APIEvent::Type::NoErrorFound) { // We could not communicate with the device, let's see if an extension can bool tryAgain = false; forEachExtension([&tryAgain, &flags, &handler](const std::shared_ptr& ext) -> bool { if(ext->onDeviceCommunicationDead(flags, handler)) tryAgain = true; return true; }); if(!tryAgain) { com->close(); report(attemptErr, APIEvent::Severity::Error); return false; // Extensions couldn't save us } attemptErr = attemptToBeginCommunication(); if(attemptErr != APIEvent::Type::NoErrorFound) { com->close(); report(attemptErr, APIEvent::Severity::Error); return false; } } bool block = false; forEachExtension([&block, &flags, &handler](const std::shared_ptr& ext) { if(ext->onDeviceOpen(flags, handler)) return true; block = true; return false; }); if(block) // Extensions say no return false; // Get component versions *after* the extension "onDeviceOpen" hooks (e.g. device reflashes) if (auto compVersions = com->getComponentVersionsSync()) componentVersions = std::move(*compVersions); else report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::EventWarning); if(!settings->disabled) { // Since we will not fail the open if a settings read fails, // downgrade any errors to warnings. Otherwise the error will // go unnoticed in the opening thread's getLastError buffer. const bool downgrading = EventManager::GetInstance().isDowngradingErrorsOnCurrentThread(); if(!downgrading) EventManager::GetInstance().downgradeErrorsOnCurrentThread(); settings->refresh(); if(!downgrading) EventManager::GetInstance().cancelErrorDowngradingOnCurrentThread(); } MessageFilter filter; filter.includeInternalInAny = true; internalHandlerCallbackID = com->addMessageCallback(std::make_shared(filter, [this](std::shared_ptr message) { handleInternalMessage(message); })); heartbeatThread = std::thread([this]() { EventManager::GetInstance().downgradeErrorsOnCurrentThread(); MessageFilter filter; filter.includeInternalInAny = true; std::atomic receivedMessage{false}; auto messageReceivedCallbackID = com->addMessageCallback(std::make_shared(filter, [&receivedMessage](std::shared_ptr message) { receivedMessage = true; })); // Give the device time to get situated auto i = 150; while(!stopHeartbeatThread && i != 0) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); i--; } while(!stopHeartbeatThread) { // Wait for 110ms for a possible heartbeat std::this_thread::sleep_for(std::chrono::milliseconds(110)); if(receivedMessage) { receivedMessage = false; } else { // Some communication, such as the bootloader and extractor interfaces, must // redirect the input stream from the device as it will no longer be in the // packet format we expect here. As a result, status updates will not reach // us here and suppressDisconnects() must be used. We don't want to request // a status and then redirect the stream, as we'll then be polluting an // otherwise quiet stream. This lock makes sure suppressDisconnects() will // block until we've either gotten our status update or disconnected from // the device. std::lock_guard lk(heartbeatMutex); if(heartbeatSuppressed()) continue; // No heartbeat received, request a status com->sendCommand(Command::RequestStatusUpdate); // Wait until we either received the message or reach max wait time for (uint32_t sleepTime = 0; sleepTime < 3500 && !receivedMessage; sleepTime += 50) std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Check if we got a message, and if not, if settings are being applied if(receivedMessage) { receivedMessage = false; } else { if(!stopHeartbeatThread && !isDisconnected()) { report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); com->driver->close(); } break; } } } com->removeMessageCallback(messageReceivedCallbackID); }); if(supportsLiveData()) clearAllLiveData(); return true; } APIEvent::Type Device::attemptToBeginCommunication() { versions.clear(); if(!afterCommunicationOpen()) { // Very unlikely, at the time of writing this only fails if rawWrite does. // If you're looking for this error, you're probably looking for if(!serial) below. // "Communication could not be established with the device. Perhaps it is not powered with 12 volts?" return getCommunicationNotEstablishedError(); } if(!com->sendCommand(Command::EnableNetworkCommunication, false)) return getCommunicationNotEstablishedError(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); auto serial = com->getSerialNumberSync(); int i = 0; while(!serial) { serial = com->getSerialNumberSync(); if(i++ > 5) break; } if(!serial) // "Communication could not be established with the device. Perhaps it is not powered with 12 volts?" return getCommunicationNotEstablishedError(); std::string currentSerial = getNeoDevice().serial; if(currentSerial != serial->deviceSerial) return APIEvent::Type::IncorrectSerialNumber; auto maybeVersions = com->getVersionsSync(); if(!maybeVersions) return getCommunicationNotEstablishedError(); else versions = std::move(*maybeVersions); return APIEvent::Type::NoErrorFound; } bool Device::close() { if(!com) { report(APIEvent::Type::Unknown, APIEvent::Severity::Error); return false; } stopHeartbeatThread = true; if(isOnline()) goOffline(); if(internalHandlerCallbackID) com->removeMessageCallback(internalHandlerCallbackID); internalHandlerCallbackID = 0; if(heartbeatThread.joinable()) heartbeatThread.join(); stopHeartbeatThread = false; forEachExtension([](const std::shared_ptr& ext) { ext->onDeviceClose(); return true; }); return com->close(); } bool Device::goOnline() { if(!com->sendCommand(Command::EnableNetworkCommunication, true)) return false; auto startTime = std::chrono::system_clock::now(); ledState = LEDState::Online; updateLEDState(); std::shared_ptr filter = std::make_shared(Network::NetID::Reset_Status); filter->includeInternalInAny = true; // Wait until communication is enabled or 5 seconds, whichever comes first while((std::chrono::system_clock::now() - startTime) < std::chrono::seconds(5)) { if(latestResetStatus && latestResetStatus->comEnabled) break; bool failOut = false; com->waitForMessageSync([this, &failOut]() { if(!com->sendCommand(Command::RequestStatusUpdate)) { failOut = true; return false; } return true; }, filter, std::chrono::milliseconds(100)); if(failOut) return false; } online = true; forEachExtension([](const std::shared_ptr& ext) { ext->onGoOnline(); return true; }); return true; } bool Device::goOffline() { forEachExtension([](const std::shared_ptr& ext) { ext->onGoOffline(); return true; }); if(isDisconnected()) { online = false; return true; } if(!com->sendCommand(Command::EnableNetworkCommunication, false)) return false; auto startTime = std::chrono::system_clock::now(); ledState = (latestResetStatus && latestResetStatus->cmRunning) ? LEDState::CoreMiniRunning : LEDState::Offline; updateLEDState(); std::shared_ptr filter = std::make_shared(Network::NetID::Reset_Status); filter->includeInternalInAny = true; // Wait until communication is disabled or 5 seconds, whichever comes first while((std::chrono::system_clock::now() - startTime) < std::chrono::seconds(5)) { if(latestResetStatus && !latestResetStatus->comEnabled) break; if(!com->sendCommand(Command::RequestStatusUpdate)) return false; com->waitForMessageSync(filter, std::chrono::milliseconds(100)); } online = false; return true; } int8_t Device::prepareScriptLoad() { if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return false; } static std::shared_ptr filter = std::make_shared(Network::NetID::CoreMiniPreLoad); std::lock_guard lg(diskLock); if(!com->sendCommand(Command::CoreMiniPreload)) return false; int8_t retVal = 0; while(retVal == 0) { auto generic = com->waitForMessageSync(filter, std::chrono::milliseconds(1000)); if(!generic) { report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); return false; } const auto resp = std::static_pointer_cast(generic); retVal = (int8_t)resp->data[0]; } return retVal; } bool Device::startScript(Disk::MemoryType memType) { if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return false; } std::lock_guard lg(diskLock); uint8_t location = static_cast(memType); auto generic = com->sendCommand(Command::LoadCoreMini, location); if(!generic) { report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); return false; } return true; } bool Device::stopScript() { if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return false; } std::lock_guard lg(diskLock); auto generic = com->sendCommand(Command::ClearCoreMini); if(!generic) { report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); return false; } return true; } bool Device::uploadCoremini(std::unique_ptr&& stream, Disk::MemoryType memType) { auto startAddress = getCoreminiStartAddress(memType); if(!startAddress) { return false; } auto connected = isLogicalDiskConnected(); if(!connected) { return false; // Already added an API error } if(!(*connected)) { report(APIEvent::Type::DiskNotConnected, APIEvent::Severity::Error); return false; } if(!stopScript()) { return false; } if(!stream || stream->bad()) { report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error); return false; } std::vector bin(std::istreambuf_iterator(*stream), {}); // Read the whole stream if(bin.size() < 4) { report(APIEvent::Type::BufferInsufficient, APIEvent::Severity::Error); return false; } uint16_t scriptVersion = *(uint16_t*)(&bin[2]); // Third and fourth byte are version number stored in little endian auto scriptStatus = getScriptStatus(); if(!scriptStatus) { return false; // Already added an API error } if(scriptStatus->coreminiVersion != scriptVersion) { // Version on device and script are not the same report(APIEvent::Type::CoreminiUploadVersionMismatch, APIEvent::Severity::Error); return false; } auto numWritten = writeLogicalDisk(*startAddress, (uint8_t*)bin.data(), static_cast(bin.size()), std::chrono::milliseconds(2000), memType); if(!numWritten) { return false; // Already added an API error } if(*numWritten == 0) { report(APIEvent::Type::FailedToWrite, APIEvent::Severity::Error); return false; // Failed to write } return true; } bool Device::clearScript() { if(!stopScript()) return false; std::vector clearData(512, 0xCD); uint64_t ScriptLocation = 0; //We only support a coremini in an SDCard, which is at the very beginning of the card auto written = writeLogicalDisk(ScriptLocation, clearData.data(), clearData.size()); return written > 0; } bool Device::transmit(std::shared_ptr frame) { if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return false; } if(!isOnline()) { report(APIEvent::Type::DeviceCurrentlyOffline, APIEvent::Severity::Error); return false; } if(!isSupportedTXNetwork(frame->network)) { report(APIEvent::Type::UnsupportedTXNetwork, APIEvent::Severity::Error); return false; } bool extensionHookedTransmit = false; bool transmitStatusFromExtension = false; forEachExtension([&](const std::shared_ptr& ext) { if(!ext->transmitHook(frame, transmitStatusFromExtension)) extensionHookedTransmit = true; return !extensionHookedTransmit; // false breaks out of the loop early }); if(extensionHookedTransmit) return transmitStatusFromExtension; std::vector packet; if(!com->encoder->encode(*com->packetizer, packet, frame)) return false; return com->sendPacket(packet); } bool Device::transmit(std::vector> frames) { for(auto& frame : frames) { if(!transmit(frame)) return false; } return true; } void Device::setWriteBlocks(bool blocks) { com->setWriteBlocks(blocks); } size_t Device::getNetworkCountByType(Network::Type type) const { size_t count = 0; for(const auto& net : getSupportedRXNetworks()) if(net.getType() == type) count++; return count; } // Indexed starting at one Network Device::getNetworkByNumber(Network::Type type, size_t index) const { size_t count = 0; for(const auto& net : getSupportedRXNetworks()) { if(net.getType() == type) { count++; if(count == index) return net; } } return Network::NetID::Invalid; } std::optional Device::readLogicalDisk(uint64_t pos, uint8_t* into, uint64_t amount, std::chrono::milliseconds timeout, Disk::MemoryType memType) { if(!into || timeout <= std::chrono::milliseconds(0)) { report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error); return std::nullopt; } if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return std::nullopt; } std::lock_guard lk(diskLock); if(diskReadDriver->getAccess() == Disk::Access::EntireCard && diskWriteDriver->getAccess() == Disk::Access::VSA) { // We have mismatched drivers, we need to add an offset to the diskReadDriver const auto offset = Disk::FindVSAInFAT([this, &timeout, &memType](uint64_t pos, uint8_t *into, uint64_t amount) { const auto start = std::chrono::steady_clock::now(); auto ret = diskReadDriver->readLogicalDisk(*com, report, pos, into, amount, timeout, memType); timeout -= std::chrono::duration_cast(std::chrono::steady_clock::now() - start); return ret; }); if(!offset.has_value()) return std::nullopt; diskReadDriver->setVSAOffset(*offset); } // This is needed for certain read drivers which take over the communication stream const auto lifetime = suppressDisconnects(); return diskReadDriver->readLogicalDisk(*com, report, pos, into, amount, timeout, memType); } std::optional Device::writeLogicalDisk(uint64_t pos, const uint8_t* from, uint64_t amount, std::chrono::milliseconds timeout, Disk::MemoryType memType) { if(!from || timeout <= std::chrono::milliseconds(0)) { report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error); return std::nullopt; } if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return std::nullopt; } std::lock_guard lk(diskLock); return diskWriteDriver->writeLogicalDisk(*com, report, *diskReadDriver, pos, from, amount, timeout, memType); } std::optional Device::isLogicalDiskConnected() { if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return std::nullopt; } // This doesn't *really* make sense here but because the disk read redirects the parser until it is done, we'll lock this // just to avoid the timeout. std::lock_guard lg(diskLock); const auto info = com->getLogicalDiskInfoSync(); if (!info) { report(APIEvent::Type::Timeout, APIEvent::Severity::Error); return std::nullopt; } return info->connected; } std::optional Device::getLogicalDiskSize() { if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return std::nullopt; } // This doesn't *really* make sense here but because the disk read redirects the parser until it is done, we'll lock this // just to avoid the timeout. std::lock_guard lg(diskLock); const auto info = com->getLogicalDiskInfoSync(); if (!info) { report(APIEvent::Type::Timeout, APIEvent::Severity::Error); return std::nullopt; } const auto reportedSize = info->getReportedSize(); if (diskReadDriver->getAccess() == Disk::Access::VSA) return reportedSize - diskReadDriver->getVSAOffset(); return reportedSize; } std::optional Device::getVSAOffsetInLogicalDisk() { if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return std::nullopt; } std::lock_guard lk(diskLock); if (diskReadDriver->getAccess() == Disk::Access::VSA || diskReadDriver->getAccess() == Disk::Access::None) return 0ull; auto offset = Disk::FindVSAInFAT([this](uint64_t pos, uint8_t *into, uint64_t amount) { return diskReadDriver->readLogicalDisk(*com, report, pos, into, amount); }); if(!offset.has_value()) return std::nullopt; if(diskReadDriver->getAccess() == Disk::Access::EntireCard && diskWriteDriver->getAccess() == Disk::Access::VSA) { // We have mismatched drivers, we need to add an offset to the diskReadDriver diskReadDriver->setVSAOffset(*offset); return 0ull; } return *offset; } std::optional Device::getDigitalIO(IO type, size_t number /* = 1 */) { if(number == 0) { // Start counting from 1 report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error); return false; } std::lock_guard lk(ioMutex); switch(type) { case IO::EthernetActivation: if(getEthernetActivationLineCount() < number) break; // ParameterOutOfRange assert(number == 1); // If you implement a device with more, you'll need to modify the accessor if(!ethActivationStatus.has_value()) report(APIEvent::Type::ValueNotYetPresent, APIEvent::Severity::Error); return ethActivationStatus; case IO::USBHostPower: if(getUSBHostPowerCount() < number) break; // ParameterOutOfRange assert(number == 1); // If you implement a device with more, you'll need to modify the accessor if(!usbHostPowerStatus.has_value()) report(APIEvent::Type::ValueNotYetPresent, APIEvent::Severity::Error); return usbHostPowerStatus; case IO::BackupPowerEnabled: if(!getBackupPowerSupported()) break; // ParameterOutOfRange assert(number == 1); // If you implement a device with more, you'll need to modify the accessor if(!backupPowerEnabled.has_value()) report(APIEvent::Type::ValueNotYetPresent, APIEvent::Severity::Error); return backupPowerEnabled; case IO::BackupPowerGood: if(!getBackupPowerSupported()) break; // ParameterOutOfRange assert(number == 1); // If you implement a device with more, you'll need to modify the accessor if(!backupPowerGood.has_value()) report(APIEvent::Type::ValueNotYetPresent, APIEvent::Severity::Error); return backupPowerGood; case IO::Misc: { bool found = false; for(const auto& misc : getMiscIO()) { if(misc.number == number) { found = misc.supportsDigitalIn; break; } } if(!found) break; // ParameterOutOfRange if(number > miscDigital.size()) break; // ParameterOutOfRange if(!miscDigital[number - 1].has_value()) report(APIEvent::Type::ValueNotYetPresent, APIEvent::Severity::Error); return miscDigital[number - 1]; } case IO::EMisc: { bool found = false; for(const auto& misc : getEMiscIO()) { if(misc.number == number) { found = misc.supportsDigitalIn; break; } } if(!found) break; // ParameterOutOfRange if(number > miscDigital.size()) break; // ParameterOutOfRange // If there is ever a device with overlapping misc IOs and emisc IOs, // you will need to make a new member variable for the emisc IOs. if(!miscDigital[number - 1].has_value()) report(APIEvent::Type::ValueNotYetPresent, APIEvent::Severity::Error); return miscDigital[number - 1]; } }; report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error); return std::nullopt; } bool Device::setDigitalIO(IO type, size_t number, bool value) { if(number == 0) { // Start counting from 1 report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error); return false; } std::lock_guard lk(ioMutex); switch(type) { case IO::EthernetActivation: if(getEthernetActivationLineCount() < number) break; // ParameterOutOfRange assert(number == 1); // If you implement a device with more, you'll need to modify the accessor ethActivationStatus = value; return com->sendCommand(Command::MiscControl, { uint8_t(1), uint8_t(value ? 1 : 0), // enetActivateSet, enetActivateValue uint8_t(0), uint8_t(0), // usbHostPowerSet, usbHostPowerValue uint8_t(0), uint8_t(0) // backupPowerSet, backupPowerValue }); case IO::USBHostPower: if(getUSBHostPowerCount() < number) break; // ParameterOutOfRange assert(number == 1); // If you implement a device with more, you'll need to modify the accessor usbHostPowerStatus = value; return com->sendCommand(Command::MiscControl, { uint8_t(0), uint8_t(0), // enetActivateSet, enetActivateValue uint8_t(1), uint8_t(value ? 1 : 0), // usbHostPowerSet, usbHostPowerValue uint8_t(0), uint8_t(0) // backupPowerSet, backupPowerValue }); case IO::BackupPowerEnabled: if(!getBackupPowerSupported()) break; // ParameterOutOfRange assert(number == 1); // If you implement a device with more, you'll need to modify the accessor backupPowerEnabled = value; return com->sendCommand(Command::MiscControl, { uint8_t(0), uint8_t(0), // enetActivateSet, enetActivateValue uint8_t(0), uint8_t(value ? 1 : 0), // usbHostPowerSet, usbHostPowerValue (set to work around firmware bug) uint8_t(1), uint8_t(value ? 1 : 0) // backupPowerSet, backupPowerValue }); case IO::BackupPowerGood: break; // Read-only, return ParameterOutOfRange case IO::Misc: case IO::EMisc: break; // Read-only for the moment, return ParameterOutOfRange }; report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error); return false; } std::optional Device::getAnalogIO(IO type, size_t number /* = 1 */) { if(number == 0) { // Start counting from 1 report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error); return false; } std::lock_guard lk(ioMutex); switch(type) { case IO::EthernetActivation: case IO::USBHostPower: case IO::BackupPowerEnabled: case IO::BackupPowerGood: break; case IO::Misc: { bool found = false; for(const auto& misc : getMiscIO()) { if(misc.number == number) { found = misc.supportsAnalogIn; break; } } if(!found) break; // ParameterOutOfRange if(number > miscAnalog.size()) break; // ParameterOutOfRange if(!miscAnalog[number - 1].has_value()) report(APIEvent::Type::ValueNotYetPresent, APIEvent::Severity::Error); return miscAnalog[number - 1]; } case IO::EMisc: { bool found = false; for(const auto& misc : getEMiscIO()) { if(misc.number == number) { found = misc.supportsAnalogIn; break; } } if(!found) break; // ParameterOutOfRange if(number > miscAnalog.size()) break; // ParameterOutOfRange // If there is ever a device with overlapping misc IOs and emisc IOs, // you will need to make a new member variable for the emisc IOs. if(!miscAnalog[number - 1].has_value()) report(APIEvent::Type::ValueNotYetPresent, APIEvent::Severity::Error); return miscAnalog[number - 1]; } }; report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error); return std::nullopt; } void Device::wiviThreadBody() { std::shared_ptr filter = std::make_shared(Message::Type::WiVICommandResponse); std::unique_lock lk(wiviMutex); // Disk access commands can std::unique_lock dl(diskLock); dl.unlock(); EventManager::GetInstance().downgradeErrorsOnCurrentThread(); bool first = true; while(!stopWiVIThread) { if(first) // Skip the first wait first = false; else stopWiVIcv.wait_for(lk, std::chrono::seconds(3)); // Use the command GetAll to get a WiVI::Info structure from the device dl.lock(); const auto generic = com->waitForMessageSync([this]() { return com->sendCommand(Command::WiVICommand, WiVI::CommandPacket::GetAll::Encode()); }, filter); dl.unlock(); if(!generic || generic->type != Message::Type::WiVICommandResponse) { report(APIEvent::Type::WiVIStackRefreshFailed, APIEvent::Severity::Error); continue; } const auto resp = std::static_pointer_cast(generic); if(!resp->success || !resp->info.has_value()) { report(APIEvent::Type::WiVIStackRefreshFailed, APIEvent::Severity::Error); continue; } // Now we know we have a WiVI::Info structure // Don't process captures unless there is a callback attached, // we don't want to clear any while nobody's listening. bool processCaptures = false; for(const auto& cb : newCaptureCallbacks) { if(cb) { processCaptures = true; break; } } if(processCaptures) { std::vector clearMasks; for (size_t i = 0; i < resp->info->captures.size(); i++) { const auto capture = resp->info->captures.at(i); if(capture.flags.uploadOverflow) report(APIEvent::Type::WiVIUploadStackOverflow, APIEvent::Severity::Error); const auto MaxUploads = sizeof(capture.uploadStack) / sizeof(capture.uploadStack[0]); auto uploadCount = capture.flags.uploadStackSize + 1u; if(uploadCount > MaxUploads) { report(APIEvent::Type::WiVIStackRefreshFailed, APIEvent::Severity::Error); uploadCount = MaxUploads; } for(size_t j = 0; j < uploadCount; j++) { const auto& upload = capture.uploadStack[j]; if(!upload.flags.pending) continue; // Not complete yet, don't notify // Schedule this upload to be cleared from the firmware's stack if(clearMasks.size() != resp->info->captures.size()) clearMasks.resize(resp->info->captures.size()); clearMasks[i] |= (1 << j); WiVIUpload wiviUpload {}; wiviUpload.captureIndex = capture.captureBlockIndex; wiviUpload.cellular = capture.flags.uploadOverCellular; wiviUpload.wifi = capture.flags.uploadOverWiFi; wiviUpload.isPrePost = capture.flags.isPrePost; wiviUpload.isPreTime = capture.flags.isPreTime; wiviUpload.preTriggerSize = capture.preTriggerSize; wiviUpload.priority = capture.flags.uploadPriority; wiviUpload.startSector = upload.startSector; wiviUpload.endSector = upload.endSector; // Notify the client for(const auto& cb : newCaptureCallbacks) { if(cb) { lk.unlock(); try { cb(wiviUpload); } catch(...) { report(APIEvent::Type::Unknown, APIEvent::Severity::Error); } lk.lock(); } } } } if(!clearMasks.empty()) { dl.lock(); const auto clearMasksGenericResp = com->waitForMessageSync([this, &clearMasks]() { return com->sendCommand(Command::WiVICommand, WiVI::CommandPacket::ClearUploads::Encode(clearMasks)); }, filter); dl.unlock(); if(!clearMasksGenericResp || clearMasksGenericResp->type != Message::Type::WiVICommandResponse || !std::static_pointer_cast(clearMasksGenericResp)->success) { report(APIEvent::Type::WiVIStackRefreshFailed, APIEvent::Severity::Error); } } } // Process sleep requests if(resp->info->sleepRequest & 1 /* sleep requested by VSSAL */) { // Notify any callers we haven't notified yet for(auto& cb : sleepRequestedCallbacks) { if(!cb.second && cb.first) { cb.second = true; lk.unlock(); try { cb.first(resp->info->connectionTimeoutMinutes); } catch(...) { report(APIEvent::Type::Unknown, APIEvent::Severity::Error); } lk.lock(); } } } else { // If the sleepRequest becomes 1 again we will notify again for(auto& cb : sleepRequestedCallbacks) cb.second = false; } } } void Device::stopWiVIThreadIfNecessary(std::unique_lock lk) { // The callbacks will be empty std::functions if they are removed for(const auto& cb : newCaptureCallbacks) { if(cb) return; // We still need the WiVI Thread } for(const auto& cb : sleepRequestedCallbacks) { if(cb.first) return; // We still need the WiVI Thread } stopWiVIThread = true; lk.unlock(); stopWiVIcv.notify_all(); wiviThread.join(); wiviThread = std::thread(); } Lifetime Device::addNewCaptureCallback(NewCaptureCallback 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 < newCaptureCallbacks.size(); idx++) { if(!newCaptureCallbacks[idx]) // Empty space (previously erased callback) break; } if(idx == newCaptureCallbacks.size()) // Create a new space newCaptureCallbacks.push_back(std::move(cb)); else newCaptureCallbacks[idx] = std::move(cb); // Cleanup function to remove this capture 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); newCaptureCallbacks[idx] = NewCaptureCallback(); stopWiVIThreadIfNecessary(std::move(lk2)); }); } Lifetime Device::addSleepRequestedCallback(SleepRequestedCallback 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 < sleepRequestedCallbacks.size(); idx++) { if(!sleepRequestedCallbacks[idx].first) // Empty space (previously erased callback) break; } if(idx == sleepRequestedCallbacks.size()) // Create a new space sleepRequestedCallbacks.emplace_back(std::move(cb), false); else sleepRequestedCallbacks[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); sleepRequestedCallbacks[idx].first = SleepRequestedCallback(); stopWiVIThreadIfNecessary(std::move(lk2)); }); } std::optional Device::isSleepRequested() 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); std::lock_guard lg(diskLock); const auto generic = com->waitForMessageSync([this]() { // VSSAL sets bit0 to indicate that it's waiting to sleep, then // it waits for Wireless neoVI to acknowledge by clearing it. // If we set bit1 at the same time we clear bit0, remote wakeup // will be suppressed (assuming the device supported it in the // first place) return com->sendCommand(Command::WiVICommand, WiVI::CommandPacket::GetSignal::Encode(WiVI::SignalType::SleepRequest)); }, filter); 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; } bool Device::allowSleep(bool remoteWakeup) { if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return false; } if(!supportsWiVI()) { report(APIEvent::Type::WiVINotSupported, APIEvent::Severity::Error); return false; } 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); std::lock_guard lg(diskLock); const auto generic = com->waitForMessageSync([this, remoteWakeup]() { // VSSAL sets bit0 to indicate that it's waiting to sleep, then // it waits for Wireless neoVI to acknowledge by clearing it. // If we set bit1 at the same time we clear bit0, remote wakeup // will be suppressed (assuming the device supported it in the // first place) return com->sendCommand(Command::WiVICommand, WiVI::CommandPacket::SetSignal::Encode( WiVI::SignalType::SleepRequest, remoteWakeup ? 0 : 2 )); }, filter); if(!generic || generic->type != Message::Type::WiVICommandResponse) { report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); return false; } const auto resp = std::static_pointer_cast(generic); if(!resp->success) { report(APIEvent::Type::ValueNotYetPresent, APIEvent::Severity::Error); return false; } return true; } void Device::scriptStatusThreadBody() { std::unique_lock lk(scriptStatusMutex); EventManager::GetInstance().downgradeErrorsOnCurrentThread(); bool first = true; while(!stopScriptStatusThread) { if(first) // Skip the first wait first = false; else stopScriptStatusCv.wait_for(lk, std::chrono::seconds(10)); const auto resp = getScriptStatus(); if (!resp) continue; //If value changed/was inserted, notify callback if(updateScriptStatusValue(ScriptStatus::CoreMiniRunning, resp->isCoreminiRunning)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::CoreMiniRunning, resp->isCoreminiRunning); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::IsEncrypted, resp->isEncrypted)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::IsEncrypted, resp->isEncrypted); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::SectorOverflow, resp->sectorOverflows)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::SectorOverflow, resp->sectorOverflows); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::RemainingSectors, resp->numRemainingSectorBuffers)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::RemainingSectors, resp->numRemainingSectorBuffers); lk.lock(); } bool logging = false; if(updateScriptStatusValue(ScriptStatus::LastSector, resp->lastSector)) { logging = true; lk.unlock(); notifyScriptStatusCallback(ScriptStatus::LastSector, resp->lastSector); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::Logging, logging)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::Logging, logging); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::ReadBinSize, resp->readBinSize)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::ReadBinSize, resp->readBinSize); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::MinSector, resp->minSector)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::MinSector, resp->minSector); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::MaxSector, resp->maxSector)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::MaxSector, resp->maxSector); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::CurrentSector, resp->currentSector)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::CurrentSector, resp->currentSector); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::CoreMiniCreateTime, resp->coreminiCreateTime)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::CoreMiniCreateTime, resp->coreminiCreateTime); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::FileChecksum, resp->fileChecksum)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::FileChecksum, resp->fileChecksum); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::CoreMiniVersion, resp->coreminiVersion)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::CoreMiniVersion, resp->coreminiVersion); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::CoreMiniHeaderSize, resp->coreminiHeaderSize)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::CoreMiniHeaderSize, resp->coreminiHeaderSize); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::DiagnosticErrorCode, resp->diagnosticErrorCode)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::DiagnosticErrorCode, resp->diagnosticErrorCode); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::DiagnosticErrorCodeCount, resp->diagnosticErrorCodeCount)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::DiagnosticErrorCodeCount, resp->diagnosticErrorCodeCount); lk.lock(); } if(updateScriptStatusValue(ScriptStatus::MaxCoreMiniSize, resp->maxCoreminiSizeKB)) { lk.unlock(); notifyScriptStatusCallback(ScriptStatus::MaxCoreMiniSize, resp->maxCoreminiSizeKB); lk.lock(); } } } std::shared_ptr Device::getScriptStatus() const { static std::shared_ptr filter = std::make_shared(Message::Type::ScriptStatus); std::lock_guard lg(diskLock); const auto generic = com->waitForMessageSync([this]() { return com->sendCommand(Command::ScriptStatus); }, filter, std::chrono::milliseconds(3000)); if(!generic || generic->type != Message::Type::ScriptStatus) { report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); return nullptr; } return std::static_pointer_cast(generic); } bool Device::updateScriptStatusValue(ScriptStatus key, uint64_t value) { auto pair = scriptStatusValues.find(key); if(pair != scriptStatusValues.end()) { if(pair->second != value) { //Value changed scriptStatusValues[key] = value; return true; } //Value didn't change return false; } //Value was inserted scriptStatusValues.insert(std::make_pair(key, value)); return true; } void Device::notifyScriptStatusCallback(ScriptStatus key, uint64_t value) { auto callbackList = scriptStatusCallbacks.find(key); if(callbackList != scriptStatusCallbacks.end()) { for(const auto& callback : callbackList->second) { if(callback) { try { callback(value); } catch(...) { report(APIEvent::Type::Unknown, APIEvent::Severity::Error); } } } } } Lifetime Device::addScriptStatusCallback(ScriptStatus key, ScriptStatusCallback cb) { if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return {}; } std::lock_guard lk(scriptStatusMutex); if(!scriptStatusThread.joinable()) { // Start the thread stopScriptStatusThread = false; scriptStatusThread = std::thread([this]() { scriptStatusThreadBody(); }); } size_t idx = 0; std::vector callbackList; auto callbackPair = scriptStatusCallbacks.find(key); if(callbackPair != scriptStatusCallbacks.end()) callbackList = callbackPair->second; if(idx == callbackList.size()) callbackList.push_back(std::move(cb)); else callbackList[idx] = std::move(cb); scriptStatusCallbacks.insert_or_assign(key, callbackList); return Lifetime([this, key, idx](){ std::unique_lock lk2(scriptStatusMutex); auto callbackList = scriptStatusCallbacks.find(key); if(callbackList != scriptStatusCallbacks.end()) callbackList->second[idx] = ScriptStatusCallback(); stopScriptStatusThreadIfNecessary(std::move(lk2)); }); } void Device::stopScriptStatusThreadIfNecessary(std::unique_lock lk) { for(const auto& callbackList : scriptStatusCallbacks) { for(const auto& callback : callbackList.second) { if(callback) return; } } stopScriptStatusThread = true; lk.unlock(); stopScriptStatusCv.notify_all(); scriptStatusThread.join(); scriptStatusThread = std::thread(); } Lifetime Device::suppressDisconnects() { std::lock_guard lk(heartbeatMutex); heartbeatSuppressedByUser++; return Lifetime([this] { std::lock_guard lk2(heartbeatMutex); heartbeatSuppressedByUser--; }); } void Device::addExtension(std::shared_ptr&& extension) { std::lock_guard lk(extensionsLock); extensions.push_back(extension); } void Device::forEachExtension(std::function&)> fn) { std::vector> extensionsCopy; { std::lock_guard lk(extensionsLock); extensionsCopy = extensions; } for(const auto& ext : extensionsCopy) { if(!fn(ext)) break; } } void Device::handleInternalMessage(std::shared_ptr message) { switch(message->type) { case Message::Type::ResetStatus: latestResetStatus = std::static_pointer_cast(message); break; case Message::Type::RawMessage: { auto rawMessage = std::static_pointer_cast(message); switch(rawMessage->network.getNetID()) { case Network::NetID::Device: { // Device is not guaranteed to be a CANMessage, it might be a RawMessage // if it couldn't be decoded to a CANMessage. We only care about the // CANMessage decoding right now. auto canmsg = std::dynamic_pointer_cast(message); if(canmsg) handleNeoVIMessage(std::move(canmsg)); break; } case Network::NetID::DeviceStatus: // Device Status format is unique per device, so the devices need to decode it themselves handleDeviceStatus(rawMessage); break; default: break; //std::cout << "HandleInternalMessage got a message from " << message->network << " and it was unhandled!" << std::endl; } break; } default: break; } forEachExtension([&](const std::shared_ptr& ext) { ext->handleMessage(message); return true; // false breaks out early }); } void Device::handleNeoVIMessage(std::shared_ptr message) { switch(message->arbid) { case 0x103: { // Report Message (neoVI FIRE 2) if(message->data.size() < 34) { report(APIEvent::Type::PacketDecodingError, APIEvent::Severity::EventWarning); return; } uint16_t emisc[2]; memcpy(emisc, message->data.data() + 24, sizeof(emisc)); std::lock_guard lk(ioMutex); miscAnalog[0] = (message->data[24] | (uint16_t(message->data[25]) << 8)) * 0.01015511; // In volts now miscAnalog[1] = (message->data[26] | (uint16_t(message->data[27]) << 8)) * 0.01015511; miscDigital[0] = message->data[28] & 0x01; miscDigital[1] = message->data[29] & 0x01; miscDigital[4] = message->data[30] & 0x01; miscDigital[5] = message->data[31] & 0x01; } } } bool Device::firmwareUpdateSupported() { bool ret = false; forEachExtension([&ret](const std::shared_ptr& ext) { if(ext->providesFirmware()) { ret = true; return false; } return true; // false breaks out early }); return ret; } APIEvent::Type Device::getCommunicationNotEstablishedError() { if(firmwareUpdateSupported()) { if(requiresVehiclePower()) return APIEvent::Type::NoSerialNumberFW12V; else return APIEvent::Type::NoSerialNumberFW; } else { if(requiresVehiclePower()) return APIEvent::Type::NoSerialNumber12V; else return APIEvent::Type::NoSerialNumber; } } void Device::updateLEDState() { std::vector args {(uint8_t) ledState}; com->sendCommand(Command::UpdateLEDState, args); } std::optional Device::sendEthPhyMsg(const EthPhyMessage& message, std::chrono::milliseconds timeout) { if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return std::nullopt; } if(!getEthPhyRegControlSupported()) { report(APIEvent::Type::EthPhyRegisterControlNotAvailable, APIEvent::Severity::Error); return std::nullopt; } if(!isOnline()) { report(APIEvent::Type::DeviceCurrentlyOffline, APIEvent::Severity::Error); return std::nullopt; } std::vector bytes; HardwareEthernetPhyRegisterPacket::EncodeFromMessage(message, bytes, report); std::shared_ptr response = com->waitForMessageSync( [this, bytes](){ return com->sendCommand(Command::PHYControlRegisters, bytes); }, std::make_shared(Network::NetID::EthPHYControl), timeout); if(!response) { report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); return std::nullopt; } auto retMsg = std::static_pointer_cast(response); if(!retMsg) { return std::nullopt; } return std::make_optional(*retMsg); } std::optional Device::SetCollectionUploaded(uint32_t collectionEntryByteAddress) { if (!supportsWiVI()) { report(APIEvent::Type::WiVINotSupported, APIEvent::Severity::EventWarning); return std::nullopt; } auto timeout = std::chrono::milliseconds(2500); std::vector args( {(uint8_t)(collectionEntryByteAddress & 0xFF), (uint8_t)((collectionEntryByteAddress >> 8) & 0xFF), (uint8_t)((collectionEntryByteAddress >> 16) & 0xFF), (uint8_t)((collectionEntryByteAddress >> 24) & 0xFF)}); std::lock_guard lg(diskLock); std::shared_ptr response = com->waitForMessageSync( [this, args](){ return com->sendCommand(ExtendedCommand::SetUploadedFlag, args); }, std::make_shared(Message::Type::ExtendedResponse), timeout); if (!response) { report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); return std::nullopt; } auto retMsg = std::static_pointer_cast(response); if (!retMsg) { // TODO fix this error report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); return std::make_optional(false); } bool success = retMsg->response == ExtendedResponse::OK; if (!success) { // TODO fix this error report(APIEvent::Type::Unknown, APIEvent::Severity::EventWarning); } // Valid device with a properly formed respose, return success return std::make_optional(success); } std::optional> Device::getRTC() { static const std::shared_ptr filter = std::make_shared(Network::NetID::RED_GET_RTC); std::shared_ptr generic = com->waitForMessageSync([this]() { return com->sendCommand(Command::GetRTC); }, filter, std::chrono::milliseconds(3000)); if(!generic) // Did not receive a message return std::nullopt; auto rawMes = std::dynamic_pointer_cast(generic); if(!rawMes) return std::nullopt; if(rawMes->data.size() != sizeof(RTCCTIME)) return std::nullopt; const auto* time = (RTCCTIME*)rawMes->data.data(); std::tm stdTime = {}; // std::tm has no member for the `FracSec` member of RTCCTIME struct stdTime.tm_sec = time->Sec; stdTime.tm_min = time->Min; stdTime.tm_hour = time->Hour; stdTime.tm_mday = time->Day; stdTime.tm_mon = time->Month - 1; // [0-11] stdTime.tm_year = time->Year + 100; // Number of years since 1900+100 stdTime.tm_wday = time->DOW; // RTCCTIME struct has no member for `tm_yday` #ifdef _MSC_VER #define timegm _mkgmtime #endif return std::chrono::system_clock::from_time_t(timegm(&stdTime)); } bool Device::setRTC(const std::chrono::time_point& time) { auto now = std::chrono::system_clock::to_time_t(time); const auto timeInfo = std::gmtime(&now); if(!timeInfo) return false; // Populate the RTCCTIME struct using the timeInfo and offsets // Create a vector of arguments to send as the payload to the communication command std::vector bytestream(sizeof(RTCCTIME)); auto rtcVals = (RTCCTIME*)bytestream.data(); rtcVals->FracSec = (uint8_t)0x00; rtcVals->Sec = (uint8_t)timeInfo->tm_sec; rtcVals->Min = (uint8_t)timeInfo->tm_min; rtcVals->Hour = (uint8_t)timeInfo->tm_hour; rtcVals->DOW = (uint8_t)timeInfo->tm_wday + 1; rtcVals->Day = (uint8_t)timeInfo->tm_mday; rtcVals->Month = (uint8_t)timeInfo->tm_mon + 1; // [0-11] rtcVals->Year = (uint8_t)timeInfo->tm_year % 100; // divide by 100 and take remainder to get last 2 digits of year const auto generic = com->waitForMessageSync([&]() { return com->sendCommand(Command::SetRTC, bytestream); }, std::make_shared(Command::SetRTC), std::chrono::milliseconds(100)); if(!generic) { report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); return false; } auto m51msg = std::dynamic_pointer_cast(generic); if(!m51msg || m51msg->data.size() != 1) { report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error); return false; } return m51msg->data.front(); } std::optional> Device::getSupportedFeatures() { auto timeout = std::chrono::milliseconds(100); std::shared_ptr msg = com->waitForMessageSync( [this](){ return com->sendCommand(ExtendedCommand::GetSupportedFeatures, {}); }, std::make_shared(Message::Type::SupportedFeatures), timeout); if(!msg) { report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); return std::nullopt; } const auto& typedResponse = std::dynamic_pointer_cast(msg); if(!typedResponse) { report(APIEvent::Type::UnexpectedResponse, APIEvent::Severity::Error); return std::nullopt; } return std::move(typedResponse->features); } std::optional Device::getGenericBinarySize(uint16_t binaryIndex) { auto timeout = std::chrono::milliseconds(2000); if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return std::nullopt; } if(!isOnline()) { report(APIEvent::Type::DeviceCurrentlyOffline, APIEvent::Severity::Error); return std::nullopt; } std::vector args = GenericBinaryStatusPacket::EncodeArguments(binaryIndex); std::shared_ptr response = com->waitForMessageSync( [this, &args](){ return com->sendCommand(ExtendedCommand::GenericBinaryInfo, args); }, std::make_shared(Message::Type::GenericBinaryStatus), timeout ); if(!response) { report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); return std::nullopt; } auto retMsg = std::static_pointer_cast(response); if(!retMsg) { return std::nullopt; } return retMsg->binarySize; } bool Device::readBinaryFile(std::ostream& stream, uint16_t binaryIndex) { auto timeout = std::chrono::milliseconds(100); auto size = getGenericBinarySize(binaryIndex); if(!size) { return false; } std::vector arguments(sizeof(ExtendedDataMessage::ExtendedDataHeader)); ExtendedDataMessage::ExtendedDataHeader& parameters = *reinterpret_cast(arguments.data()); auto filter = std::make_shared(Network::NetID::ExtendedData); for(size_t offset = 0; offset < *size; offset+=ExtendedDataMessage::MaxExtendedDataBufferSize) { parameters.subCommand = ExtendedDataSubCommand::GenericBinaryRead; parameters.userValue = static_cast(binaryIndex); parameters.offset = static_cast(offset); parameters.length = static_cast(std::min(ExtendedDataMessage::MaxExtendedDataBufferSize, *size - offset)); std::shared_ptr response = com->waitForMessageSync( [this, arguments](){ return com->sendCommand(Command::ExtendedData, arguments); }, filter, timeout ); if(!response) { report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error); return false; } auto retMsg = std::static_pointer_cast(response); if(!stream.write(reinterpret_cast(retMsg->data.data()), retMsg->data.size())) { return false; } } return true; } bool Device::subscribeLiveData(std::shared_ptr message) { if(!supportsLiveData()) { report(APIEvent::Type::LiveDataNotSupported, APIEvent::Severity::Error); return false; } if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return false; } if((message->args.size() > MAX_LIVE_DATA_ENTRIES) || message->args.empty()) { report(APIEvent::Type::LiveDataInvalidArgument, APIEvent::Severity::Error); return false; } std::vector bytes; if(!com->encoder->encode(*com->packetizer, bytes, message)) { report(APIEvent::Type::LiveDataEncoderError, APIEvent::Severity::Error); return false; } std::shared_ptr response = com->waitForMessageSync( [this, &bytes](){ return com->sendPacket(bytes); }, std::make_shared(Message::Type::LiveData)); if(response) { auto statusMsg = std::dynamic_pointer_cast(response); if(statusMsg->requestedCommand == message->cmd) { switch(statusMsg->status) { case LiveDataStatus::SUCCESS: return true; case LiveDataStatus::ERR_DUPLICATE: case LiveDataStatus::ERR_HANDLE: { report(APIEvent::Type::LiveDataInvalidHandle, APIEvent::Severity::Error); return false; } case LiveDataStatus::ERR_FULL: { report(APIEvent::Type::LiveDataMaxSignalsReached, APIEvent::Severity::Error); return false; } case LiveDataStatus::ERR_UNKNOWN_COMMAND: { report(APIEvent::Type::LiveDataCommandFailed, APIEvent::Severity::Error); return false; } default: break; } } } report(APIEvent::Type::LiveDataNoDeviceResponse, APIEvent::Severity::Error); return false; } bool Device::unsubscribeLiveData(const LiveDataHandle& handle) { if(!supportsLiveData()) { report(APIEvent::Type::LiveDataNotSupported, APIEvent::Severity::Error); return false; } if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return false; } if(!handle) { report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error); return false; } auto msg = std::make_shared(); msg->cmd = LiveDataCommand::UNSUBSCRIBE; msg->handle = handle; std::vector bytes; if(!com->encoder->encode(*com->packetizer, bytes, msg)) { report(APIEvent::Type::LiveDataEncoderError, APIEvent::Severity::Error); return false; } std::shared_ptr response = com->waitForMessageSync( [this, &bytes](){ return com->sendPacket(bytes); }, std::make_shared(Message::Type::LiveData)); if(!response) { report(APIEvent::Type::LiveDataNoDeviceResponse, APIEvent::Severity::Error); return false; } auto statusMsg = std::dynamic_pointer_cast(response); if(!statusMsg || statusMsg->requestedCommand != msg->cmd) { report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error); return false; } if(statusMsg->status != LiveDataStatus::SUCCESS) { report(APIEvent::Type::LiveDataCommandFailed, APIEvent::Severity::Error); return false; } return true; } bool Device::clearAllLiveData() { if(!supportsLiveData()) { report(APIEvent::Type::LiveDataNotSupported, APIEvent::Severity::Error); return false; } if(!isOpen()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return false; } auto msg = std::make_shared(); msg->cmd = LiveDataCommand::CLEAR_ALL; std::vector bytes; if(!com->encoder->encode(*com->packetizer, bytes, msg)) { report(APIEvent::Type::LiveDataEncoderError, APIEvent::Severity::Error); return false; } std::shared_ptr response = com->waitForMessageSync( [this, &bytes](){ return com->sendPacket(bytes); }, std::make_shared(Message::Type::LiveData)); if(!response) { report(APIEvent::Type::LiveDataNoDeviceResponse, APIEvent::Severity::Error); return false; } auto statusMsg = std::dynamic_pointer_cast(response); if(!statusMsg || statusMsg->requestedCommand != msg->cmd) { report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error); return false; } if(statusMsg->status != LiveDataStatus::SUCCESS) { report(APIEvent::Type::LiveDataCommandFailed, APIEvent::Severity::Error); return false; } return true; }