bool initUartOutput(flowOutputState state, char* port, unsigned int baud, size_t bufferSize) { // Initialize UART communication int uartErrno = uart_open(port, baud); if (uartErrno) { caerLog(CAER_LOG_ALERT, SUBSYSTEM_UART, "Failed to identify serial communication, errno=%i.",uartErrno); return (false); } // Test message char* testMessage = "DVS128UART"; if (uart_tx((int) strlen(testMessage), (unsigned char*) testMessage)) { caerLog(CAER_LOG_ALERT, SUBSYSTEM_UART, "Test transmission unsuccessful - connection closed."); uart_close(); return(false); } // Start output handler thread state->thread = 0; if (thrd_create(&state->thread, &outputHandlerThread, state) != thrd_success) { caerLog(CAER_LOG_ALERT, SUBSYSTEM_UART,"Failed to start output handler thread"); return (false); } state->buffer = ringBufferInit(bufferSize); if (state->buffer == NULL) { caerLog(CAER_LOG_ERROR, SUBSYSTEM_UART, "Failed to allocate transfer ring-buffer."); return (false); } atomic_store(&state->running, true); caerLog(CAER_LOG_NOTICE, SUBSYSTEM_UART, "Streaming flow events to port %s.",port); return (true); }
void caerConfigInit(const char *configFile, int argc, char *argv[]) { // If configFile is NULL, no config file will be accessed at all, // and neither will it be written back at shutdown. if (configFile != NULL) { // Let's try to open the file for reading, or create it. int configFileFd = open(configFile, O_RDONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP); if (configFileFd >= 0) { // File opened for reading successfully. // This means it exists and we can access it, so let's remember // it for writing the config later at shutdown (if permitted). caerConfigFilePath = realpath(configFile, NULL); // Determine if there is actual content to parse first. struct stat configFileStat; fstat(configFileFd, &configFileStat); if (configFileStat.st_size > 0) { sshsNodeImportSubTreeFromXML(sshsGetNode(sshsGetGlobal(), "/"), configFileFd, true); } close(configFileFd); // Ensure configuration is written back at shutdown. atexit(&caerConfigShutDownWriteBack); } else { caerLog(CAER_LOG_EMERGENCY, "Config", "Could not create and/or read from the configuration file '%s'. Error: %d.", configFile, errno); exit(EXIT_FAILURE); } } else { caerLog(CAER_LOG_EMERGENCY, "Config", "No configuration file defined, using default values for everything."); } // Override with command line arguments if requested. if (argc > 1) { // Format: -o node key type value (5 arguments). Equal to caerctl format. for (size_t i = 1; i < (size_t) argc; i += 5) { if ((i + 4) < (size_t) argc && caerStrEquals(argv[i], "-o")) { sshsNode node = sshsGetNode(sshsGetGlobal(), argv[i + 1]); if (node == NULL) { caerLog(CAER_LOG_EMERGENCY, "Config", "SSHS Node %s doesn't exist.", argv[i + 1]); continue; } if (!sshsNodeStringToNodeConverter(node, argv[i + 2], argv[i + 3], argv[i + 4])) { caerLog(CAER_LOG_EMERGENCY, "Config", "Failed to convert attribute %s of type %s with value %s.", argv[i + 2], argv[i + 3], argv[i + 4]); } } } } }
void addPacketToTransferBuffer(flowOutputState state, FlowEventPacket packet, int32_t flowNumber) { if (packet == NULL) { // There was nothing in this mainloop run: return return; } caerEventPacketHeader header = &packet->packetHeader; // If no valid events are present, there is nothing to add if (caerEventPacketHeaderGetEventValid(header) == 0) { return; } if (flowNumber == 0) { return; } // Allocate memory for new flow event packet FlowEventPacket copy = malloc(sizeof(struct flow_event_packet)); if (copy == NULL) { caerLog(CAER_LOG_ERROR, SUBSYSTEM_UART,"Failed to copy event packet."); return; } // Copy header information copy->packetHeader = packet->packetHeader; copy->packetHeader.eventNumber = flowNumber; copy->packetHeader.eventValid = flowNumber; copy->events = malloc(sizeof(struct flow_event) * (size_t) flowNumber); // Copy events int j = 0; for (int i = 0; i < header->eventNumber; i++) { FlowEvent e = &packet->events[i]; if (e->hasFlow) { copy->events[j] = *e; j++; } } // Verify number of events copied if (j != flowNumber) { caerLog(CAER_LOG_ERROR, SUBSYSTEM_FILE, "Copied %d events while number of flow events is %d.", j, flowNumber); flowEventPacketFree(copy); return; } if (!ringBufferPut(state->buffer, copy)) { flowEventPacketFree(copy); caerLog(CAER_LOG_ALERT, SUBSYSTEM_UART,"Failed to add event packet to ring buffer."); return; } }
caerModuleData caerModuleInitialize(uint16_t moduleID, const char *moduleShortName, sshsNode mainloopNode) { // Generate short module name with ID, reused in all error messages and later code. size_t nameLength = (size_t) snprintf(NULL, 0, "%" PRIu16 "-%s", moduleID, moduleShortName); char nameString[nameLength + 1]; snprintf(nameString, nameLength + 1, "%" PRIu16 "-%s", moduleID, moduleShortName); // Allocate memory for the module. caerModuleData moduleData = calloc(1, sizeof(struct caer_module_data)); if (moduleData == NULL) { caerLog(CAER_LOG_ALERT, nameString, "Failed to allocate memory for module. Error: %d.", errno); thrd_exit(EXIT_FAILURE); } // Set module ID for later identification (hash-table key). moduleData->moduleID = moduleID; // Put module into startup state. moduleData->moduleStatus = STOPPED; atomic_store_explicit(&moduleData->running, true, memory_order_relaxed); // Determine SSHS module node. Use short name for better human recognition. char sshsString[nameLength + 2]; strncpy(sshsString, nameString, nameLength); sshsString[nameLength] = '/'; sshsString[nameLength + 1] = '\0'; // Initialize configuration, shutdown hooks. moduleData->moduleNode = sshsGetRelativeNode(mainloopNode, sshsString); if (moduleData->moduleNode == NULL) { caerLog(CAER_LOG_ALERT, nameString, "Failed to allocate configuration node for module."); thrd_exit(EXIT_FAILURE); } sshsNodePutBool(moduleData->moduleNode, "shutdown", false); // Always reset to false. sshsNodeAddAttributeListener(moduleData->moduleNode, moduleData, &caerModuleShutdownListener); // Setup default full log string name. moduleData->moduleSubSystemString = malloc(nameLength + 1); if (moduleData->moduleSubSystemString == NULL) { caerLog(CAER_LOG_ALERT, nameString, "Failed to allocate subsystem string for module."); thrd_exit(EXIT_FAILURE); } strncpy(moduleData->moduleSubSystemString, nameString, nameLength); moduleData->moduleSubSystemString[nameLength] = '\0'; atomic_thread_fence(memory_order_release); return (moduleData); }
void caerConfigServerStart(void) { // Enable the configuration server thread. atomic_store(&configServerThread.running, true); // Start the thread. if ((errno = thrd_create(&configServerThread.thread, &caerConfigServerRunner, NULL)) == thrd_success) { // Successfully started thread. caerLog(CAER_LOG_DEBUG, "Config Server", "Thread created successfully."); } else { // Failed to create thread. caerLog(CAER_LOG_EMERGENCY, "Config Server", "Failed to create thread. Error: %d.", errno); exit(EXIT_FAILURE); } }
void posecalibration_destroy(PoseCalibration *calibClass) { try { delete calibClass; } catch (const std::exception& ex) { caerLog(CAER_LOG_ERROR, "PoseCalibration_destroy()", "Failed with C++ exception: %s", ex.what()); } }
bool initFileOutput(flowOutputState state, char* fileName, size_t bufferSize) { // Initialize file communication state->file = fopen(fileName,"w+"); if (state->file == NULL) { caerLog(CAER_LOG_ALERT, SUBSYSTEM_FILE,"Failed to open file"); return (false); } // Write header as check for file output if (fprintf(state->file,"#cAER event data with optic flow values\n") < 0) { caerLog(CAER_LOG_ALERT, SUBSYSTEM_FILE,"Failed to write header"); fclose(state->file); return (false); } // Write creation date time_t rawTime; struct tm * timeInfo; time (&rawTime); timeInfo = localtime(&rawTime); fprintf(state->file,"#Date created: %s\n",asctime(timeInfo)); // Write column legend fprintf(state->file,"#x,y,t,p,u,v\n"); state->fileLineNumber = 4; // Start output handler thread (ONLY IF NOT YET CALLED BY UART) if (state->mode != OF_OUT_BOTH || !atomic_load_explicit(&state->running, memory_order_relaxed)) { state->mode = OF_OUT_FILE; // to ensure that only file output is performed in this case state->thread = 0; if (thrd_create(&state->thread, &outputHandlerThread, state) != thrd_success) { caerLog(CAER_LOG_ALERT, SUBSYSTEM_FILE, "Failed to start output handler thread"); return (false); } state->buffer = ringBufferInit(bufferSize); if (state->buffer == NULL) { caerLog(CAER_LOG_ERROR, SUBSYSTEM_FILE, "Failed to allocate transfer ring-buffer."); return (false); } atomic_store(&state->running, true); } caerLog(CAER_LOG_NOTICE, SUBSYSTEM_FILE, "Logging events to fileName %s",fileName); return (true); }
bool posecalibration_findMarkers(PoseCalibration *calibClass, caerFrameEvent frame) { try { return (calibClass->findMarkers(frame)); } catch (const std::exception& ex) { caerLog(CAER_LOG_ERROR, "PoseCalibration_findMarkers()", "Failed with C++ exception: %s", ex.what()); return (false); } }
PoseCalibration *posecalibration_init(PoseCalibrationSettings settings) { try { return (new PoseCalibration(settings)); } catch (const std::exception& ex) { caerLog(CAER_LOG_ERROR, "PoseCalibration()", "Failed with C++ exception: %s", ex.what()); return (NULL); } }
bool posecalibration_loadCalibrationFile(PoseCalibration *calibClass, PoseCalibrationSettings settings) { try { return (calibClass->loadCalibrationFile(settings)); } catch (const std::exception& ex) { caerLog(CAER_LOG_ERROR, "PoseCalibration_loadCalibrationFile()", "Failed with C++ exception: %s", ex.what()); return (false); } }
void closeUartOutput(flowOutputState state) { state->running = false; uart_close(); if ((errno = thrd_join(state->thread, NULL)) != thrd_success) { // This should never happen! caerLog(CAER_LOG_CRITICAL, SUBSYSTEM_UART, "Failed to join output handling thread. Error: %d.", errno); } ringBufferFree(state->buffer); }
void caerConfigServerStop(void) { // Only execute if the server thread was actually started. if (!atomic_load_explicit(&configServerThread.running, memory_order_relaxed)) { return; } // Disable the configuration server thread first. atomic_store(&configServerThread.running, false); // Then wait on it to finish. if ((errno = thrd_join(configServerThread.thread, NULL)) == thrd_success) { // Successfully joined thread. caerLog(CAER_LOG_DEBUG, "Config Server", "Thread terminated successfully."); } else { // Failed to join thread. caerLog(CAER_LOG_EMERGENCY, "Config Server", "Failed to terminate thread. Error: %d.", errno); exit(EXIT_FAILURE); } }
static inline void caerConfigSendResponse(int connectedClientSocket, uint8_t action, uint8_t type, const uint8_t *msg, size_t msgLength) { size_t responseLength = 4 + msgLength; uint8_t response[responseLength]; response[0] = action; response[1] = type; setMsgLen(response, (uint16_t) msgLength); memcpy(response + 4, msg, msgLength); // Msg must already be NUL terminated! if (!sendUntilDone(connectedClientSocket, response, responseLength)) { // Error on send. Log for now. caerLog(CAER_LOG_DEBUG, "Config Server", "Disconnected client on send (fd %d).", connectedClientSocket); return; } caerLog(CAER_LOG_DEBUG, "Config Server", "Sent back message to client: action=%" PRIu8 ", type=%" PRIu8 ", msgLength=%zu.", action, type, msgLength); }
static inline void caerConfigSendError(int connectedClientSocket, const char *errorMsg) { size_t errorMsgLength = strlen(errorMsg); size_t responseLength = 4 + errorMsgLength + 1; // +1 for terminating NUL byte. uint8_t response[responseLength]; response[0] = ERROR; response[1] = STRING; setMsgLen(response, (uint16_t) (errorMsgLength + 1)); memcpy(response + 4, errorMsg, errorMsgLength); response[4 + errorMsgLength] = '\0'; if (!sendUntilDone(connectedClientSocket, response, responseLength)) { // Error on send. Log for now. caerLog(CAER_LOG_DEBUG, "Config Server", "Disconnected client on send (fd %d).", connectedClientSocket); return; } caerLog(CAER_LOG_DEBUG, "Config Server", "Sent back error message '%s' to client.", errorMsg); }
static inline bool writeFlowEventPacketFile(flowOutputState state, FlowEventPacket flow) { caerEventPacketHeader header = (caerEventPacketHeader) flow; if (caerEventPacketHeaderGetEventValid(header) == 0) { return (false); } for (int32_t i = 0; i < caerEventPacketHeaderGetEventNumber(header); i++) { FlowEvent e = &(flow->events[i]); if (e == NULL) { caerLog(CAER_LOG_ERROR,SUBSYSTEM_FILE,"Event null pointer found."); return (false); } if (!e->hasFlow) {continue;} // Get event data uint8_t x = (uint8_t) caerPolarityEventGetX((caerPolarityEvent) e); uint8_t y = (uint8_t) caerPolarityEventGetY((caerPolarityEvent) e); int32_t t = caerPolarityEventGetTimestamp((caerPolarityEvent) e); bool p = caerPolarityEventGetPolarity((caerPolarityEvent) e); double u = e->u; double v = e->v; // Write to file if (state->fileLineNumber < FILE_MAX_NUMBER_OF_LINES) { fprintf(state->file,"%3i,%3i,%10i,%d,%4.3f,%4.3f\n",x,y,t,p,u,v); state->fileLineNumber++; } else { // If too many lines, stop logging to prevent overrun if (state->fileLineNumber == FILE_MAX_NUMBER_OF_LINES) { caerLog(CAER_LOG_NOTICE, SUBSYSTEM_FILE, "File log reached limit of %d lines - " "no more lines will be added.", FILE_MAX_NUMBER_OF_LINES); state->fileLineNumber++; } } } return (true); }
static inline bool sendFlowEventPacketUart(FlowEventPacket flow) { caerEventPacketHeader header = (caerEventPacketHeader) flow; uint32_t packetSize = (uint32_t) caerEventPacketHeaderGetEventNumber(header); if (packetSize == 0) { // No events to send - return return (false); } // Events are separated by unsigned ints of value 255. This value should // never occur as pixel location unsigned char eventSeparator = 255; // Iterate through packet and send events for (uint32_t i = 0; i < packetSize; i++) { FlowEvent e = &(flow->events[i]); if (e == NULL) { caerLog(CAER_LOG_ERROR,SUBSYSTEM_UART,"Event null pointer found."); return (false); } if (!e->hasFlow) {continue;} uint8_t x = (uint8_t) caerPolarityEventGetX((caerPolarityEvent) e); uint8_t y = (uint8_t) caerPolarityEventGetY((caerPolarityEvent) e); int32_t t = caerPolarityEventGetTimestamp((caerPolarityEvent) e); int16_t u = (int16_t) (e->u*100); int16_t v = (int16_t) (e->v*100); // Send data over UART if (uart_tx(sizeof(x),(unsigned char*) &x) || uart_tx(sizeof(y),(unsigned char*) &y) || uart_tx(sizeof(t),(unsigned char*) &t) || uart_tx(sizeof(u),(unsigned char*) &u) || uart_tx(sizeof(v),(unsigned char*) &v) || uart_tx(sizeof(eventSeparator), &eventSeparator)) { caerLog(CAER_LOG_ERROR,SUBSYSTEM_UART,"Event info not fully sent."); return (false); } } return (true); }
static void orderAndSendEventPackets(outputCommonState state, caerEventPacketContainer currPacketContainer) { // Sort container by first timestamp (required) and by type ID (convenience). size_t currPacketContainerSize = (size_t) caerEventPacketContainerGetEventPacketsNumber(currPacketContainer); qsort(currPacketContainer->eventPackets, currPacketContainerSize, sizeof(caerEventPacketHeader), &packetsFirstTimestampThenTypeCmp); // Since we just got new data, let's first check that it does conform to our expectations. // This means the timestamp didn't slide back! So new smallest TS is >= than last highest TS. // These checks are needed to avoid illegal ordering. Normal operation will never trigger // these, as stated in the assumptions at the start of file, but erroneous usage or mixing // or reordering of packet containers is possible, and has to be caught here. int64_t highestTimestamp = 0; for (size_t cpIdx = 0; cpIdx < currPacketContainerSize; cpIdx++) { caerEventPacketHeader cpPacket = caerEventPacketContainerGetEventPacket(currPacketContainer, (int32_t) cpIdx); void *cpFirstEvent = caerGenericEventGetEvent(cpPacket, 0); int64_t cpFirstEventTimestamp = caerGenericEventGetTimestamp64(cpFirstEvent, cpPacket); if (cpFirstEventTimestamp < state->lastTimestamp) { // Smaller TS than already sent, illegal, ignore packet. caerLog(CAER_LOG_ERROR, state->parentModule->moduleSubSystemString, "Detected timestamp going back, expected at least %" PRIi64 " but got %" PRIi64 "." " Ignoring packet of type %" PRIi16 " from source %" PRIi16 ", with %" PRIi32 " events!", state->lastTimestamp, cpFirstEventTimestamp, caerEventPacketHeaderGetEventType(cpPacket), caerEventPacketHeaderGetEventSource(cpPacket), caerEventPacketHeaderGetEventNumber(cpPacket)); } else { // Bigger or equal TS than already sent, this is good. Strict TS ordering ensures // that all other packets in this container are the same, so we can start sending // the packets from here on out to the file descriptor. sendEventPacket(state, cpPacket); // Update highest timestamp for this packet container, based upon its valid packets. void *cpLastEvent = caerGenericEventGetEvent(cpPacket, caerEventPacketHeaderGetEventNumber(cpPacket) - 1); int64_t cpLastEventTimestamp = caerGenericEventGetTimestamp64(cpLastEvent, cpPacket); if (cpLastEventTimestamp > highestTimestamp) { highestTimestamp = cpLastEventTimestamp; } } } // Remember highest timestamp for check in next iteration. state->lastTimestamp = highestTimestamp; }
static inline void writeBufferToAll(outputCommonState state, const uint8_t *buffer, size_t bufferSize) { for (size_t i = 0; i < state->fileDescriptors->fdsSize; i++) { int fd = state->fileDescriptors->fds[i]; if (fd >= 0) { if (!writeUntilDone(fd, buffer, bufferSize)) { // Write failed, most of the reasons for that to happen are // not recoverable from, so we just disable this file descriptor. // This also detects client-side close() for TCP server connections. caerLog(CAER_LOG_INFO, state->parentModule->moduleSubSystemString, "Disconnect or error on fd %d, closing and removing. Error: %d.", fd, errno); close(fd); state->fileDescriptors->fds[i] = -1; } } } }
static void caerVisualizerEventHandlerNeuronMonitor(caerVisualizerPublicState state, const sf::Event &event) { // This only works with actual hardware. const std::string moduleLibrary = sshsNodeGetStdString(state->eventSourceConfigNode, "moduleLibrary"); if (moduleLibrary != "caer_dynapse") { return; } // On release of left click. if (event.type == sf::Event::MouseButtonReleased && event.mouseButton.button == sf::Mouse::Button::Left) { float positionX = (float) event.mouseButton.x; float positionY = (float) event.mouseButton.y; // Adjust coordinates according to zoom factor. float currentZoomFactor = state->renderZoomFactor.load(); if (currentZoomFactor > 1.0f) { positionX = floorf(positionX / currentZoomFactor); positionY = floorf(positionY / currentZoomFactor); } else if (currentZoomFactor < 1.0f) { positionX = floorf(positionX * currentZoomFactor); positionY = floorf(positionY * currentZoomFactor); } // Transform into chip ID, core ID and neuron ID. const struct caer_spike_event val = caerDynapseSpikeEventFromXY(U16T(positionX), U16T(positionY)); uint8_t chipId = caerSpikeEventGetChipID(&val); uint8_t coreId = caerSpikeEventGetSourceCoreID(&val); uint32_t neuronId = caerSpikeEventGetNeuronID(&val); // Set value via SSHS. sshsNode neuronMonitorNode = sshsGetRelativeNode(state->eventSourceConfigNode, "NeuronMonitor/"); char monitorKey[] = "Ux_Cy"; monitorKey[1] = (char) (48 + chipId); monitorKey[4] = (char) (48 + coreId); sshsNodePutInt(neuronMonitorNode, monitorKey, I32T(neuronId)); caerLog(CAER_LOG_NOTICE, "Visualizer", "Monitoring neuron - chip ID: %d, core ID: %d, neuron ID: %d.", chipId, coreId, neuronId); } }
bool caerModuleSetSubSystemString(caerModuleData moduleData, const char *subSystemString) { // Allocate new memory for new string. size_t subSystemStringLenght = strlen(subSystemString); char *newSubSystemString = malloc(subSystemStringLenght + 1); if (newSubSystemString == NULL) { // Failed to allocate memory. Log this and don't use the new string. caerLog(CAER_LOG_ERROR, moduleData->moduleSubSystemString, "Failed to allocate new sub-system string for module."); return (false); } // Copy new string into allocated memory. strncpy(newSubSystemString, subSystemString, subSystemStringLenght); newSubSystemString[subSystemStringLenght] = '\0'; // Switch new string with old string and free old memory. free(moduleData->moduleSubSystemString); moduleData->moduleSubSystemString = newSubSystemString; return (true); }
static void caerConfigServerHandleRequest(int connectedClientSocket, uint8_t action, uint8_t type, const uint8_t *extra, size_t extraLength, const uint8_t *node, size_t nodeLength, const uint8_t *key, size_t keyLength, const uint8_t *value, size_t valueLength) { UNUSED_ARGUMENT(extra); caerLog(CAER_LOG_DEBUG, "Config Server", "Handling request: action=%" PRIu8 ", type=%" PRIu8 ", extraLength=%zu, nodeLength=%zu, keyLength=%zu, valueLength=%zu.", action, type, extraLength, nodeLength, keyLength, valueLength); // Interpretation of data is up to each action individually. sshs configStore = sshsGetGlobal(); switch (action) { case NODE_EXISTS: { // We only need the node name here. Type is not used (ignored)! bool result = sshsExistsNode(configStore, (const char *) node); // Send back result to client. Format is the same as incoming data. const uint8_t *sendResult = (const uint8_t *) ((result) ? ("true") : ("false")); size_t sendResultLength = (result) ? (5) : (6); caerConfigSendResponse(connectedClientSocket, NODE_EXISTS, BOOL, sendResult, sendResultLength); break; } case ATTR_EXISTS: { bool nodeExists = sshsExistsNode(configStore, (const char *) node); // Only allow operations on existing nodes, this is for remote // control, so we only manipulate what's already there! if (!nodeExists) { // Send back error message to client. caerConfigSendError(connectedClientSocket, "Node doesn't exist. Operations are only allowed on existing data."); break; } // This cannot fail, since we know the node exists from above. sshsNode wantedNode = sshsGetNode(configStore, (const char *) node); // Check if attribute exists. bool result = sshsNodeAttributeExists(wantedNode, (const char *) key, type); // Send back result to client. Format is the same as incoming data. const uint8_t *sendResult = (const uint8_t *) ((result) ? ("true") : ("false")); size_t sendResultLength = (result) ? (5) : (6); caerConfigSendResponse(connectedClientSocket, ATTR_EXISTS, BOOL, sendResult, sendResultLength); break; } case GET: { bool nodeExists = sshsExistsNode(configStore, (const char *) node); // Only allow operations on existing nodes, this is for remote // control, so we only manipulate what's already there! if (!nodeExists) { // Send back error message to client. caerConfigSendError(connectedClientSocket, "Node doesn't exist. Operations are only allowed on existing data."); break; } // This cannot fail, since we know the node exists from above. sshsNode wantedNode = sshsGetNode(configStore, (const char *) node); // Check if attribute exists. Only allow operations on existing attributes! bool attrExists = sshsNodeAttributeExists(wantedNode, (const char *) key, type); if (!attrExists) { // Send back error message to client. caerConfigSendError(connectedClientSocket, "Attribute of given type doesn't exist. Operations are only allowed on existing data."); break; } union sshs_node_attr_value result = sshsNodeGetAttribute(wantedNode, (const char *) key, type); char *resultStr = sshsHelperValueToStringConverter(type, result); if (resultStr == NULL) { // Send back error message to client. caerConfigSendError(connectedClientSocket, "Failed to allocate memory for value string."); } else { caerConfigSendResponse(connectedClientSocket, GET, type, (const uint8_t *) resultStr, strlen(resultStr) + 1); free(resultStr); } // If this is a string, we must remember to free the original result.str // too, since it will also be a copy of the string coming from SSHS. if (type == STRING) { free(result.string); } break; } case PUT: { bool nodeExists = sshsExistsNode(configStore, (const char *) node); // Only allow operations on existing nodes, this is for remote // control, so we only manipulate what's already there! if (!nodeExists) { // Send back error message to client. caerConfigSendError(connectedClientSocket, "Node doesn't exist. Operations are only allowed on existing data."); break; } // This cannot fail, since we know the node exists from above. sshsNode wantedNode = sshsGetNode(configStore, (const char *) node); // Check if attribute exists. Only allow operations on existing attributes! bool attrExists = sshsNodeAttributeExists(wantedNode, (const char *) key, type); if (!attrExists) { // Send back error message to client. caerConfigSendError(connectedClientSocket, "Attribute of given type doesn't exist. Operations are only allowed on existing data."); break; } // Put given value into config node. Node, attr and type are already verified. const char *typeStr = sshsHelperTypeToStringConverter(type); if (!sshsNodeStringToNodeConverter(wantedNode, (const char *) key, typeStr, (const char *) value)) { // Send back error message to client. caerConfigSendError(connectedClientSocket, "Impossible to convert value according to type."); break; } // Send back confirmation to the client. caerConfigSendResponse(connectedClientSocket, PUT, BOOL, (const uint8_t *) "true", 5); break; } case GET_CHILDREN: { bool nodeExists = sshsExistsNode(configStore, (const char *) node); // Only allow operations on existing nodes, this is for remote // control, so we only manipulate what's already there! if (!nodeExists) { // Send back error message to client. caerConfigSendError(connectedClientSocket, "Node doesn't exist. Operations are only allowed on existing data."); break; } // This cannot fail, since we know the node exists from above. sshsNode wantedNode = sshsGetNode(configStore, (const char *) node); // Get the names of all the child nodes and return them. size_t numNames; const char **childNames = sshsNodeGetChildNames(wantedNode, &numNames); // No children at all, return empty. if (childNames == NULL) { // Send back error message to client. caerConfigSendError(connectedClientSocket, "Node has no children."); break; } // We need to return a big string with all of the child names, // separated by a NUL character. size_t namesLength = 0; for (size_t i = 0; i < numNames; i++) { namesLength += strlen(childNames[i]) + 1; // +1 for terminating NUL byte. } // Allocate a buffer for the names and copy them over. char namesBuffer[namesLength]; for (size_t i = 0, acc = 0; i < numNames; i++) { size_t len = strlen(childNames[i]) + 1; memcpy(namesBuffer + acc, childNames[i], len); acc += len; } caerConfigSendResponse(connectedClientSocket, GET_CHILDREN, STRING, (const uint8_t *) namesBuffer, namesLength); break; } case GET_ATTRIBUTES: { bool nodeExists = sshsExistsNode(configStore, (const char *) node); // Only allow operations on existing nodes, this is for remote // control, so we only manipulate what's already there! if (!nodeExists) { // Send back error message to client. caerConfigSendError(connectedClientSocket, "Node doesn't exist. Operations are only allowed on existing data."); break; } // This cannot fail, since we know the node exists from above. sshsNode wantedNode = sshsGetNode(configStore, (const char *) node); // Get the keys of all the attributes and return them. size_t numKeys; const char **attrKeys = sshsNodeGetAttributeKeys(wantedNode, &numKeys); // No attributes at all, return empty. if (attrKeys == NULL) { // Send back error message to client. caerConfigSendError(connectedClientSocket, "Node has no attributes."); break; } // We need to return a big string with all of the attribute keys, // separated by a NUL character. size_t keysLength = 0; for (size_t i = 0; i < numKeys; i++) { keysLength += strlen(attrKeys[i]) + 1; // +1 for terminating NUL byte. } // Allocate a buffer for the keys and copy them over. char keysBuffer[keysLength]; for (size_t i = 0, acc = 0; i < numKeys; i++) { size_t len = strlen(attrKeys[i]) + 1; memcpy(keysBuffer + acc, attrKeys[i], len); acc += len; } caerConfigSendResponse(connectedClientSocket, GET_ATTRIBUTES, STRING, (const uint8_t *) keysBuffer, keysLength); break; } case GET_TYPES: { bool nodeExists = sshsExistsNode(configStore, (const char *) node); // Only allow operations on existing nodes, this is for remote // control, so we only manipulate what's already there! if (!nodeExists) { // Send back error message to client. caerConfigSendError(connectedClientSocket, "Node doesn't exist. Operations are only allowed on existing data."); break; } // This cannot fail, since we know the node exists from above. sshsNode wantedNode = sshsGetNode(configStore, (const char *) node); // Check if any keys match the given one and return its types. size_t numTypes; enum sshs_node_attr_value_type *attrTypes = sshsNodeGetAttributeTypes(wantedNode, (const char *) key, &numTypes); // No attributes for specified key, return empty. if (attrTypes == NULL) { // Send back error message to client. caerConfigSendError(connectedClientSocket, "Node has no attributes with specified key."); break; } // We need to return a big string with all of the attribute types, // separated by a NUL character. size_t typesLength = 0; for (size_t i = 0; i < numTypes; i++) { const char *typeString = sshsHelperTypeToStringConverter(attrTypes[i]); typesLength += strlen(typeString) + 1; // +1 for terminating NUL byte. } // Allocate a buffer for the types and copy them over. char typesBuffer[typesLength]; for (size_t i = 0, acc = 0; i < numTypes; i++) { const char *typeString = sshsHelperTypeToStringConverter(attrTypes[i]); size_t len = strlen(typeString) + 1; memcpy(typesBuffer + acc, typeString, len); acc += len; } caerConfigSendResponse(connectedClientSocket, GET_TYPES, STRING, (const uint8_t *) typesBuffer, typesLength); break; } default: // Unknown action, send error back to client. caerConfigSendError(connectedClientSocket, "Unknown action."); break; } }
static int outputHandlerThread(void *stateArg) { if (stateArg == NULL) { return (thrd_error); } flowOutputState state = stateArg; switch (state->mode) { case OF_OUT_UART: thrd_set_name(SUBSYSTEM_UART); break; case OF_OUT_FILE: thrd_set_name(SUBSYSTEM_FILE); break; case OF_OUT_BOTH: thrd_set_name("FLOW_OUTPUT"); break; case OF_OUT_NONE: break; } struct timespec sleepTime = { .tv_sec = 0, .tv_nsec = 500000 }; // Wait until the buffer is initialized while (!atomic_load_explicit(&state->running, memory_order_relaxed)) { thrd_sleep(&sleepTime, NULL); } // Main thread loop while (atomic_load_explicit(&state->running, memory_order_relaxed)) { FlowEventPacket packet = getPacketFromTransferBuffer(state->buffer); if (packet == NULL) { // no data: sleep for a while thrd_sleep(&sleepTime, NULL); } else { if (state->mode == OF_OUT_UART || state->mode == OF_OUT_BOTH) { if (!sendFlowEventPacketUart(packet)) { caerLog(CAER_LOG_ALERT, SUBSYSTEM_UART, "A flow packet was not sent."); } } if (state->mode == OF_OUT_FILE || state->mode == OF_OUT_BOTH) { if (!writeFlowEventPacketFile(state,packet)) { caerLog(CAER_LOG_ALERT, SUBSYSTEM_FILE, "A flow packet was not written."); } } flowEventPacketFree(packet); } } // If shutdown: empty buffer before closing thread FlowEventPacket packet; while ((packet = getPacketFromTransferBuffer(state->buffer)) != NULL) { if (state->mode == OF_OUT_UART || state->mode == OF_OUT_BOTH) { if (!sendFlowEventPacketUart(packet)) { caerLog(CAER_LOG_ALERT, SUBSYSTEM_UART, "A flow packet was not sent."); } } if (state->mode == OF_OUT_FILE || state->mode == OF_OUT_BOTH) { if (!writeFlowEventPacketFile(state,packet)) { caerLog(CAER_LOG_ALERT, SUBSYSTEM_FILE, "A flow packet was not written."); } } flowEventPacketFree(packet); } return (thrd_success); }
static bool caerInputDVS128Init(caerModuleData moduleData) { caerLog(CAER_LOG_DEBUG, moduleData->moduleSubSystemString, "Initializing module ..."); // USB port/bus/SN settings/restrictions. // These can be used to force connection to one specific device at startup. sshsNodePutShortIfAbsent(moduleData->moduleNode, "BusNumber", 0); sshsNodePutShortIfAbsent(moduleData->moduleNode, "DevAddress", 0); sshsNodePutStringIfAbsent(moduleData->moduleNode, "SerialNumber", ""); // Add auto-restart setting. sshsNodePutBoolIfAbsent(moduleData->moduleNode, "Auto-Restart", true); // Start data acquisition, and correctly notify mainloop of new data and module of exceptional // shutdown cases (device pulled, ...). char *serialNumber = sshsNodeGetString(moduleData->moduleNode, "SerialNumber"); moduleData->moduleState = caerDeviceOpen(moduleData->moduleID, CAER_DEVICE_DVS128, U8T(sshsNodeGetShort(moduleData->moduleNode, "BusNumber")), U8T(sshsNodeGetShort(moduleData->moduleNode, "DevAddress")), serialNumber); free(serialNumber); if (moduleData->moduleState == NULL) { // Failed to open device. return (false); } // Put global source information into SSHS. struct caer_dvs128_info devInfo = caerDVS128InfoGet(moduleData->moduleState); sshsNode sourceInfoNode = sshsGetRelativeNode(moduleData->moduleNode, "sourceInfo/"); sshsNodePutShort(sourceInfoNode, "logicVersion", devInfo.logicVersion); sshsNodePutBool(sourceInfoNode, "deviceIsMaster", devInfo.deviceIsMaster); sshsNodePutShort(sourceInfoNode, "dvsSizeX", devInfo.dvsSizeX); sshsNodePutShort(sourceInfoNode, "dvsSizeY", devInfo.dvsSizeY); // Put source information for "virtual" APS frame that can be used to display and debug filter information. sshsNodePutShort(sourceInfoNode, "apsSizeX", devInfo.dvsSizeX); sshsNodePutShort(sourceInfoNode, "apsSizeY", devInfo.dvsSizeY); caerModuleSetSubSystemString(moduleData, devInfo.deviceString); // Ensure good defaults for data acquisition settings. // No blocking behavior due to mainloop notification, and no auto-start of // all producers to ensure cAER settings are respected. caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_DATAEXCHANGE, CAER_HOST_CONFIG_DATAEXCHANGE_BLOCKING, false); caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_DATAEXCHANGE, CAER_HOST_CONFIG_DATAEXCHANGE_START_PRODUCERS, false); caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_DATAEXCHANGE, CAER_HOST_CONFIG_DATAEXCHANGE_STOP_PRODUCERS, true); // Create default settings and send them to the device. createDefaultConfiguration(moduleData); sendDefaultConfiguration(moduleData); // Start data acquisition. bool ret = caerDeviceDataStart(moduleData->moduleState, &mainloopDataNotifyIncrease, &mainloopDataNotifyDecrease, caerMainloopGetReference(), &moduleShutdownNotify, moduleData->moduleNode); if (!ret) { // Failed to start data acquisition, close device and exit. caerDeviceClose((caerDeviceHandle *) &moduleData->moduleState); return (false); } return (true); }
static bool caerOutputUnixSocketServerInit(caerModuleData moduleData) { // First, always create all needed setting nodes, set their default values // and add their listeners. sshsNodePutStringIfAbsent(moduleData->moduleNode, "socketPath", "/tmp/caer.sock"); sshsNodePutShortIfAbsent(moduleData->moduleNode, "backlogSize", 5); sshsNodePutShortIfAbsent(moduleData->moduleNode, "concurrentConnections", 10); // Open a Unix local socket on a known path, to be accessed by other processes (server-like mode). int serverSockFd = socket(AF_UNIX, SOCK_STREAM, 0); if (serverSockFd < 0) { caerLog(CAER_LOG_CRITICAL, moduleData->moduleSubSystemString, "Could not create local Unix socket. Error: %d.", errno); return (false); } struct sockaddr_un unixSocketAddr; memset(&unixSocketAddr, 0, sizeof(struct sockaddr_un)); unixSocketAddr.sun_family = AF_UNIX; char *socketPath = sshsNodeGetString(moduleData->moduleNode, "socketPath"); strncpy(unixSocketAddr.sun_path, socketPath, sizeof(unixSocketAddr.sun_path) - 1); unixSocketAddr.sun_path[sizeof(unixSocketAddr.sun_path) - 1] = '\0'; // Ensure NUL terminated string. free(socketPath); // Bind socket to above address. if (bind(serverSockFd, (struct sockaddr *) &unixSocketAddr, sizeof(struct sockaddr_un)) < 0) { close(serverSockFd); caerLog(CAER_LOG_CRITICAL, moduleData->moduleSubSystemString, "Could not bind local Unix socket. Error: %d.", errno); return (false); } // Listen to new connections on the socket. if (listen(serverSockFd, sshsNodeGetShort(moduleData->moduleNode, "backlogSize")) < 0) { close(serverSockFd); caerLog(CAER_LOG_CRITICAL, moduleData->moduleSubSystemString, "Could not listen on local Unix socket. Error: %d.", errno); return (false); } outputCommonFDs fileDescriptors = caerOutputCommonAllocateFdArray( (size_t) sshsNodeGetShort(moduleData->moduleNode, "concurrentConnections")); if (fileDescriptors == NULL) { close(serverSockFd); caerLog(CAER_LOG_CRITICAL, moduleData->moduleSubSystemString, "Unable to allocate memory for file descriptors."); return (false); } fileDescriptors->serverFd = serverSockFd; if (!caerOutputCommonInit(moduleData, fileDescriptors, true, false)) { close(serverSockFd); free(fileDescriptors); return (false); } caerLog(CAER_LOG_INFO, moduleData->moduleSubSystemString, "Local Unix socket ready at '%s'.", unixSocketAddr.sun_path); return (true); }
int main(void) { // Install signal handler for global shutdown. #if defined(_WIN32) if (signal(SIGTERM, &globalShutdownSignalHandler) == SIG_ERR) { caerLog(CAER_LOG_CRITICAL, "ShutdownAction", "Failed to set signal handler for SIGTERM. Error: %d.", errno); return (EXIT_FAILURE); } if (signal(SIGINT, &globalShutdownSignalHandler) == SIG_ERR) { caerLog(CAER_LOG_CRITICAL, "ShutdownAction", "Failed to set signal handler for SIGINT. Error: %d.", errno); return (EXIT_FAILURE); } #else struct sigaction shutdownAction; shutdownAction.sa_handler = &globalShutdownSignalHandler; shutdownAction.sa_flags = 0; sigemptyset(&shutdownAction.sa_mask); sigaddset(&shutdownAction.sa_mask, SIGTERM); sigaddset(&shutdownAction.sa_mask, SIGINT); if (sigaction(SIGTERM, &shutdownAction, NULL) == -1) { caerLog(CAER_LOG_CRITICAL, "ShutdownAction", "Failed to set signal handler for SIGTERM. Error: %d.", errno); return (EXIT_FAILURE); } if (sigaction(SIGINT, &shutdownAction, NULL) == -1) { caerLog(CAER_LOG_CRITICAL, "ShutdownAction", "Failed to set signal handler for SIGINT. Error: %d.", errno); return (EXIT_FAILURE); } #endif // Open a DVS128, give it a device ID of 1, and don't care about USB bus or SN restrictions. caerDeviceHandle dvs128_handle = caerDeviceOpen(1, CAER_DEVICE_DVS128, 0, 0, NULL); if (dvs128_handle == NULL) { return (EXIT_FAILURE); } // Let's take a look at the information we have on the device. struct caer_dvs128_info dvs128_info = caerDVS128InfoGet(dvs128_handle); printf("%s --- ID: %d, Master: %d, DVS X: %d, DVS Y: %d, Logic: %d.\n", dvs128_info.deviceString, dvs128_info.deviceID, dvs128_info.deviceIsMaster, dvs128_info.dvsSizeX, dvs128_info.dvsSizeY, dvs128_info.logicVersion); // Send the default configuration before using the device. // No configuration is sent automatically! caerDeviceSendDefaultConfig(dvs128_handle); // Tweak some biases, to increase bandwidth in this case. caerDeviceConfigSet(dvs128_handle, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_PR, 695); caerDeviceConfigSet(dvs128_handle, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_FOLL, 867); // Let's verify they really changed! uint32_t prBias, follBias; caerDeviceConfigGet(dvs128_handle, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_PR, &prBias); caerDeviceConfigGet(dvs128_handle, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_FOLL, &follBias); printf("New bias values --- PR: %d, FOLL: %d.\n", prBias, follBias); // Now let's get start getting some data from the device. We just loop in blocking mode, // no notification needed regarding new events. The shutdown notification, for example if // the device is disconnected, should be listened to. caerDeviceDataStart(dvs128_handle, NULL, NULL, NULL, &usbShutdownHandler, NULL); // Let's turn on blocking data-get mode to avoid wasting resources. caerDeviceConfigSet(dvs128_handle, CAER_HOST_CONFIG_DATAEXCHANGE, CAER_HOST_CONFIG_DATAEXCHANGE_BLOCKING, true); while (!atomic_load_explicit(&globalShutdown, memory_order_relaxed)) { caerEventPacketContainer packetContainer = caerDeviceDataGet(dvs128_handle); if (packetContainer == NULL) { continue; // Skip if nothing there. } int32_t packetNum = caerEventPacketContainerGetEventPacketsNumber(packetContainer); printf("\nGot event container with %d packets (allocated).\n", packetNum); for (int32_t i = 0; i < packetNum; i++) { caerEventPacketHeader packetHeader = caerEventPacketContainerGetEventPacket(packetContainer, i); if (packetHeader == NULL) { printf("Packet %d is empty (not present).\n", i); continue; // Skip if nothing there. } printf("Packet %d of type %d -> size is %d.\n", i, caerEventPacketHeaderGetEventType(packetHeader), caerEventPacketHeaderGetEventNumber(packetHeader)); // Packet 0 is always the special events packet for DVS128, while packet is the polarity events packet. if (i == POLARITY_EVENT) { caerPolarityEventPacket polarity = (caerPolarityEventPacket) packetHeader; // Get full timestamp and addresses of first event. caerPolarityEvent firstEvent = caerPolarityEventPacketGetEvent(polarity, 0); int32_t ts = caerPolarityEventGetTimestamp(firstEvent); uint16_t x = caerPolarityEventGetX(firstEvent); uint16_t y = caerPolarityEventGetY(firstEvent); bool pol = caerPolarityEventGetPolarity(firstEvent); printf("First polarity event - ts: %d, x: %d, y: %d, pol: %d.\n", ts, x, y, pol); } } caerEventPacketContainerFree(packetContainer); } caerDeviceDataStop(dvs128_handle); caerDeviceClose(&dvs128_handle); printf("Shutdown successful.\n"); return (EXIT_SUCCESS); }
/** * Copy event packets to the ring buffer for transfer to the output handler thread. * * @param state output module state. * @param packetsListSize the length of the variable-length argument list of event packets. * @param packetsList a variable-length argument list of event packets. */ static void copyPacketsToTransferRing(outputCommonState state, size_t packetsListSize, va_list packetsList) { caerEventPacketHeader packets[packetsListSize]; size_t packetsSize = 0; // Count how many packets are really there, skipping empty event packets. for (size_t i = 0; i < packetsListSize; i++) { caerEventPacketHeader packetHeader = va_arg(packetsList, caerEventPacketHeader); // Found non-empty event packet. if (packetHeader != NULL) { // Get source information from the event packet. int16_t eventSource = caerEventPacketHeaderGetEventSource(packetHeader); // Check that source is unique. int16_t sourceID = I16T(atomic_load_explicit(&state->sourceID, memory_order_relaxed)); if (sourceID == -1) { state->sourceInfoNode = caerMainloopGetSourceInfo(U16T(eventSource)); if (state->sourceInfoNode == NULL) { // This should never happen, but we handle it gracefully. caerLog(CAER_LOG_ERROR, state->parentModule->moduleSubSystemString, "Failed to get source info to setup output module."); return; } atomic_store(&state->sourceID, eventSource); // Remember this! } else if (sourceID != eventSource) { caerLog(CAER_LOG_ERROR, state->parentModule->moduleSubSystemString, "An output module can only handle packets from the same source! " "A packet with source %" PRIi16 " was sent, but this output module expects only packets from source %" PRIi16 ".", eventSource, sourceID); continue; } // Source ID is correct, packet is not empty, we got it! packets[packetsSize++] = packetHeader; } } // There was nothing in this mainloop run! if (packetsSize == 0) { return; } // Allocate memory for event packet array structure that will get passed to output handler thread. caerEventPacketContainer eventPackets = caerEventPacketContainerAllocate((int32_t) packetsSize); if (eventPackets == NULL) { return; } // Handle the valid only flag here, that way we don't have to do another copy and // process it in the output handling thread. We get the value once here, so we do // the same for all packets from the same mainloop run, avoiding mid-way changes. bool validOnly = atomic_load_explicit(&state->validOnly, memory_order_relaxed); // Now copy each event packet and send the array out. Track how many packets there are. size_t idx = 0; for (size_t i = 0; i < packetsSize; i++) { if (validOnly) { caerEventPacketContainerSetEventPacket(eventPackets, (int32_t) idx, caerCopyEventPacketOnlyValidEvents(packets[i])); } else { caerEventPacketContainerSetEventPacket(eventPackets, (int32_t) idx, caerCopyEventPacketOnlyEvents(packets[i])); } if (caerEventPacketContainerGetEventPacket(eventPackets, (int32_t) idx) == NULL) { // Failed to copy packet. Signal but try to continue anyway. if ((validOnly && (caerEventPacketHeaderGetEventValid(packets[i]) == 0)) || (!validOnly && (caerEventPacketHeaderGetEventNumber(packets[i]) == 0))) { caerLog(CAER_LOG_NOTICE, state->parentModule->moduleSubSystemString, "Submitted empty event packet to output. Ignoring empty event packet."); } else { caerLog(CAER_LOG_ERROR, state->parentModule->moduleSubSystemString, "Failed to copy event packet to output."); } } else { idx++; } } // We might have failed to copy all packets (unlikely). if (idx == 0) { caerEventPacketContainerFree(eventPackets); return; } // Reset packet container size so we only consider the packets we managed // to successfully copy. caerEventPacketContainerSetEventPacketsNumber(eventPackets, (int32_t) idx); retry: if (!ringBufferPut(state->transferRing, eventPackets)) { if (atomic_load_explicit(&state->keepPackets, memory_order_relaxed)) { // Retry forever if requested. goto retry; } caerEventPacketContainerFree(eventPackets); caerLog(CAER_LOG_INFO, state->parentModule->moduleSubSystemString, "Failed to put packet's array copy on transfer ring-buffer: full."); return; } }
static inline bool caerFrameEventPNGCompress(uint8_t **outBuffer, size_t *outSize, uint16_t *inBuffer, int32_t xSize, int32_t ySize, enum caer_frame_event_color_channels channels) { png_structp png_ptr = NULL; png_infop info_ptr = NULL; png_byte **row_pointers = NULL; // Initialize the write struct. png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { return (false); } // Initialize the info struct. info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { png_destroy_write_struct(&png_ptr, NULL); return (false); } // Set up error handling. if (setjmp(png_jmpbuf(png_ptr))) { if (row_pointers != NULL) { png_free(png_ptr, row_pointers); } png_destroy_write_struct(&png_ptr, &info_ptr); return (false); } // Set image attributes. png_set_IHDR(png_ptr, info_ptr, (png_uint_32) xSize, (png_uint_32) ySize, 16, caerFrameEventColorToLibPNG(channels), PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // Handle endianness of 16-bit depth pixels correctly. // PNG assumes big-endian, our Frame Event is always little-endian. png_set_swap(png_ptr); // Initialize rows of PNG. row_pointers = png_malloc(png_ptr, (size_t) ySize * sizeof(png_byte *)); if (row_pointers == NULL) { png_destroy_write_struct(&png_ptr, &info_ptr); return (false); } for (size_t y = 0; y < (size_t) ySize; y++) { row_pointers[y] = (png_byte *) &inBuffer[y * (size_t) xSize * channels]; } // Set write function to buffer one. struct mem_encode state = { .buffer = NULL, .size = 0 }; png_set_write_fn(png_ptr, &state, &caerLibPNGWriteBuffer, NULL); // Actually write the image data. png_set_rows(png_ptr, info_ptr, row_pointers); png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); // Free allocated memory for rows. png_free(png_ptr, row_pointers); // Destroy main structs. png_destroy_write_struct(&png_ptr, &info_ptr); // Pass out buffer with resulting PNG image. *outBuffer = state.buffer; *outSize = state.size; return (true); } #endif static size_t compressEventPacket(outputCommonState state, caerEventPacketHeader packet, size_t packetSize) { // Data compression technique 1: serialize timestamps for event types that tend to repeat them a lot. // Currently, this means polarity events. if ((state->format & 0x01) && caerEventPacketHeaderGetEventType(packet) == POLARITY_EVENT) { // Search for runs of at least 3 events with the same timestamp, and convert them to a special // sequence: leave first event unchanged, but mark its timestamp as special by setting the // highest bit (bit 31) to one (it is forbidden for timestamps in memory to have that bit set for // signed-integer-only language compatibility). Then, for the second event, change its timestamp // to a 4-byte integer saying how many more events will follow afterwards with this same timestamp // (this is used for decoding), so only their data portion will be given. Then follow with those // event's data, back to back, with their timestamps removed. // So let's assume there are 6 events with TS=1234. In memory this looks like this: // E1(data,ts), E2(data,ts), E3(data,ts), E4(data,ts), E5(data,ts), E6(data,ts) // After timestamp serialization compression step: // E1(data,ts|0x80000000), E2(data,4), E3(data), E4(data), E5(data), E5(data) // This change is only in the data itself, not in the packet headers, so that we can still use the // eventCapacity and eventSize fields to calculate memory allocation when doing decompression. // As such, to correctly interpret this data, the Format flags must be correctly set. All current // file or network formats do specify those as mandatory in their headers, so we can rely on that. // Also all event types where this kind of thing makes any sense do have the timestamp as their last // data member in their struct, so we can use that information, stored in tsOffset header field, // together with eventSize, to come up with a generic implementation applicable to all other event // types that satisfy this condition of TS-as-last-member (so we can use that offset as event size). // When this is enabled, it requires full iteration thorough the whole event packet, both at // compression and at decompression time. size_t currPacketOffset = CAER_EVENT_PACKET_HEADER_SIZE; // Start here, no change to header. int32_t eventSize = caerEventPacketHeaderGetEventSize(packet); int32_t eventTSOffset = caerEventPacketHeaderGetEventTSOffset(packet); int32_t lastTS = -1; int32_t currTS = -1; size_t tsRun = 0; bool doMemMove = false; // Initially don't move memory, until we actually shrink the size. for (int32_t caerIteratorCounter = 0; caerIteratorCounter <= caerEventPacketHeaderGetEventNumber(packet); caerIteratorCounter++) { // Iterate until one element past the end, to flush the last run. In that particular case, // we don't get a new element or TS, as we'd be past the end of the array. if (caerIteratorCounter < caerEventPacketHeaderGetEventNumber(packet)) { void *caerIteratorElement = caerGenericEventGetEvent(packet, caerIteratorCounter); currTS = caerGenericEventGetTimestamp(caerIteratorElement, packet); if (currTS == lastTS) { // Increase size of run of same TS events currently being seen. tsRun++; continue; } } // TS are different, at this point look if the last run was long enough // and if it makes sense to compress. It does starting with 3 events. if (tsRun >= 3) { // First event to remains there, we set its TS highest bit. uint8_t *firstEvent = caerGenericEventGetEvent(packet, caerIteratorCounter - (int32_t) tsRun--); caerGenericEventSetTimestamp(firstEvent, packet, caerGenericEventGetTimestamp(firstEvent, packet) | I32T(0x80000000)); // Now use second event's timestamp for storing how many further events. uint8_t *secondEvent = caerGenericEventGetEvent(packet, caerIteratorCounter - (int32_t) tsRun--); caerGenericEventSetTimestamp(secondEvent, packet, I32T(tsRun)); // Is at least 1. // Finally move modified memory where it needs to go. if (doMemMove) { memmove(((uint8_t *) packet) + currPacketOffset, firstEvent, (size_t) eventSize * 2); } else { doMemMove = true; // After first shrink always move memory. } currPacketOffset += (size_t) eventSize * 2; // Now go through remaining events and move their data close together. while (tsRun > 0) { uint8_t *thirdEvent = caerGenericEventGetEvent(packet, caerIteratorCounter - (int32_t) tsRun--); memmove(((uint8_t *) packet) + currPacketOffset, thirdEvent, (size_t) eventTSOffset); currPacketOffset += (size_t) eventTSOffset; } } else { // Just copy data unchanged if no compression is possible. if (doMemMove) { uint8_t *startEvent = caerGenericEventGetEvent(packet, caerIteratorCounter - (int32_t) tsRun); memmove(((uint8_t *) packet) + currPacketOffset, startEvent, (size_t) eventSize * tsRun); } currPacketOffset += (size_t) eventSize * tsRun; } // Reset values for next iteration. lastTS = currTS; tsRun = 1; } return (currPacketOffset); } #ifdef ENABLE_INOUT_PNG_COMPRESSION // Data compression technique 2: do PNG compression on frames, Grayscale and RGB(A). if ((state->format & 0x02) && caerEventPacketHeaderGetEventType(packet) == FRAME_EVENT) { size_t currPacketOffset = CAER_EVENT_PACKET_HEADER_SIZE; // Start here, no change to header. size_t frameHeaderSize = sizeof(struct caer_frame_event); CAER_FRAME_ITERATOR_ALL_START((caerFrameEventPacket) packet) size_t pixelSize = caerFrameEventGetPixelsSize(caerFrameIteratorElement); // Keep frame event header intact, compress image data, move memory close together. memmove(((uint8_t *) packet) + currPacketOffset, caerFrameIteratorElement, frameHeaderSize); currPacketOffset += frameHeaderSize; uint8_t *outBuffer; size_t outSize; if (!caerFrameEventPNGCompress(&outBuffer, &outSize, caerFrameEventGetPixelArrayUnsafe(caerFrameIteratorElement), caerFrameEventGetLengthX(caerFrameIteratorElement), caerFrameEventGetLengthY(caerFrameIteratorElement), caerFrameEventGetChannelNumber(caerFrameIteratorElement))) { // Failed to generate PNG. // Discard this frame event. currPacketOffset -= frameHeaderSize; continue; } // Check that the image didn't actually grow. // Add integer needed for storing PNG block length. if ((outSize + sizeof(int32_t)) > pixelSize) { caerLog(CAER_LOG_ERROR, state->parentModule->moduleSubSystemString, "Failed to compress frame event. " "Image actually grew by %zu bytes to a total of %zu bytes.", (outSize - pixelSize), outSize); free(outBuffer); currPacketOffset -= frameHeaderSize; continue; } // Store size of PNG image block as 4 byte integer. int32_t outSizeInt = I32T(outSize); memcpy(((uint8_t *) packet) + currPacketOffset, &outSizeInt, sizeof(int32_t)); currPacketOffset += sizeof(int32_t); memcpy(((uint8_t *) packet) + currPacketOffset, outBuffer, outSize); currPacketOffset += outSize; // Free allocated PNG block memory. free(outBuffer); } return (currPacketOffset); } #endif return (packetSize); }
static void handleNewServerConnections(outputCommonState state) { // First let's see if any new connections are waiting on the listening // socket to be accepted. This returns right away (non-blocking). socklen_t clientAddrLength = sizeof(struct sockaddr_in); struct sockaddr_in clientAddr; memset(&clientAddr, 0, clientAddrLength); int acceptResult = accept(state->fileDescriptors->serverFd, (struct sockaddr *) &clientAddr, &clientAddrLength); if (acceptResult < 0) { if (errno != EAGAIN && errno != EWOULDBLOCK) { // Only log real failure. EAGAIN/EWOULDBLOCK just means no // connections are present for non-blocking accept() right now. caerLog(CAER_LOG_ERROR, state->parentModule->moduleSubSystemString, "TCP server accept() failed. Error: %d.", errno); } return; } // New connection present! // Put it in the list of FDs and send header, if there is space left, or close. for (size_t i = 0; i < state->fileDescriptors->fdsSize; i++) { if (state->fileDescriptors->fds[i] == -1) { caerLog(CAER_LOG_DEBUG, state->parentModule->moduleSubSystemString, "Accepted new TCP connection from client (fd %d).", acceptResult); // Commit current buffer, so that for the new clients, it's empty, and they // don't get packets and data that are possibly cut in the middle. commitOutputBuffer(state); // Empty place in FD list, add this one. state->fileDescriptors->fds[i] = acceptResult; // Successfully connected, send header to client. sendNetworkHeader(state, &state->fileDescriptors->fds[i]); // Add client IP to list. This is a comma separated string. char *connectedClientsStr = sshsNodeGetString(state->parentModule->moduleNode, "connectedClients"); size_t newConnectedClientStrLength = strlen(connectedClientsStr) + 1 + INET_ADDRSTRLEN + 1; char *newConnectedClientsStr = realloc(connectedClientsStr, newConnectedClientStrLength); if (newConnectedClientsStr == NULL) { free(connectedClientsStr); caerLog(CAER_LOG_ERROR, state->parentModule->moduleSubSystemString, "Failed to update connectedClients information."); return; } const char *clientAddrStr = inet_ntop(AF_INET, &clientAddr.sin_addr, (char[INET_ADDRSTRLEN] ) { 0x00 }, INET_ADDRSTRLEN); if (!caerStrEquals(newConnectedClientsStr, "")) { // Only prepend comma if the string wasn't empty before. strncat(newConnectedClientsStr, ",", 1); } strncat(newConnectedClientsStr, clientAddrStr, INET_ADDRSTRLEN); sshsNodePutString(state->parentModule->moduleNode, "connectedClients", newConnectedClientsStr); free(newConnectedClientsStr); return; }
static int caerConfigServerRunner(void *inPtr) { UNUSED_ARGUMENT(inPtr); // Get the right configuration node first. sshsNode serverNode = sshsGetNode(sshsGetGlobal(), "/server/"); // Ensure default values are present. sshsNodePutStringIfAbsent(serverNode, "ipAddress", "127.0.0.1"); sshsNodePutIntIfAbsent(serverNode, "portNumber", 4040); sshsNodePutShortIfAbsent(serverNode, "backlogSize", 5); sshsNodePutShortIfAbsent(serverNode, "concurrentConnections", 5); // Open a TCP server socket for configuration handling. // TCP chosen for reliability, which is more important here than speed. int configServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (configServerSocket < 0) { caerLog(CAER_LOG_CRITICAL, "Config Server", "Could not create server socket. Error: %d.", errno); return (EXIT_FAILURE); } // Make socket address reusable right away. socketReuseAddr(configServerSocket, true); struct sockaddr_in configServerAddress; memset(&configServerAddress, 0, sizeof(struct sockaddr_in)); configServerAddress.sin_family = AF_INET; configServerAddress.sin_port = htons(sshsNodeGetInt(serverNode, "portNumber")); char *ipAddress = sshsNodeGetString(serverNode, "ipAddress"); inet_aton(ipAddress, &configServerAddress.sin_addr); // htonl() is implicit here. free(ipAddress); // Bind socket to above address. if (bind(configServerSocket, (struct sockaddr *) &configServerAddress, sizeof(struct sockaddr_in)) < 0) { caerLog(CAER_LOG_CRITICAL, "Config Server", "Could not bind server socket. Error: %d.", errno); return (EXIT_FAILURE); } // Listen to new connections on the socket. if (listen(configServerSocket, sshsNodeGetShort(serverNode, "backlogSize")) < 0) { caerLog(CAER_LOG_CRITICAL, "Config Server", "Could not listen on server socket. Error: %d.", errno); return (EXIT_FAILURE); } // Control message format: 1 byte ACTION, 1 byte TYPE, 2 bytes EXTRA_LEN, // 2 bytes NODE_LEN, 2 bytes KEY_LEN, 2 bytes VALUE_LEN, then up to 4086 // bytes split between EXTRA, NODE, KEY, VALUE (with 4 bytes for NUL). // Basically: (EXTRA_LEN + NODE_LEN + KEY_LEN + VALUE_LEN) <= 4086. // EXTRA, NODE, KEY, VALUE have to be NUL terminated, and their length // must include the NUL termination byte. // This results in a maximum message size of 4096 bytes (4KB). uint8_t configServerBuffer[4096]; caerLog(CAER_LOG_NOTICE, "Config Server", "Ready and listening on %s:%" PRIu16 ".", inet_ntoa(configServerAddress.sin_addr), ntohs(configServerAddress.sin_port)); size_t connections = (size_t) sshsNodeGetShort(serverNode, "concurrentConnections") + 1;// +1 for listening socket. struct pollfd pollSockets[connections]; // Initially ignore all pollfds, and react only to POLLIN events. for (size_t i = 0; i < connections; i++) { pollSockets[i].fd = -1; pollSockets[i].events = POLLIN; } // First pollfd is the listening socket, always. pollSockets[0].fd = configServerSocket; while (atomic_load_explicit(&configServerThread.running, memory_order_relaxed)) { int pollResult = poll(pollSockets, (nfds_t) connections, 1000); if (pollResult > 0) { // First let's check if there's a new connection waiting to be accepted. if ((pollSockets[0].revents & POLLIN) != 0) { int newClientSocket = accept(pollSockets[0].fd, NULL, NULL); if (newClientSocket >= 0) { // Put it in the list of watched fds if possible, or close. bool putInFDList = false; for (size_t i = 1; i < connections; i++) { if (pollSockets[i].fd == -1) { // Empty place in watch list, add this one. pollSockets[i].fd = newClientSocket; putInFDList = true; break; } } // No space for new connection, just close it (client will exit). if (!putInFDList) { close(newClientSocket); caerLog(CAER_LOG_DEBUG, "Config Server", "Rejected client (fd %d), queue full.", newClientSocket); } else { caerLog(CAER_LOG_DEBUG, "Config Server", "Accepted new connection from client (fd %d).", newClientSocket); } } } for (size_t i = 1; i < connections; i++) { // Work on existing connections, valid and with read events. if (pollSockets[i].fd != -1 && (pollSockets[i].revents & POLLIN) != 0) { // Accepted client connection, let's get all the data we need: first // the 10 byte header, and then whatever remains based on that. if (!recvUntilDone(pollSockets[i].fd, configServerBuffer, 10)) { // Closed on other side or error. Let's just close here too. close(pollSockets[i].fd); caerLog(CAER_LOG_DEBUG, "Config Server", "Disconnected client on recv (fd %d).", pollSockets[i].fd); pollSockets[i].fd = -1; continue; } // Decode header fields (all in little-endian). uint8_t action = configServerBuffer[0]; uint8_t type = configServerBuffer[1]; uint16_t extraLength = le16toh(*(uint16_t * )(configServerBuffer + 2)); uint16_t nodeLength = le16toh(*(uint16_t * )(configServerBuffer + 4)); uint16_t keyLength = le16toh(*(uint16_t * )(configServerBuffer + 6)); uint16_t valueLength = le16toh(*(uint16_t * )(configServerBuffer + 8)); // Total length to get for command. size_t readLength = (size_t) (extraLength + nodeLength + keyLength + valueLength); if (!recvUntilDone(pollSockets[i].fd, configServerBuffer + 10, readLength)) { // Closed on other side or error. Let's just close here too. close(pollSockets[i].fd); caerLog(CAER_LOG_DEBUG, "Config Server", "Disconnected client on recv (fd %d).", pollSockets[i].fd); pollSockets[i].fd = -1; continue; } // Now we have everything. The header fields are already // fully decoded: handle request (and send back data eventually). caerConfigServerHandleRequest(pollSockets[i].fd, action, type, configServerBuffer + 10, extraLength, configServerBuffer + 10 + extraLength, nodeLength, configServerBuffer + 10 + extraLength + nodeLength, keyLength, configServerBuffer + 10 + extraLength + nodeLength + keyLength, valueLength); } } } else if (pollResult < 0) { caerLog(CAER_LOG_ALERT, "Config Server", "poll() failed. Error: %d.", errno); } // pollResult == 0 just means nothing to do (timeout reached). } // Shutdown! close(configServerSocket); return (EXIT_SUCCESS); }
int main(int argc, char *argv[]) { // Install signal handler for global shutdown. struct sigaction shutdownAction; shutdownAction.sa_handler = &globalShutdownSignalHandler; shutdownAction.sa_flags = 0; sigemptyset(&shutdownAction.sa_mask); sigaddset(&shutdownAction.sa_mask, SIGTERM); sigaddset(&shutdownAction.sa_mask, SIGINT); if (sigaction(SIGTERM, &shutdownAction, NULL) == -1) { caerLog(CAER_LOG_CRITICAL, "ShutdownAction", "Failed to set signal handler for SIGTERM. Error: %d.", errno); return (EXIT_FAILURE); } if (sigaction(SIGINT, &shutdownAction, NULL) == -1) { caerLog(CAER_LOG_CRITICAL, "ShutdownAction", "Failed to set signal handler for SIGINT. Error: %d.", errno); return (EXIT_FAILURE); } // First of all, parse the IP:Port we need to listen on. // Those are for now also the only two parameters permitted. // If none passed, attempt to connect to default TCP IP:Port. const char *ipAddress = "127.0.0.1"; uint16_t portNumber = 7777; if (argc != 1 && argc != 3) { fprintf(stderr, "Incorrect argument number. Either pass none for default IP:Port" "combination of 127.0.0.1:7777, or pass the IP followed by the Port.\n"); return (EXIT_FAILURE); } // If explicitly passed, parse arguments. if (argc == 3) { ipAddress = argv[1]; sscanf(argv[2], "%" SCNu16, &portNumber); } // Create listening socket for TCP data. int listenTCPSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (listenTCPSocket < 0) { fprintf(stderr, "Failed to create TCP socket.\n"); return (EXIT_FAILURE); } struct sockaddr_in listenTCPAddress; memset(&listenTCPAddress, 0, sizeof(struct sockaddr_in)); listenTCPAddress.sin_family = AF_INET; listenTCPAddress.sin_port = htons(portNumber); inet_aton(ipAddress, &listenTCPAddress.sin_addr); // htonl() is implicit here. if (connect(listenTCPSocket, (struct sockaddr *) &listenTCPAddress, sizeof(struct sockaddr_in)) < 0) { fprintf(stderr, "Failed to connect to remote TCP data server.\n"); return (EXIT_FAILURE); } // 64K data buffer should be enough for the TCP event packets. size_t dataBufferLength = 1024 * 64; uint8_t *dataBuffer = malloc(dataBufferLength); while (!atomic_load_explicit(&globalShutdown, memory_order_relaxed)) { // Get packet header, to calculate packet size. if (!recvUntilDone(listenTCPSocket, dataBuffer, sizeof(struct caer_event_packet_header))) { fprintf(stderr, "Error in header recv() call: %d\n", errno); continue; } // Decode successfully received data. caerEventPacketHeader header = (caerEventPacketHeader) dataBuffer; int16_t eventType = caerEventPacketHeaderGetEventType(header); int16_t eventSource = caerEventPacketHeaderGetEventSource(header); int32_t eventSize = caerEventPacketHeaderGetEventSize(header); int32_t eventTSOffset = caerEventPacketHeaderGetEventTSOffset(header); int32_t eventCapacity = caerEventPacketHeaderGetEventCapacity(header); int32_t eventNumber = caerEventPacketHeaderGetEventNumber(header); int32_t eventValid = caerEventPacketHeaderGetEventValid(header); printf( "type = %" PRIi16 ", source = %" PRIi16 ", size = %" PRIi32 ", tsOffset = %" PRIi32 ", capacity = %" PRIi32 ", number = %" PRIi32 ", valid = %" PRIi32 ".\n", eventType, eventSource, eventSize, eventTSOffset, eventCapacity, eventNumber, eventValid); // Get rest of event packet, the part with the events themselves. if (!recvUntilDone(listenTCPSocket, dataBuffer + sizeof(struct caer_event_packet_header), (size_t) (eventCapacity * eventSize))) { fprintf(stderr, "Error in data recv() call: %d\n", errno); continue; } if (eventValid > 0) { void *firstEvent = caerGenericEventGetEvent(header, 0); void *lastEvent = caerGenericEventGetEvent(header, eventValid - 1); int32_t firstTS = caerGenericEventGetTimestamp(firstEvent, header); int32_t lastTS = caerGenericEventGetTimestamp(lastEvent, header); int32_t tsDifference = lastTS - firstTS; printf("Time difference in packet: %" PRIi32 " (first = %" PRIi32 ", last = %" PRIi32 ").\n", tsDifference, firstTS, lastTS); } printf("\n\n"); } // Close connection. close(listenTCPSocket); free(dataBuffer); return (EXIT_SUCCESS); }