From 3a42372dcd4bba985a9dc152573f8aaf6bd41962 Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Fri, 26 Oct 2018 20:53:30 -0400 Subject: [PATCH] Add error system --- CMakeLists.txt | 2 + api/icsneoc/icsneoc.cpp | 134 +++++++++++++--- api/icsneocpp/error.cpp | 149 ++++++++++++++++++ api/icsneocpp/errormanager.cpp | 134 ++++++++++++++++ communication/communication.cpp | 6 +- communication/multichannelcommunication.cpp | 6 +- device/device.cpp | 30 ++-- device/idevicesettings.cpp | 20 ++- include/icsneo/api/error.h | 115 ++++++++++++++ include/icsneo/api/errormanager.h | 98 ++++++++++++ include/icsneo/communication/communication.h | 5 +- include/icsneo/communication/decoder.h | 4 + include/icsneo/communication/encoder.h | 3 +- .../communication/multichannelcommunication.h | 3 +- include/icsneo/communication/packetizer.h | 6 + include/icsneo/device/device.h | 30 +++- include/icsneo/device/idevicesettings.h | 12 +- include/icsneo/device/nullsettings.h | 4 +- include/icsneo/device/plasion/plasion.h | 2 +- 19 files changed, 691 insertions(+), 72 deletions(-) create mode 100644 api/icsneocpp/error.cpp create mode 100644 api/icsneocpp/errormanager.cpp create mode 100644 include/icsneo/api/error.h create mode 100644 include/icsneo/api/errormanager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cfd607f..fdbf63f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,8 @@ set(SRC_FILES ${COMMON_SRC} ${PLATFORM_SRC}) add_library(icsneocpp api/icsneocpp/icsneocpp.cpp + api/icsneocpp/error.cpp + api/icsneocpp/errormanager.cpp ${SRC_FILES} ) target_include_directories(icsneocpp diff --git a/api/icsneoc/icsneoc.cpp b/api/icsneoc/icsneoc.cpp index 8114f76..220ad2a 100644 --- a/api/icsneoc/icsneoc.cpp +++ b/api/icsneoc/icsneoc.cpp @@ -7,6 +7,7 @@ #include "icsneo/icsneoc.h" #include "icsneo/icsneocpp.h" #include "icsneo/platform/dynamiclib.h" +#include "icsneo/api/errormanager.h" #include #include #include @@ -25,8 +26,10 @@ static std::map>> polledMes void icsneo_findAllDevices(neodevice_t* devices, size_t* count) { std::vector> foundDevices = icsneo::FindAllDevices(); - if(count == nullptr) + if(count == nullptr) { + ErrorManager::GetInstance().add(APIError::RequiredParameterNull); return; + } if(devices == nullptr) { *count = foundDevices.size(); @@ -39,7 +42,7 @@ void icsneo_findAllDevices(neodevice_t* devices, size_t* count) { *count = foundDevices.size(); size_t outputSize = *count; if(outputSize > inputSize) { - // TODO an error should be returned that the data was truncated + ErrorManager::GetInstance().add(APIError::OutputTruncated); outputSize = inputSize; } @@ -54,9 +57,22 @@ void icsneo_freeUnconnectedDevices() { } bool icsneo_serialNumToString(uint32_t num, char* str, size_t* count) { + // TAG String copy function + if(count == nullptr) { + ErrorManager::GetInstance().add(APIError::RequiredParameterNull); + return false; + } + auto result = Device::SerialNumToString(num); + + if(str == nullptr) { + *count = result.length() + 1; + return false; + } + if(*count < result.length()) { *count = result.length() + 1; // This is how big of a buffer we need + ErrorManager::GetInstance().add(APIError::BufferInsufficient); return false; } @@ -83,8 +99,10 @@ bool icsneo_isValidNeoDevice(const neodevice_t* device) { } bool icsneo_openDevice(const neodevice_t* device) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } if(!device->device->open()) return false; @@ -104,8 +122,10 @@ bool icsneo_openDevice(const neodevice_t* device) { } bool icsneo_closeDevice(const neodevice_t* device) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } if(!device->device->close()) return false; @@ -123,47 +143,61 @@ bool icsneo_closeDevice(const neodevice_t* device) { } bool icsneo_goOnline(const neodevice_t* device) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } return device->device->goOnline(); } bool icsneo_goOffline(const neodevice_t* device) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } return device->device->goOffline(); } bool icsneo_isOnline(const neodevice_t* device) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } return device->device->isOnline(); } bool icsneo_enableMessagePolling(const neodevice_t* device) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } device->device->enableMessagePolling(); return true; } bool icsneo_disableMessagePolling(const neodevice_t* device) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } return device->device->disableMessagePolling(); } bool icsneo_getMessages(const neodevice_t* device, neomessage_t* messages, size_t* items) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } - if(items == nullptr) + if(items == nullptr) { + ErrorManager::GetInstance().add(APIError::RequiredParameterNull); return false; + } if(messages == nullptr) { // A NULL value for messages means the user wants the current size of the buffer into items @@ -189,79 +223,117 @@ bool icsneo_getMessages(const neodevice_t* device, neomessage_t* messages, size_ } size_t icsneo_getPollingMessageLimit(const neodevice_t* device) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return 0; + } return device->device->getPollingMessageLimit(); } bool icsneo_setPollingMessageLimit(const neodevice_t* device, size_t newLimit) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } device->device->setPollingMessageLimit(newLimit); return true; } bool icsneo_getProductName(const neodevice_t* device, char* str, size_t* maxLength) { - if(!icsneo_isValidNeoDevice(device)) + // TAG String copy function + if(maxLength == nullptr) { + ErrorManager::GetInstance().add(APIError::RequiredParameterNull); return false; + } - *maxLength = device->device->getType().toString().copy(str, *maxLength); + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); + return false; + } + + std::string output = device->device->getType().toString(); + + if(str == nullptr) { + *maxLength = output.length(); + return false; + } + + *maxLength = output.copy(str, *maxLength); str[*maxLength] = '\0'; + + if(output.length() > *maxLength) + ErrorManager::GetInstance().add(APIError::OutputTruncated); + return true; } bool icsneo_settingsRefresh(const neodevice_t* device) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } return device->device->settings->refresh(); } bool icsneo_settingsApply(const neodevice_t* device) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } return device->device->settings->apply(); } bool icsneo_settingsApplyTemporary(const neodevice_t* device) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } return device->device->settings->apply(true); } bool icsneo_settingsApplyDefaults(const neodevice_t* device) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } return device->device->settings->applyDefaults(); } bool icsneo_settingsApplyDefaultsTemporary(const neodevice_t* device) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } return device->device->settings->applyDefaults(true); } bool icsneo_setBaudrate(const neodevice_t* device, uint16_t netid, uint32_t newBaudrate) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } return device->device->settings->setBaudrateFor(netid, newBaudrate); } bool icsneo_transmit(const neodevice_t* device, const neomessage_t* message) { - if(!icsneo_isValidNeoDevice(device)) + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); return false; + } return device->device->transmit(CreateMessageFromNeoMessage(message)); } bool icsneo_transmitMessages(const neodevice_t* device, const neomessage_t* messages, size_t count) { + // Transmit implements neodevice_t check so it is not needed here // TODO This can be implemented faster for(size_t i = 0; i < count; i++) { if(!icsneo_transmit(device, messages + i)) @@ -271,10 +343,24 @@ bool icsneo_transmitMessages(const neodevice_t* device, const neomessage_t* mess } bool icsneo_describeDevice(const neodevice_t* device, char* str, size_t* maxLength) { - if(!icsneo_isValidNeoDevice(device)) + // TAG String copy function + if(maxLength == nullptr) { + ErrorManager::GetInstance().add(APIError::RequiredParameterNull); return false; + } - *maxLength = device->device->describe().copy(str, *maxLength); + if(!icsneo_isValidNeoDevice(device)) { + ErrorManager::GetInstance().add(APIError::InvalidNeoDevice); + return false; + } + + std::string output = device->device->describe(); + + *maxLength = output.copy(str, *maxLength); str[*maxLength] = '\0'; + + if(output.length() > *maxLength) + ErrorManager::GetInstance().add(APIError::OutputTruncated); + return true; } \ No newline at end of file diff --git a/api/icsneocpp/error.cpp b/api/icsneocpp/error.cpp new file mode 100644 index 0000000..c9584bf --- /dev/null +++ b/api/icsneocpp/error.cpp @@ -0,0 +1,149 @@ +#include "icsneo/api/error.h" +#include "icsneo/device/device.h" +#include + +using namespace icsneo; + +APIError::APIError(ErrorType error) : errorStruct({}) { + init(error); +} + +APIError::APIError(ErrorType error, const Device* forDevice) : errorStruct({}) { + device = forDevice; + serial = device->getSerial(); + errorStruct.serial[serial.copy(errorStruct.serial, sizeof(errorStruct.serial))] = '\0'; + + init(error); +} + +void APIError::init(ErrorType error) { + timepoint = std::chrono::high_resolution_clock::now(); + errorStruct.description = DescriptionForType(error); + errorStruct.errorNumber = (uint32_t)error; + errorStruct.severity = (uint8_t)SeverityForType(error); + errorStruct.timestamp = std::chrono::high_resolution_clock::to_time_t(timepoint); +} + +std::string APIError::describe() const noexcept { + std::stringstream ss; + if(device) + ss << *device; // Makes use of device.describe() + else + ss << "API"; + ss << " Error: "; + ss << getDescription(); + return ss.str(); +} + +bool APIError::isForDevice(std::string serial) const noexcept { + if(!device || serial.length() == 0) + return false; + return device->getSerial() == serial; +} + +// API Errors +static constexpr const char* ERROR_INVALID_NEODEVICE = "The provided neodevice_t object was invalid."; +static constexpr const char* ERROR_REQUIRED_PARAMETER_NULL = "A required parameter was NULL."; +static constexpr const char* ERROR_BUFFER_INSUFFICIENT = "The provided buffer was insufficient. No data was written."; +static constexpr const char* ERROR_OUTPUT_TRUNCATED = "The output was too large for the provided buffer and has been truncated."; +static constexpr const char* ERROR_PARAMETER_OUT_OF_RANGE = "A parameter was out of range."; + +// Device Errors +static constexpr const char* ERROR_POLLING_MESSAGE_OVERFLOW = "Too many messages have been recieved for the polling message buffer, some have been lost!"; +static constexpr const char* ERROR_NO_SERIAL_NUMBER = "Communication could not be established with the device. Perhaps it is not powered with 12 volts?"; +static constexpr const char* ERROR_INCORRECT_SERIAL_NUMBER = "The device did not return the expected serial number!"; +static constexpr const char* ERROR_SETTINGS_READ = "The device settings could not be read."; +static constexpr const char* ERROR_SETTINGS_VERSION = "The settings version is incorrect, please update your firmware with neoVI Explorer."; +static constexpr const char* ERROR_SETTINGS_LENGTH = "The settings length is incorrect, please update your firmware with neoVI Explorer."; +static constexpr const char* ERROR_SETTINGS_CHECKSUM = "The settings checksum is incorrect, attempting to set defaults may remedy this issue."; +static constexpr const char* ERROR_SETTINGS_NOT_AVAILABLE = "Settings are not available for this device."; + +static constexpr const char* ERROR_TOO_MANY_ERRORS = "Too many errors have occurred. The list has been truncated."; +static constexpr const char* ERROR_UNKNOWN = "An unknown internal error occurred."; +static constexpr const char* ERROR_INVALID = "An invalid internal error occurred."; +const char* APIError::DescriptionForType(ErrorType type) { + switch(type) { + // API Errors + case InvalidNeoDevice: + return ERROR_INVALID_NEODEVICE; + case RequiredParameterNull: + return ERROR_REQUIRED_PARAMETER_NULL; + case BufferInsufficient: + return ERROR_BUFFER_INSUFFICIENT; + case OutputTruncated: + return ERROR_OUTPUT_TRUNCATED; + case ParameterOutOfRange: + return ERROR_PARAMETER_OUT_OF_RANGE; + + // Device Errors + case PollingMessageOverflow: + return ERROR_POLLING_MESSAGE_OVERFLOW; + case NoSerialNumber: + return ERROR_NO_SERIAL_NUMBER; + case IncorrectSerialNumber: + return ERROR_INCORRECT_SERIAL_NUMBER; + case SettingsReadError: + return ERROR_SETTINGS_READ; + case SettingsVersionError: + return ERROR_SETTINGS_VERSION; + case SettingsLengthError: + return ERROR_SETTINGS_LENGTH; + case SettingsChecksumError: + return ERROR_SETTINGS_CHECKSUM; + case SettingsNotAvailable: + return ERROR_SETTINGS_NOT_AVAILABLE; + + // Other Errors + case TooManyErrors: + return ERROR_TOO_MANY_ERRORS; + case Unknown: + return ERROR_UNKNOWN; + default: + return ERROR_INVALID; + } +} + +APIError::Severity APIError::SeverityForType(ErrorType type) { + switch(type) { + // API Warnings + case OutputTruncated: + // Device Warnings + case PollingMessageOverflow: + return Severity::Warning; + + // API Errors + case InvalidNeoDevice: + case RequiredParameterNull: + case BufferInsufficient: + case ParameterOutOfRange: + // Device Errors + case NoSerialNumber: + case IncorrectSerialNumber: + case SettingsReadError: + case SettingsVersionError: + case SettingsLengthError: + case SettingsChecksumError: + case SettingsNotAvailable: + // Other Errors + case TooManyErrors: + case Unknown: + default: + return Severity::Error; + } +} + +bool ErrorFilter::match(const APIError& error) const noexcept { + if(type != APIError::Any && type != error.getType()) + return false; + + if(matchOnDevicePtr && !error.isForDevice(device)) + return false; + + if(severity != APIError::Severity::Any && severity != error.getSeverity()) + return false; + + if(serial.length() != 0 && !error.isForDevice(serial)) + return false; + + return true; +} \ No newline at end of file diff --git a/api/icsneocpp/errormanager.cpp b/api/icsneocpp/errormanager.cpp new file mode 100644 index 0000000..08f1f6a --- /dev/null +++ b/api/icsneocpp/errormanager.cpp @@ -0,0 +1,134 @@ +#include "icsneo/api/errormanager.h" +#include + +using namespace icsneo; + +static std::unique_ptr singleton; + +ErrorManager& ErrorManager::GetInstance() { + if(!singleton) + singleton = std::unique_ptr(new ErrorManager()); + return *singleton.get(); +} + +void ErrorManager::get(std::vector& errorOutput, size_t max, ErrorFilter filter) { + std::lock_guard lk(mutex); + + if(max == 0) // A limit of 0 indicates no limit + max = (size_t)-1; + + size_t count = 0; + errorOutput.clear(); + auto it = errors.begin(); + while(it != errors.end()) { + if(filter.match(*it)) { + errorOutput.push_back(*it); + errors.erase(it++); + if(count++ >= max) + break; // We now have as many written to output as we can + } else { + it++; + } + } +} + +bool ErrorManager::getOne(APIError& errorOutput, ErrorFilter filter) { + std::vector output; + get(output, filter, 1); + + if(output.size() == 0) + return false; + + errorOutput = output[0]; + return true; +} + +void ErrorManager::discard(ErrorFilter filter) { + std::lock_guard lk(mutex); + errors.remove_if([&filter](const APIError& error) { + return filter.match(error); + }); +} + +size_t ErrorManager::count_internal(ErrorFilter filter) const { + size_t ret = 0; + for(auto& error : errors) + if(filter.match(error)) + ret++; + return ret; +} + +bool ErrorManager::beforeAddCheck(APIError::ErrorType type) { + if(enforceLimit()) { // The enforceLimit will add the "TooManyErrors" error for us if necessary + // We need to decide whether to add this error or drop it + // We would have to remove something if we added this error + if(APIError::SeverityForType(type) < lowestCurrentSeverity()) + return false; // Don't add this one, we are already full of higher priority items + } + return true; +} + +bool ErrorManager::enforceLimit() { + if(errors.size() + 1 < errorLimit) + return false; + + bool hasTooManyWarningAlready = count_internal(ErrorFilter(APIError::TooManyErrors)) != 0; + size_t amountToRemove = (errors.size() + (hasTooManyWarningAlready ? 0 : 1)) - errorLimit; + + discardLeastSevere(amountToRemove); + if(!hasTooManyWarningAlready) + add_internal(APIError::TooManyErrors); + return true; +} + +APIError::Severity ErrorManager::lowestCurrentSeverity() { + if(errors.empty()) + return APIError::Severity(0); + + APIError::Severity lowest = APIError::Severity::Error; + auto it = errors.begin(); + while(it != errors.end()) { + if((*it).getSeverity() < lowest) + lowest = (*it).getSeverity(); + } + return lowest; +} + +void ErrorManager::discardLeastSevere(size_t count) { + if(count == 0) + return; + + ErrorFilter infoFilter(APIError::Severity::Info); + auto it = errors.begin(); + while(it != errors.end()) { + if(infoFilter.match(*it)) { + errors.erase(it++); + if(--count == 0) + break; + } + } + + if(count != 0) { + ErrorFilter warningFilter(APIError::Severity::Warning); + it = errors.begin(); + while(it != errors.end()) { + if(warningFilter.match(*it)) { + errors.erase(it++); + if(--count == 0) + break; + } + } + } + + if(count != 0) { + ErrorFilter warningFilter(APIError::Severity::Warning); + it = errors.begin(); + while(it != errors.end()) { + if(warningFilter.match(*it)) { + errors.erase(it++); + if(--count == 0) + break; + } + } + } +} \ No newline at end of file diff --git a/communication/communication.cpp b/communication/communication.cpp index b2a0479..092e482 100644 --- a/communication/communication.cpp +++ b/communication/communication.cpp @@ -127,8 +127,10 @@ void Communication::readTask() { if(packetizer->input(readBytes)) { for(auto& packet : packetizer->output()) { std::shared_ptr msg; - if(!decoder->decode(msg, packet)) - continue; // TODO Report an error to the user, we failed to decode this packet + if(!decoder->decode(msg, packet)) { + err(APIError::Unknown); // TODO Use specific error + continue; + } for(auto& cb : messageCallbacks) { if(!closing) { // We might have closed while reading or processing diff --git a/communication/multichannelcommunication.cpp b/communication/multichannelcommunication.cpp index 338bebd..c159dae 100644 --- a/communication/multichannelcommunication.cpp +++ b/communication/multichannelcommunication.cpp @@ -103,8 +103,10 @@ void MultiChannelCommunication::readTask() { if(packetizer->input(payloadBytes)) { for(auto& packet : packetizer->output()) { std::shared_ptr msg; - if(!decoder->decode(msg, packet)) - continue; // TODO Report an error to the user, we failed to decode this packet + if(!decoder->decode(msg, packet)) { + err(APIError::Unknown); // TODO Use specific error + continue; + } for(auto& cb : messageCallbacks) { // We might have closed while reading or processing if(!closing) { diff --git a/device/device.cpp b/device/device.cpp index 45d9fc0..e0468b1 100644 --- a/device/device.cpp +++ b/device/device.cpp @@ -122,13 +122,15 @@ void Device::enforcePollingMessageLimit() { while(pollingContainer.size_approx() > pollingMessageLimit) { std::shared_ptr throwAway; pollingContainer.try_dequeue(throwAway); - // TODO Flag an error for the user! + err(APIError::PollingMessageOverflow); } } bool Device::open() { - if(!com) + if(!com) { + err(APIError::Unknown); return false; + } if(!com->open()) return false; @@ -141,13 +143,13 @@ bool Device::open() { break; } if(!serial) { - std::cout << "Failed to get serial number in " << i << " tries" << std::endl; + err(APIError::NoSerialNumber); return false; } std::string currentSerial = getNeoDevice().serial; if(currentSerial != serial->deviceSerial) { - std::cout << "Found device had serial " << getNeoDevice().serial << " but connected device has serial " << serial->deviceSerial.c_str() << "!" << std::endl; + err(APIError::IncorrectSerialNumber); return false; } @@ -166,8 +168,10 @@ bool Device::open() { } bool Device::close() { - if(!com) + if(!com) { + err(APIError::Unknown); return false; + } if(internalHandlerCallbackID) com->removeMessageCallback(internalHandlerCallbackID); @@ -213,22 +217,6 @@ bool Device::transmit(std::vector> messages) { return true; } -template -void Device::initialize() { - auto transport = makeTransport(); - setupTransport(transport.get()); - auto packetizer = makePacketizer(); - setupPacketizer(packetizer.get()); - auto encoder = makeEncoder(packetizer); - setupEncoder(encoder.get()); - auto decoder = makeDecoder(); - setupDecoder(decoder.get()); - com = makeCommunication(std::move(transport), packetizer, std::move(encoder), std::move(decoder)); - setupCommunication(com.get()); - settings = makeSettings(com); - setupSettings(settings.get()); -} - void Device::handleInternalMessage(std::shared_ptr message) { switch(message->network.getNetID()) { case Network::NetID::Reset_Status: diff --git a/device/idevicesettings.cpp b/device/idevicesettings.cpp index 7637c2f..6b170e3 100644 --- a/device/idevicesettings.cpp +++ b/device/idevicesettings.cpp @@ -43,16 +43,22 @@ uint16_t IDeviceSettings::CalculateGSChecksum(const std::vector& settin } bool IDeviceSettings::refresh(bool ignoreChecksum) { - if(disabled) + if(disabled) { + err(APIError::SettingsNotAvailable); return false; + } std::vector rxSettings; bool ret = com->getSettingsSync(rxSettings); - if(!ret) + if(!ret) { + err(APIError::SettingsReadError); return false; + } - if(rxSettings.size() < 6) // We need to at least have the header of GLOBAL_SETTINGS + if(rxSettings.size() < 6) { // We need to at least have the header of GLOBAL_SETTINGS + err(APIError::SettingsReadError); return false; + } constexpr size_t gs_size = 3 * sizeof(uint16_t); size_t rxLen = rxSettings.size() - gs_size; @@ -63,17 +69,17 @@ bool IDeviceSettings::refresh(bool ignoreChecksum) { rxSettings.erase(rxSettings.begin(), rxSettings.begin() + gs_size); if(gs_version != 5) { - std::cout << "gs_version was " << gs_version << " instead of 5.\nPlease update your firmware." << std::endl; + err(APIError::SettingsVersionError); return false; } if(rxLen != gs_len) { - std::cout << "rxLen was " << rxLen << " and gs_len was " << gs_len << " while reading settings" << std::endl; + err(APIError::SettingsLengthError); return false; } if(!ignoreChecksum && gs_chksum != CalculateGSChecksum(rxSettings)) { - std::cout << "Checksum mismatch while reading settings" << std::endl; + err(APIError::SettingsChecksumError); return false; } @@ -81,7 +87,7 @@ bool IDeviceSettings::refresh(bool ignoreChecksum) { settingsLoaded = true; if(settings.size() != structSize) { - std::cout << "Settings size was " << settings.size() << " bytes but it should be " << structSize << " bytes for this device" << std::endl; + err(APIError::SettingsLengthError); settingsLoaded = false; } diff --git a/include/icsneo/api/error.h b/include/icsneo/api/error.h new file mode 100644 index 0000000..ec1b90f --- /dev/null +++ b/include/icsneo/api/error.h @@ -0,0 +1,115 @@ +#ifndef __ICSNEO_API_ERROR_H_ +#define __ICSNEO_API_ERROR_H_ + +#include +#include + +#ifdef __cplusplus +#define CONSTEXPR constexpr +#else +#define CONSTEXPR const +#endif + +typedef struct { + const char* description; + time_t timestamp; + uint32_t errorNumber; + uint8_t severity; + char serial[7]; + uint8_t reserved[16]; +} neoerror_t; + +#include +#include +#include +#include + +namespace icsneo { + +class Device; + +class APIError { +public: + enum ErrorType : uint32_t { + Any = 0, // Used for filtering, should not appear in data + + // API Errors + InvalidNeoDevice = 0x1000, + RequiredParameterNull = 0x1001, + BufferInsufficient = 0x1002, + OutputTruncated = 0x1003, + ParameterOutOfRange = 0x1004, + + // Device Errors + PollingMessageOverflow = 0x2000, + NoSerialNumber = 0x2001, + IncorrectSerialNumber = 0x2002, + SettingsReadError = 0x2003, + SettingsVersionError = 0x2004, + SettingsLengthError = 0x2005, + SettingsChecksumError = 0x2006, + SettingsNotAvailable = 0x2007, + + TooManyErrors = 0xFFFFFFFE, + Unknown = 0xFFFFFFFF + }; + enum class Severity : uint8_t { + Any = 0, // Used for filtering, should not appear in data + Info = 0x10, + Warning = 0x20, + Error = 0x30 + }; + + APIError(ErrorType error); + APIError(ErrorType error, const Device* device); + + ErrorType getType() const noexcept { return ErrorType(errorStruct.errorNumber); } + Severity getSeverity() const noexcept { return Severity(errorStruct.severity); } + std::string getDescription() const noexcept { return std::string(errorStruct.description); } + const Device* getDevice() const noexcept { return device; } // Will return nullptr if this is an API-wide error + std::chrono::time_point getTimestamp() const noexcept { return timepoint; } + + bool isForDevice(Device* forDevice) const noexcept { return forDevice == device; } + bool isForDevice(std::string serial) const noexcept; + + // As opposed to getDescription, this will also add text such as "neoVI FIRE 2 CY2468 Error: " to fully describe the problem + std::string describe() const noexcept; + friend std::ostream& operator<<(std::ostream& os, const APIError& error) { + os << error.describe(); + return os; + } + + static const char* DescriptionForType(ErrorType type); + static Severity SeverityForType(ErrorType type); + +private: + neoerror_t errorStruct; + std::string serial; + std::chrono::time_point timepoint; + const Device* device; + + void init(ErrorType error); +}; + +class ErrorFilter { +public: + ErrorFilter() {} // Empty filter matches anything + ErrorFilter(APIError::ErrorType error) : type(error) {} + ErrorFilter(APIError::Severity severity) : severity(severity) {} + ErrorFilter(Device* device, APIError::ErrorType error = APIError::Any) : type(error), matchOnDevicePtr(true), device(device) {} + ErrorFilter(Device* device, APIError::Severity severity = APIError::Severity::Any) : severity(severity), matchOnDevicePtr(true), device(device) {} + ErrorFilter(std::string serial, APIError::ErrorType error = APIError::Any) : type(error), serial(serial) {} + ErrorFilter(std::string serial, APIError::Severity severity = APIError::Severity::Any) : severity(severity), serial(serial) {} + + bool match(const APIError& error) const noexcept; + + APIError::Severity severity = APIError::Severity::Any; + APIError::ErrorType type = APIError::Any; + bool matchOnDevicePtr = false; + Device* device = nullptr; // nullptr will match on "no device, generic API error" + std::string serial; // Empty serial will match any, including no device. Not affected by matchOnDevicePtr +}; + +} + +#endif \ No newline at end of file diff --git a/include/icsneo/api/errormanager.h b/include/icsneo/api/errormanager.h new file mode 100644 index 0000000..896557b --- /dev/null +++ b/include/icsneo/api/errormanager.h @@ -0,0 +1,98 @@ +#ifndef __ICSNEO_API_ERRORMANAGER_H_ +#define __ICSNEO_API_ERRORMANAGER_H_ + +#include +#include +#include +#include +#include "icsneo/api/error.h" + +namespace icsneo { + +typedef std::function device_errorhandler_t; + +class ErrorManager { +public: + static ErrorManager& GetInstance(); + + size_t count(ErrorFilter filter = ErrorFilter()) const { + std::lock_guard lk(mutex); + return count_internal(filter); + }; + + std::vector get(ErrorFilter filter, size_t max = 0) { return get(max, filter); } + std::vector get(size_t max = 0, ErrorFilter filter = ErrorFilter()) { + std::vector ret; + get(ret, filter, max); + return ret; + } + void get(std::vector& errors, ErrorFilter filter, size_t max = 0) { get(errors, max, filter); } + void get(std::vector& errors, size_t max = 0, ErrorFilter filter = ErrorFilter()); + bool getOne(APIError& error, ErrorFilter filter = ErrorFilter()); + + void add(APIError error) { + std::lock_guard lk(mutex); + add_internal(error); + } + void add(APIError::ErrorType type) { + std::lock_guard lk(mutex); + add_internal(type); + } + void add(APIError::ErrorType type, const Device* forDevice) { + std::lock_guard lk(mutex); + add_internal(type, forDevice); + } + + void discard(ErrorFilter filter = ErrorFilter()); + + void setErrorLimit(size_t newLimit) { + if(newLimit < 10) { + add(APIError::ParameterOutOfRange); + return; + } + + std::lock_guard lk(mutex); + errorLimit = newLimit; + enforceLimit(); + } + + size_t getErrorLimit() const { return errorLimit; } + +private: + ErrorManager() {} + mutable std::mutex mutex; + std::list errors; + size_t errorLimit = 10000; + + size_t count_internal(ErrorFilter filter = ErrorFilter()) const; + + void add_internal(APIError error) { + if(!beforeAddCheck(error.getType())) + return; + errors.push_back(error); + enforceLimit(); + } + void add_internal(APIError::ErrorType type) { + if(!beforeAddCheck(type)) + return; + errors.emplace_back(type); + enforceLimit(); + } + void add_internal(APIError::ErrorType type, const Device* forDevice) { + if(!beforeAddCheck(type)) + return; + errors.emplace_back(type, forDevice); + enforceLimit(); + } + + bool beforeAddCheck(APIError::ErrorType type); // Returns whether the error should be added + + bool enforceLimit(); // Returns whether the limit enforcement resulted in an overflow + + APIError::Severity lowestCurrentSeverity(); + void discardLeastSevere(size_t count = 1); +}; + +} + +#endif \ No newline at end of file diff --git a/include/icsneo/communication/communication.h b/include/icsneo/communication/communication.h index 28175cd..e92c873 100644 --- a/include/icsneo/communication/communication.h +++ b/include/icsneo/communication/communication.h @@ -7,6 +7,7 @@ #include "icsneo/communication/packet.h" #include "icsneo/communication/message/callback/messagecallback.h" #include "icsneo/communication/message/serialnumbermessage.h" +#include "icsneo/api/errormanager.h" #include "icsneo/communication/packetizer.h" #include "icsneo/communication/encoder.h" #include "icsneo/communication/decoder.h" @@ -22,10 +23,11 @@ namespace icsneo { class Communication { public: Communication( + device_errorhandler_t err, std::unique_ptr com, std::shared_ptr p, std::unique_ptr e, - std::unique_ptr md) : packetizer(p), encoder(std::move(e)), decoder(std::move(md)), impl(std::move(com)) {} + std::unique_ptr md) : packetizer(p), encoder(std::move(e)), decoder(std::move(md)), err(err), impl(std::move(com)) {} virtual ~Communication() { close(); } bool open(); @@ -50,6 +52,7 @@ public: std::shared_ptr packetizer; // Ownership is shared with the encoder std::unique_ptr encoder; std::unique_ptr decoder; + device_errorhandler_t err; protected: std::unique_ptr impl; diff --git a/include/icsneo/communication/decoder.h b/include/icsneo/communication/decoder.h index e401d8f..ddcde4f 100644 --- a/include/icsneo/communication/decoder.h +++ b/include/icsneo/communication/decoder.h @@ -5,6 +5,7 @@ #include "icsneo/communication/message/canmessage.h" #include "icsneo/communication/packet.h" #include "icsneo/communication/network.h" +#include "icsneo/api/errormanager.h" #include #include #include @@ -16,9 +17,12 @@ namespace icsneo { class Decoder { public: static uint64_t GetUInt64FromLEBytes(uint8_t* bytes); + + Decoder(device_errorhandler_t err) : err(err) {} bool decode(std::shared_ptr& result, const std::shared_ptr& packet); private: + device_errorhandler_t err; typedef uint16_t icscm_bitfield; struct HardwareCANPacket { struct { diff --git a/include/icsneo/communication/encoder.h b/include/icsneo/communication/encoder.h index c2db16e..46cfc1a 100644 --- a/include/icsneo/communication/encoder.h +++ b/include/icsneo/communication/encoder.h @@ -15,7 +15,7 @@ namespace icsneo { class Encoder { public: - Encoder(std::shared_ptr packetizerInstance) : packetizer(packetizerInstance) {} + Encoder(device_errorhandler_t err, std::shared_ptr p) : packetizer(p), err(err) {} bool encode(std::vector& result, const std::shared_ptr& message); bool encode(std::vector& result, Command cmd, bool boolean) { return encode(result, cmd, std::vector({ (uint8_t)boolean })); } bool encode(std::vector& result, Command cmd, std::vector arguments = {}); @@ -23,6 +23,7 @@ public: bool supportCANFD = false; private: std::shared_ptr packetizer; + device_errorhandler_t err; }; } diff --git a/include/icsneo/communication/multichannelcommunication.h b/include/icsneo/communication/multichannelcommunication.h index 96009e5..4f7e81a 100644 --- a/include/icsneo/communication/multichannelcommunication.h +++ b/include/icsneo/communication/multichannelcommunication.h @@ -11,10 +11,11 @@ namespace icsneo { class MultiChannelCommunication : public Communication { public: MultiChannelCommunication( + device_errorhandler_t err, std::unique_ptr com, std::shared_ptr p, std::unique_ptr e, - std::unique_ptr md) : Communication(std::move(com), p, std::move(e), std::move(md)) {} + std::unique_ptr md) : Communication(err, std::move(com), p, std::move(e), std::move(md)) {} void spawnThreads() override; void joinThreads() override; bool sendPacket(std::vector& bytes) override; diff --git a/include/icsneo/communication/packetizer.h b/include/icsneo/communication/packetizer.h index 822d2a6..2307297 100644 --- a/include/icsneo/communication/packetizer.h +++ b/include/icsneo/communication/packetizer.h @@ -2,6 +2,7 @@ #define __PACKETIZER_H_ #include "icsneo/communication/packet.h" +#include "icsneo/api/errormanager.h" #include #include #include @@ -11,6 +12,9 @@ namespace icsneo { class Packetizer { public: static uint8_t ICSChecksum(const std::vector& data); + + Packetizer(device_errorhandler_t err) : err(err) {} + std::vector& packetWrap(std::vector& data, bool shortFormat); bool input(const std::vector& bytes); @@ -37,6 +41,8 @@ private: std::deque bytes; std::vector> processedPackets; + + device_errorhandler_t err; }; } diff --git a/include/icsneo/device/device.h b/include/icsneo/device/device.h index c0fb5e5..97395e0 100644 --- a/include/icsneo/device/device.h +++ b/include/icsneo/device/device.h @@ -4,6 +4,7 @@ #include #include #include +#include "icsneo/api/errormanager.h" #include "icsneo/device/neodevice.h" #include "icsneo/device/idevicesettings.h" #include "icsneo/device/nullsettings.h" @@ -71,6 +72,7 @@ protected: int messagePollingCallbackID = 0; int internalHandlerCallbackID = 0; std::shared_ptr com; + device_errorhandler_t err; // START Initialization Functions Device(neodevice_t neodevice = { 0 }) { @@ -79,26 +81,44 @@ protected: } template - void initialize(); + void initialize() { + err = makeErrorHandler(); + auto transport = makeTransport(); + setupTransport(transport.get()); + auto packetizer = makePacketizer(); + setupPacketizer(packetizer.get()); + auto encoder = makeEncoder(packetizer); + setupEncoder(encoder.get()); + auto decoder = makeDecoder(); + setupDecoder(decoder.get()); + com = makeCommunication(std::move(transport), packetizer, std::move(encoder), std::move(decoder)); + setupCommunication(com.get()); + settings = makeSettings(com); + setupSettings(settings.get()); + } + + virtual device_errorhandler_t makeErrorHandler() { + return [this](APIError::ErrorType type) { ErrorManager::GetInstance().add(type, this); }; + } template std::unique_ptr makeTransport() { return std::unique_ptr(new Transport(getWritableNeoDevice())); } virtual void setupTransport(ICommunication* transport) {} - virtual std::shared_ptr makePacketizer() { return std::make_shared(); } + virtual std::shared_ptr makePacketizer() { return std::make_shared(err); } virtual void setupPacketizer(Packetizer* packetizer) {} - virtual std::unique_ptr makeEncoder(std::shared_ptr p) { return std::unique_ptr(new Encoder(p)); } + virtual std::unique_ptr makeEncoder(std::shared_ptr p) { return std::unique_ptr(new Encoder(err, p)); } virtual void setupEncoder(Encoder* encoder) {} - virtual std::unique_ptr makeDecoder() { return std::unique_ptr(new Decoder()); } + virtual std::unique_ptr makeDecoder() { return std::unique_ptr(new Decoder(err)); } virtual void setupDecoder(Decoder* decoder) {} virtual std::shared_ptr makeCommunication( std::unique_ptr t, std::shared_ptr p, std::unique_ptr e, - std::unique_ptr d) { return std::make_shared(std::move(t), p, std::move(e), std::move(d)); } + std::unique_ptr d) { return std::make_shared(err, std::move(t), p, std::move(e), std::move(d)); } virtual void setupCommunication(Communication* com) {} template diff --git a/include/icsneo/device/idevicesettings.h b/include/icsneo/device/idevicesettings.h index 44c566e..6d272fb 100644 --- a/include/icsneo/device/idevicesettings.h +++ b/include/icsneo/device/idevicesettings.h @@ -283,11 +283,7 @@ public: static constexpr uint16_t GS_VERSION = 5; static uint16_t CalculateGSChecksum(const std::vector& settings); - // Parameter createInoperableSettings exists because it is serving as a warning that you probably don't want to do this - typedef void* warn_t; - IDeviceSettings(warn_t createInoperableSettings) : disabled(true), readonly(true), structSize(0) { (void)createInoperableSettings; } - - IDeviceSettings(std::shared_ptr com, size_t size) : com(com), structSize(size) {} + IDeviceSettings(std::shared_ptr com, size_t size) : com(com), err(com->err), structSize(size) {} virtual ~IDeviceSettings() {} bool ok() { return !disabled && settingsLoaded; } @@ -313,9 +309,15 @@ public: bool readonly = false; protected: std::shared_ptr com; + device_errorhandler_t err; size_t structSize; bool settingsLoaded = false; std::vector settings; + + // Parameter createInoperableSettings exists because it is serving as a warning that you probably don't want to do this + typedef void* warn_t; + IDeviceSettings(warn_t createInoperableSettings, std::shared_ptr com) + : disabled(true), readonly(true), err(com->err), structSize(0) { (void)createInoperableSettings; } }; } diff --git a/include/icsneo/device/nullsettings.h b/include/icsneo/device/nullsettings.h index d3050a4..7ff7f96 100644 --- a/include/icsneo/device/nullsettings.h +++ b/include/icsneo/device/nullsettings.h @@ -9,8 +9,8 @@ namespace icsneo { class NullSettings : public IDeviceSettings { public: - // Calls the base constructor with "createInoperableSettings" - NullSettings(std::shared_ptr com = std::shared_ptr()) : IDeviceSettings(nullptr) { (void)com; } + // Calls the protected base constructor with "createInoperableSettings" + NullSettings(std::shared_ptr com) : IDeviceSettings(nullptr, com) {} }; } diff --git a/include/icsneo/device/plasion/plasion.h b/include/icsneo/device/plasion/plasion.h index c1a0259..ce695d8 100644 --- a/include/icsneo/device/plasion/plasion.h +++ b/include/icsneo/device/plasion/plasion.h @@ -14,7 +14,7 @@ protected: std::shared_ptr packetizer, std::unique_ptr encoder, std::unique_ptr decoder - ) override { return std::make_shared(std::move(transport), packetizer, std::move(encoder), std::move(decoder)); } + ) override { return std::make_shared(err, std::move(transport), packetizer, std::move(encoder), std::move(decoder)); } public: Plasion(neodevice_t neodevice) : Device(neodevice) {}