static int packetsFirstTimestampThenTypeCmp(const void *a, const void *b) { const caerEventPacketHeader *aa = a; const caerEventPacketHeader *bb = b; // Sort first by timestamp of the first event. int32_t eventTimestampA = caerGenericEventGetTimestamp(caerGenericEventGetEvent(*aa, 0), *aa); int32_t eventTimestampB = caerGenericEventGetTimestamp(caerGenericEventGetEvent(*bb, 0), *bb); if (eventTimestampA < eventTimestampB) { return (-1); } else if (eventTimestampA > eventTimestampB) { return (1); } else { // If equal, further sort by type ID. int16_t eventTypeA = caerEventPacketHeaderGetEventType(*aa); int16_t eventTypeB = caerEventPacketHeaderGetEventType(*bb); if (eventTypeA < eventTypeB) { return (-1); } else if (eventTypeA > eventTypeB) { return (1); } else { return (0); } } }
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; }
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); }
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); }
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); }
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 DYNAPSE, give it a device ID of 1, and don't care about USB bus or SN restrictions. caerDeviceHandle dynapse_handle = caerDeviceOpen(1, CAER_DEVICE_DYNAPSE, 0, 0, NULL); if (dynapse_handle == NULL) { return (EXIT_FAILURE); } // Let's take a look at the information we have on the device. struct caer_dynapse_info dynapse_info = caerDynapseInfoGet(dynapse_handle); printf("%s --- ID: %d, Master: %d, Logic: %d.\n", dynapse_info.deviceString, dynapse_info.deviceID, dynapse_info.deviceIsMaster, dynapse_info.logicVersion); // Send the default configuration before using the device. // No configuration is sent automatically! caerDeviceSendDefaultConfig(dynapse_handle); // 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. // This automatically turns on the AER and CHIP state machines. caerDeviceDataStart(dynapse_handle, NULL, NULL, NULL, &usbShutdownHandler, NULL); // Let's turn on blocking data-get mode to avoid wasting resources. caerDeviceConfigSet(dynapse_handle, CAER_HOST_CONFIG_DATAEXCHANGE, CAER_HOST_CONFIG_DATAEXCHANGE_BLOCKING, true); while (!atomic_load_explicit(&globalShutdown, memory_order_relaxed)) { caerEventPacketContainer packetContainer = caerDeviceDataGet(dynapse_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)); // Spike Events if (i == SPIKE_EVENT) { caerSpikeEventPacket spike = (caerSpikeEventPacket) packetHeader; // Get full timestamp and addresses of first event. caerSpikeEventConst firstEvent = caerSpikeEventPacketGetEventConst(spike, 0); int32_t ts = caerSpikeEventGetTimestamp(firstEvent); uint16_t neuid = caerSpikeEventGetNeuronID(firstEvent); uint16_t coreid = caerSpikeEventGetSourceCoreID(firstEvent); printf("First spike event - ts: %d, neu: %d, core: %d\n", ts, neuid, coreid); } } caerEventPacketContainerFree(packetContainer); } caerDeviceDataStop(dynapse_handle); caerDeviceClose(&dynapse_handle); printf("Shutdown successful.\n"); return (EXIT_SUCCESS); }