Add error system

pull/4/head
Paul Hollinsky 2018-10-26 20:53:30 -04:00
parent a331a2afa8
commit 3a42372dcd
19 changed files with 691 additions and 72 deletions

View File

@ -59,6 +59,8 @@ set(SRC_FILES ${COMMON_SRC} ${PLATFORM_SRC})
add_library(icsneocpp add_library(icsneocpp
api/icsneocpp/icsneocpp.cpp api/icsneocpp/icsneocpp.cpp
api/icsneocpp/error.cpp
api/icsneocpp/errormanager.cpp
${SRC_FILES} ${SRC_FILES}
) )
target_include_directories(icsneocpp target_include_directories(icsneocpp

View File

@ -7,6 +7,7 @@
#include "icsneo/icsneoc.h" #include "icsneo/icsneoc.h"
#include "icsneo/icsneocpp.h" #include "icsneo/icsneocpp.h"
#include "icsneo/platform/dynamiclib.h" #include "icsneo/platform/dynamiclib.h"
#include "icsneo/api/errormanager.h"
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory> #include <memory>
@ -25,8 +26,10 @@ static std::map<devicehandle_t, std::vector<std::shared_ptr<Message>>> polledMes
void icsneo_findAllDevices(neodevice_t* devices, size_t* count) { void icsneo_findAllDevices(neodevice_t* devices, size_t* count) {
std::vector<std::shared_ptr<Device>> foundDevices = icsneo::FindAllDevices(); std::vector<std::shared_ptr<Device>> foundDevices = icsneo::FindAllDevices();
if(count == nullptr) if(count == nullptr) {
ErrorManager::GetInstance().add(APIError::RequiredParameterNull);
return; return;
}
if(devices == nullptr) { if(devices == nullptr) {
*count = foundDevices.size(); *count = foundDevices.size();
@ -39,7 +42,7 @@ void icsneo_findAllDevices(neodevice_t* devices, size_t* count) {
*count = foundDevices.size(); *count = foundDevices.size();
size_t outputSize = *count; size_t outputSize = *count;
if(outputSize > inputSize) { if(outputSize > inputSize) {
// TODO an error should be returned that the data was truncated ErrorManager::GetInstance().add(APIError::OutputTruncated);
outputSize = inputSize; outputSize = inputSize;
} }
@ -54,9 +57,22 @@ void icsneo_freeUnconnectedDevices() {
} }
bool icsneo_serialNumToString(uint32_t num, char* str, size_t* count) { 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); auto result = Device::SerialNumToString(num);
if(str == nullptr) {
*count = result.length() + 1;
return false;
}
if(*count < result.length()) { if(*count < result.length()) {
*count = result.length() + 1; // This is how big of a buffer we need *count = result.length() + 1; // This is how big of a buffer we need
ErrorManager::GetInstance().add(APIError::BufferInsufficient);
return false; return false;
} }
@ -83,8 +99,10 @@ bool icsneo_isValidNeoDevice(const neodevice_t* device) {
} }
bool icsneo_openDevice(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; return false;
}
if(!device->device->open()) if(!device->device->open())
return false; return false;
@ -104,8 +122,10 @@ bool icsneo_openDevice(const neodevice_t* device) {
} }
bool icsneo_closeDevice(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; return false;
}
if(!device->device->close()) if(!device->device->close())
return false; return false;
@ -123,47 +143,61 @@ bool icsneo_closeDevice(const neodevice_t* device) {
} }
bool icsneo_goOnline(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 false;
}
return device->device->goOnline(); return device->device->goOnline();
} }
bool icsneo_goOffline(const neodevice_t* device) { bool icsneo_goOffline(const neodevice_t* device) {
if(!icsneo_isValidNeoDevice(device)) if(!icsneo_isValidNeoDevice(device)) {
ErrorManager::GetInstance().add(APIError::InvalidNeoDevice);
return false; return false;
}
return device->device->goOffline(); return device->device->goOffline();
} }
bool icsneo_isOnline(const neodevice_t* device) { bool icsneo_isOnline(const neodevice_t* device) {
if(!icsneo_isValidNeoDevice(device)) if(!icsneo_isValidNeoDevice(device)) {
ErrorManager::GetInstance().add(APIError::InvalidNeoDevice);
return false; return false;
}
return device->device->isOnline(); return device->device->isOnline();
} }
bool icsneo_enableMessagePolling(const neodevice_t* device) { bool icsneo_enableMessagePolling(const neodevice_t* device) {
if(!icsneo_isValidNeoDevice(device)) if(!icsneo_isValidNeoDevice(device)) {
ErrorManager::GetInstance().add(APIError::InvalidNeoDevice);
return false; return false;
}
device->device->enableMessagePolling(); device->device->enableMessagePolling();
return true; return true;
} }
bool icsneo_disableMessagePolling(const neodevice_t* device) { bool icsneo_disableMessagePolling(const neodevice_t* device) {
if(!icsneo_isValidNeoDevice(device)) if(!icsneo_isValidNeoDevice(device)) {
ErrorManager::GetInstance().add(APIError::InvalidNeoDevice);
return false; return false;
}
return device->device->disableMessagePolling(); return device->device->disableMessagePolling();
} }
bool icsneo_getMessages(const neodevice_t* device, neomessage_t* messages, size_t* items) { 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; return false;
}
if(items == nullptr) if(items == nullptr) {
ErrorManager::GetInstance().add(APIError::RequiredParameterNull);
return false; return false;
}
if(messages == nullptr) { if(messages == nullptr) {
// A NULL value for messages means the user wants the current size of the buffer into items // 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) { size_t icsneo_getPollingMessageLimit(const neodevice_t* device) {
if(!icsneo_isValidNeoDevice(device)) if(!icsneo_isValidNeoDevice(device)) {
ErrorManager::GetInstance().add(APIError::InvalidNeoDevice);
return 0; return 0;
}
return device->device->getPollingMessageLimit(); return device->device->getPollingMessageLimit();
} }
bool icsneo_setPollingMessageLimit(const neodevice_t* device, size_t newLimit) { 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; return false;
}
device->device->setPollingMessageLimit(newLimit); device->device->setPollingMessageLimit(newLimit);
return true; return true;
} }
bool icsneo_getProductName(const neodevice_t* device, char* str, size_t* maxLength) { 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; 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'; str[*maxLength] = '\0';
if(output.length() > *maxLength)
ErrorManager::GetInstance().add(APIError::OutputTruncated);
return true; return true;
} }
bool icsneo_settingsRefresh(const neodevice_t* device) { bool icsneo_settingsRefresh(const neodevice_t* device) {
if(!icsneo_isValidNeoDevice(device)) if(!icsneo_isValidNeoDevice(device)) {
ErrorManager::GetInstance().add(APIError::InvalidNeoDevice);
return false; return false;
}
return device->device->settings->refresh(); return device->device->settings->refresh();
} }
bool icsneo_settingsApply(const neodevice_t* device) { bool icsneo_settingsApply(const neodevice_t* device) {
if(!icsneo_isValidNeoDevice(device)) if(!icsneo_isValidNeoDevice(device)) {
ErrorManager::GetInstance().add(APIError::InvalidNeoDevice);
return false; return false;
}
return device->device->settings->apply(); return device->device->settings->apply();
} }
bool icsneo_settingsApplyTemporary(const neodevice_t* device) { bool icsneo_settingsApplyTemporary(const neodevice_t* device) {
if(!icsneo_isValidNeoDevice(device)) if(!icsneo_isValidNeoDevice(device)) {
ErrorManager::GetInstance().add(APIError::InvalidNeoDevice);
return false; return false;
}
return device->device->settings->apply(true); return device->device->settings->apply(true);
} }
bool icsneo_settingsApplyDefaults(const neodevice_t* device) { bool icsneo_settingsApplyDefaults(const neodevice_t* device) {
if(!icsneo_isValidNeoDevice(device)) if(!icsneo_isValidNeoDevice(device)) {
ErrorManager::GetInstance().add(APIError::InvalidNeoDevice);
return false; return false;
}
return device->device->settings->applyDefaults(); return device->device->settings->applyDefaults();
} }
bool icsneo_settingsApplyDefaultsTemporary(const neodevice_t* device) { bool icsneo_settingsApplyDefaultsTemporary(const neodevice_t* device) {
if(!icsneo_isValidNeoDevice(device)) if(!icsneo_isValidNeoDevice(device)) {
ErrorManager::GetInstance().add(APIError::InvalidNeoDevice);
return false; return false;
}
return device->device->settings->applyDefaults(true); return device->device->settings->applyDefaults(true);
} }
bool icsneo_setBaudrate(const neodevice_t* device, uint16_t netid, uint32_t newBaudrate) { 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 false;
}
return device->device->settings->setBaudrateFor(netid, newBaudrate); return device->device->settings->setBaudrateFor(netid, newBaudrate);
} }
bool icsneo_transmit(const neodevice_t* device, const neomessage_t* message) { 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 false;
}
return device->device->transmit(CreateMessageFromNeoMessage(message)); return device->device->transmit(CreateMessageFromNeoMessage(message));
} }
bool icsneo_transmitMessages(const neodevice_t* device, const neomessage_t* messages, size_t count) { 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 // TODO This can be implemented faster
for(size_t i = 0; i < count; i++) { for(size_t i = 0; i < count; i++) {
if(!icsneo_transmit(device, messages + 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) { 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; 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'; str[*maxLength] = '\0';
if(output.length() > *maxLength)
ErrorManager::GetInstance().add(APIError::OutputTruncated);
return true; return true;
} }

View File

@ -0,0 +1,149 @@
#include "icsneo/api/error.h"
#include "icsneo/device/device.h"
#include <sstream>
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;
}

View File

@ -0,0 +1,134 @@
#include "icsneo/api/errormanager.h"
#include <memory>
using namespace icsneo;
static std::unique_ptr<ErrorManager> singleton;
ErrorManager& ErrorManager::GetInstance() {
if(!singleton)
singleton = std::unique_ptr<ErrorManager>(new ErrorManager());
return *singleton.get();
}
void ErrorManager::get(std::vector<APIError>& errorOutput, size_t max, ErrorFilter filter) {
std::lock_guard<std::mutex> 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<APIError> output;
get(output, filter, 1);
if(output.size() == 0)
return false;
errorOutput = output[0];
return true;
}
void ErrorManager::discard(ErrorFilter filter) {
std::lock_guard<std::mutex> 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;
}
}
}
}

View File

@ -127,8 +127,10 @@ void Communication::readTask() {
if(packetizer->input(readBytes)) { if(packetizer->input(readBytes)) {
for(auto& packet : packetizer->output()) { for(auto& packet : packetizer->output()) {
std::shared_ptr<Message> msg; std::shared_ptr<Message> msg;
if(!decoder->decode(msg, packet)) if(!decoder->decode(msg, packet)) {
continue; // TODO Report an error to the user, we failed to decode this packet err(APIError::Unknown); // TODO Use specific error
continue;
}
for(auto& cb : messageCallbacks) { for(auto& cb : messageCallbacks) {
if(!closing) { // We might have closed while reading or processing if(!closing) { // We might have closed while reading or processing

View File

@ -103,8 +103,10 @@ void MultiChannelCommunication::readTask() {
if(packetizer->input(payloadBytes)) { if(packetizer->input(payloadBytes)) {
for(auto& packet : packetizer->output()) { for(auto& packet : packetizer->output()) {
std::shared_ptr<Message> msg; std::shared_ptr<Message> msg;
if(!decoder->decode(msg, packet)) if(!decoder->decode(msg, packet)) {
continue; // TODO Report an error to the user, we failed to decode this packet err(APIError::Unknown); // TODO Use specific error
continue;
}
for(auto& cb : messageCallbacks) { // We might have closed while reading or processing for(auto& cb : messageCallbacks) { // We might have closed while reading or processing
if(!closing) { if(!closing) {

View File

@ -122,13 +122,15 @@ void Device::enforcePollingMessageLimit() {
while(pollingContainer.size_approx() > pollingMessageLimit) { while(pollingContainer.size_approx() > pollingMessageLimit) {
std::shared_ptr<Message> throwAway; std::shared_ptr<Message> throwAway;
pollingContainer.try_dequeue(throwAway); pollingContainer.try_dequeue(throwAway);
// TODO Flag an error for the user! err(APIError::PollingMessageOverflow);
} }
} }
bool Device::open() { bool Device::open() {
if(!com) if(!com) {
err(APIError::Unknown);
return false; return false;
}
if(!com->open()) if(!com->open())
return false; return false;
@ -141,13 +143,13 @@ bool Device::open() {
break; break;
} }
if(!serial) { if(!serial) {
std::cout << "Failed to get serial number in " << i << " tries" << std::endl; err(APIError::NoSerialNumber);
return false; return false;
} }
std::string currentSerial = getNeoDevice().serial; std::string currentSerial = getNeoDevice().serial;
if(currentSerial != serial->deviceSerial) { 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; return false;
} }
@ -166,8 +168,10 @@ bool Device::open() {
} }
bool Device::close() { bool Device::close() {
if(!com) if(!com) {
err(APIError::Unknown);
return false; return false;
}
if(internalHandlerCallbackID) if(internalHandlerCallbackID)
com->removeMessageCallback(internalHandlerCallbackID); com->removeMessageCallback(internalHandlerCallbackID);
@ -213,22 +217,6 @@ bool Device::transmit(std::vector<std::shared_ptr<Message>> messages) {
return true; return true;
} }
template<typename Transport, typename Settings>
void Device::initialize() {
auto transport = makeTransport<Transport>();
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<Settings>(com);
setupSettings(settings.get());
}
void Device::handleInternalMessage(std::shared_ptr<Message> message) { void Device::handleInternalMessage(std::shared_ptr<Message> message) {
switch(message->network.getNetID()) { switch(message->network.getNetID()) {
case Network::NetID::Reset_Status: case Network::NetID::Reset_Status:

View File

@ -43,16 +43,22 @@ uint16_t IDeviceSettings::CalculateGSChecksum(const std::vector<uint8_t>& settin
} }
bool IDeviceSettings::refresh(bool ignoreChecksum) { bool IDeviceSettings::refresh(bool ignoreChecksum) {
if(disabled) if(disabled) {
err(APIError::SettingsNotAvailable);
return false; return false;
}
std::vector<uint8_t> rxSettings; std::vector<uint8_t> rxSettings;
bool ret = com->getSettingsSync(rxSettings); bool ret = com->getSettingsSync(rxSettings);
if(!ret) if(!ret) {
err(APIError::SettingsReadError);
return false; 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; return false;
}
constexpr size_t gs_size = 3 * sizeof(uint16_t); constexpr size_t gs_size = 3 * sizeof(uint16_t);
size_t rxLen = rxSettings.size() - gs_size; size_t rxLen = rxSettings.size() - gs_size;
@ -63,17 +69,17 @@ bool IDeviceSettings::refresh(bool ignoreChecksum) {
rxSettings.erase(rxSettings.begin(), rxSettings.begin() + gs_size); rxSettings.erase(rxSettings.begin(), rxSettings.begin() + gs_size);
if(gs_version != 5) { 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; return false;
} }
if(rxLen != gs_len) { 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; return false;
} }
if(!ignoreChecksum && gs_chksum != CalculateGSChecksum(rxSettings)) { if(!ignoreChecksum && gs_chksum != CalculateGSChecksum(rxSettings)) {
std::cout << "Checksum mismatch while reading settings" << std::endl; err(APIError::SettingsChecksumError);
return false; return false;
} }
@ -81,7 +87,7 @@ bool IDeviceSettings::refresh(bool ignoreChecksum) {
settingsLoaded = true; settingsLoaded = true;
if(settings.size() != structSize) { 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; settingsLoaded = false;
} }

View File

@ -0,0 +1,115 @@
#ifndef __ICSNEO_API_ERROR_H_
#define __ICSNEO_API_ERROR_H_
#include <stdint.h>
#include <time.h>
#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 <vector>
#include <chrono>
#include <string>
#include <ostream>
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<std::chrono::high_resolution_clock> 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<std::chrono::high_resolution_clock> 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

View File

@ -0,0 +1,98 @@
#ifndef __ICSNEO_API_ERRORMANAGER_H_
#define __ICSNEO_API_ERRORMANAGER_H_
#include <vector>
#include <list>
#include <mutex>
#include <functional>
#include "icsneo/api/error.h"
namespace icsneo {
typedef std::function<void (APIError::ErrorType)> device_errorhandler_t;
class ErrorManager {
public:
static ErrorManager& GetInstance();
size_t count(ErrorFilter filter = ErrorFilter()) const {
std::lock_guard<std::mutex> lk(mutex);
return count_internal(filter);
};
std::vector<APIError> get(ErrorFilter filter, size_t max = 0) { return get(max, filter); }
std::vector<APIError> get(size_t max = 0, ErrorFilter filter = ErrorFilter()) {
std::vector<APIError> ret;
get(ret, filter, max);
return ret;
}
void get(std::vector<APIError>& errors, ErrorFilter filter, size_t max = 0) { get(errors, max, filter); }
void get(std::vector<APIError>& errors, size_t max = 0, ErrorFilter filter = ErrorFilter());
bool getOne(APIError& error, ErrorFilter filter = ErrorFilter());
void add(APIError error) {
std::lock_guard<std::mutex> lk(mutex);
add_internal(error);
}
void add(APIError::ErrorType type) {
std::lock_guard<std::mutex> lk(mutex);
add_internal(type);
}
void add(APIError::ErrorType type, const Device* forDevice) {
std::lock_guard<std::mutex> 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<std::mutex> lk(mutex);
errorLimit = newLimit;
enforceLimit();
}
size_t getErrorLimit() const { return errorLimit; }
private:
ErrorManager() {}
mutable std::mutex mutex;
std::list<APIError> 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

View File

@ -7,6 +7,7 @@
#include "icsneo/communication/packet.h" #include "icsneo/communication/packet.h"
#include "icsneo/communication/message/callback/messagecallback.h" #include "icsneo/communication/message/callback/messagecallback.h"
#include "icsneo/communication/message/serialnumbermessage.h" #include "icsneo/communication/message/serialnumbermessage.h"
#include "icsneo/api/errormanager.h"
#include "icsneo/communication/packetizer.h" #include "icsneo/communication/packetizer.h"
#include "icsneo/communication/encoder.h" #include "icsneo/communication/encoder.h"
#include "icsneo/communication/decoder.h" #include "icsneo/communication/decoder.h"
@ -22,10 +23,11 @@ namespace icsneo {
class Communication { class Communication {
public: public:
Communication( Communication(
device_errorhandler_t err,
std::unique_ptr<ICommunication> com, std::unique_ptr<ICommunication> com,
std::shared_ptr<Packetizer> p, std::shared_ptr<Packetizer> p,
std::unique_ptr<Encoder> e, std::unique_ptr<Encoder> e,
std::unique_ptr<Decoder> md) : packetizer(p), encoder(std::move(e)), decoder(std::move(md)), impl(std::move(com)) {} std::unique_ptr<Decoder> md) : packetizer(p), encoder(std::move(e)), decoder(std::move(md)), err(err), impl(std::move(com)) {}
virtual ~Communication() { close(); } virtual ~Communication() { close(); }
bool open(); bool open();
@ -50,6 +52,7 @@ public:
std::shared_ptr<Packetizer> packetizer; // Ownership is shared with the encoder std::shared_ptr<Packetizer> packetizer; // Ownership is shared with the encoder
std::unique_ptr<Encoder> encoder; std::unique_ptr<Encoder> encoder;
std::unique_ptr<Decoder> decoder; std::unique_ptr<Decoder> decoder;
device_errorhandler_t err;
protected: protected:
std::unique_ptr<ICommunication> impl; std::unique_ptr<ICommunication> impl;

View File

@ -5,6 +5,7 @@
#include "icsneo/communication/message/canmessage.h" #include "icsneo/communication/message/canmessage.h"
#include "icsneo/communication/packet.h" #include "icsneo/communication/packet.h"
#include "icsneo/communication/network.h" #include "icsneo/communication/network.h"
#include "icsneo/api/errormanager.h"
#include <queue> #include <queue>
#include <vector> #include <vector>
#include <memory> #include <memory>
@ -16,9 +17,12 @@ namespace icsneo {
class Decoder { class Decoder {
public: public:
static uint64_t GetUInt64FromLEBytes(uint8_t* bytes); static uint64_t GetUInt64FromLEBytes(uint8_t* bytes);
Decoder(device_errorhandler_t err) : err(err) {}
bool decode(std::shared_ptr<Message>& result, const std::shared_ptr<Packet>& packet); bool decode(std::shared_ptr<Message>& result, const std::shared_ptr<Packet>& packet);
private: private:
device_errorhandler_t err;
typedef uint16_t icscm_bitfield; typedef uint16_t icscm_bitfield;
struct HardwareCANPacket { struct HardwareCANPacket {
struct { struct {

View File

@ -15,7 +15,7 @@ namespace icsneo {
class Encoder { class Encoder {
public: public:
Encoder(std::shared_ptr<Packetizer> packetizerInstance) : packetizer(packetizerInstance) {} Encoder(device_errorhandler_t err, std::shared_ptr<Packetizer> p) : packetizer(p), err(err) {}
bool encode(std::vector<uint8_t>& result, const std::shared_ptr<Message>& message); bool encode(std::vector<uint8_t>& result, const std::shared_ptr<Message>& message);
bool encode(std::vector<uint8_t>& result, Command cmd, bool boolean) { return encode(result, cmd, std::vector<uint8_t>({ (uint8_t)boolean })); } bool encode(std::vector<uint8_t>& result, Command cmd, bool boolean) { return encode(result, cmd, std::vector<uint8_t>({ (uint8_t)boolean })); }
bool encode(std::vector<uint8_t>& result, Command cmd, std::vector<uint8_t> arguments = {}); bool encode(std::vector<uint8_t>& result, Command cmd, std::vector<uint8_t> arguments = {});
@ -23,6 +23,7 @@ public:
bool supportCANFD = false; bool supportCANFD = false;
private: private:
std::shared_ptr<Packetizer> packetizer; std::shared_ptr<Packetizer> packetizer;
device_errorhandler_t err;
}; };
} }

View File

@ -11,10 +11,11 @@ namespace icsneo {
class MultiChannelCommunication : public Communication { class MultiChannelCommunication : public Communication {
public: public:
MultiChannelCommunication( MultiChannelCommunication(
device_errorhandler_t err,
std::unique_ptr<ICommunication> com, std::unique_ptr<ICommunication> com,
std::shared_ptr<Packetizer> p, std::shared_ptr<Packetizer> p,
std::unique_ptr<Encoder> e, std::unique_ptr<Encoder> e,
std::unique_ptr<Decoder> md) : Communication(std::move(com), p, std::move(e), std::move(md)) {} std::unique_ptr<Decoder> md) : Communication(err, std::move(com), p, std::move(e), std::move(md)) {}
void spawnThreads() override; void spawnThreads() override;
void joinThreads() override; void joinThreads() override;
bool sendPacket(std::vector<uint8_t>& bytes) override; bool sendPacket(std::vector<uint8_t>& bytes) override;

View File

@ -2,6 +2,7 @@
#define __PACKETIZER_H_ #define __PACKETIZER_H_
#include "icsneo/communication/packet.h" #include "icsneo/communication/packet.h"
#include "icsneo/api/errormanager.h"
#include <queue> #include <queue>
#include <vector> #include <vector>
#include <memory> #include <memory>
@ -11,6 +12,9 @@ namespace icsneo {
class Packetizer { class Packetizer {
public: public:
static uint8_t ICSChecksum(const std::vector<uint8_t>& data); static uint8_t ICSChecksum(const std::vector<uint8_t>& data);
Packetizer(device_errorhandler_t err) : err(err) {}
std::vector<uint8_t>& packetWrap(std::vector<uint8_t>& data, bool shortFormat); std::vector<uint8_t>& packetWrap(std::vector<uint8_t>& data, bool shortFormat);
bool input(const std::vector<uint8_t>& bytes); bool input(const std::vector<uint8_t>& bytes);
@ -37,6 +41,8 @@ private:
std::deque<uint8_t> bytes; std::deque<uint8_t> bytes;
std::vector<std::shared_ptr<Packet>> processedPackets; std::vector<std::shared_ptr<Packet>> processedPackets;
device_errorhandler_t err;
}; };
} }

View File

@ -4,6 +4,7 @@
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <cstring> #include <cstring>
#include "icsneo/api/errormanager.h"
#include "icsneo/device/neodevice.h" #include "icsneo/device/neodevice.h"
#include "icsneo/device/idevicesettings.h" #include "icsneo/device/idevicesettings.h"
#include "icsneo/device/nullsettings.h" #include "icsneo/device/nullsettings.h"
@ -71,6 +72,7 @@ protected:
int messagePollingCallbackID = 0; int messagePollingCallbackID = 0;
int internalHandlerCallbackID = 0; int internalHandlerCallbackID = 0;
std::shared_ptr<Communication> com; std::shared_ptr<Communication> com;
device_errorhandler_t err;
// START Initialization Functions // START Initialization Functions
Device(neodevice_t neodevice = { 0 }) { Device(neodevice_t neodevice = { 0 }) {
@ -79,26 +81,44 @@ protected:
} }
template<typename Transport, typename Settings = NullSettings> template<typename Transport, typename Settings = NullSettings>
void initialize(); void initialize() {
err = makeErrorHandler();
auto transport = makeTransport<Transport>();
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<Settings>(com);
setupSettings(settings.get());
}
virtual device_errorhandler_t makeErrorHandler() {
return [this](APIError::ErrorType type) { ErrorManager::GetInstance().add(type, this); };
}
template<typename Transport> template<typename Transport>
std::unique_ptr<ICommunication> makeTransport() { return std::unique_ptr<ICommunication>(new Transport(getWritableNeoDevice())); } std::unique_ptr<ICommunication> makeTransport() { return std::unique_ptr<ICommunication>(new Transport(getWritableNeoDevice())); }
virtual void setupTransport(ICommunication* transport) {} virtual void setupTransport(ICommunication* transport) {}
virtual std::shared_ptr<Packetizer> makePacketizer() { return std::make_shared<Packetizer>(); } virtual std::shared_ptr<Packetizer> makePacketizer() { return std::make_shared<Packetizer>(err); }
virtual void setupPacketizer(Packetizer* packetizer) {} virtual void setupPacketizer(Packetizer* packetizer) {}
virtual std::unique_ptr<Encoder> makeEncoder(std::shared_ptr<Packetizer> p) { return std::unique_ptr<Encoder>(new Encoder(p)); } virtual std::unique_ptr<Encoder> makeEncoder(std::shared_ptr<Packetizer> p) { return std::unique_ptr<Encoder>(new Encoder(err, p)); }
virtual void setupEncoder(Encoder* encoder) {} virtual void setupEncoder(Encoder* encoder) {}
virtual std::unique_ptr<Decoder> makeDecoder() { return std::unique_ptr<Decoder>(new Decoder()); } virtual std::unique_ptr<Decoder> makeDecoder() { return std::unique_ptr<Decoder>(new Decoder(err)); }
virtual void setupDecoder(Decoder* decoder) {} virtual void setupDecoder(Decoder* decoder) {}
virtual std::shared_ptr<Communication> makeCommunication( virtual std::shared_ptr<Communication> makeCommunication(
std::unique_ptr<ICommunication> t, std::unique_ptr<ICommunication> t,
std::shared_ptr<Packetizer> p, std::shared_ptr<Packetizer> p,
std::unique_ptr<Encoder> e, std::unique_ptr<Encoder> e,
std::unique_ptr<Decoder> d) { return std::make_shared<Communication>(std::move(t), p, std::move(e), std::move(d)); } std::unique_ptr<Decoder> d) { return std::make_shared<Communication>(err, std::move(t), p, std::move(e), std::move(d)); }
virtual void setupCommunication(Communication* com) {} virtual void setupCommunication(Communication* com) {}
template<typename Settings> template<typename Settings>

View File

@ -283,11 +283,7 @@ public:
static constexpr uint16_t GS_VERSION = 5; static constexpr uint16_t GS_VERSION = 5;
static uint16_t CalculateGSChecksum(const std::vector<uint8_t>& settings); static uint16_t CalculateGSChecksum(const std::vector<uint8_t>& settings);
// Parameter createInoperableSettings exists because it is serving as a warning that you probably don't want to do this IDeviceSettings(std::shared_ptr<Communication> com, size_t size) : com(com), err(com->err), structSize(size) {}
typedef void* warn_t;
IDeviceSettings(warn_t createInoperableSettings) : disabled(true), readonly(true), structSize(0) { (void)createInoperableSettings; }
IDeviceSettings(std::shared_ptr<Communication> com, size_t size) : com(com), structSize(size) {}
virtual ~IDeviceSettings() {} virtual ~IDeviceSettings() {}
bool ok() { return !disabled && settingsLoaded; } bool ok() { return !disabled && settingsLoaded; }
@ -313,9 +309,15 @@ public:
bool readonly = false; bool readonly = false;
protected: protected:
std::shared_ptr<Communication> com; std::shared_ptr<Communication> com;
device_errorhandler_t err;
size_t structSize; size_t structSize;
bool settingsLoaded = false; bool settingsLoaded = false;
std::vector<uint8_t> settings; std::vector<uint8_t> 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<Communication> com)
: disabled(true), readonly(true), err(com->err), structSize(0) { (void)createInoperableSettings; }
}; };
} }

View File

@ -9,8 +9,8 @@ namespace icsneo {
class NullSettings : public IDeviceSettings { class NullSettings : public IDeviceSettings {
public: public:
// Calls the base constructor with "createInoperableSettings" // Calls the protected base constructor with "createInoperableSettings"
NullSettings(std::shared_ptr<Communication> com = std::shared_ptr<Communication>()) : IDeviceSettings(nullptr) { (void)com; } NullSettings(std::shared_ptr<Communication> com) : IDeviceSettings(nullptr, com) {}
}; };
} }

View File

@ -14,7 +14,7 @@ protected:
std::shared_ptr<Packetizer> packetizer, std::shared_ptr<Packetizer> packetizer,
std::unique_ptr<Encoder> encoder, std::unique_ptr<Encoder> encoder,
std::unique_ptr<Decoder> decoder std::unique_ptr<Decoder> decoder
) override { return std::make_shared<MultiChannelCommunication>(std::move(transport), packetizer, std::move(encoder), std::move(decoder)); } ) override { return std::make_shared<MultiChannelCommunication>(err, std::move(transport), packetizer, std::move(encoder), std::move(decoder)); }
public: public:
Plasion(neodevice_t neodevice) : Device(neodevice) {} Plasion(neodevice_t neodevice) : Device(neodevice) {}