static void caerVisualizerEventHandlerInput(caerVisualizerPublicState state, const sf::Event &event) { // This only works with an input module. const std::string moduleLibrary = sshsNodeGetStdString(state->eventSourceConfigNode, "moduleLibrary"); if (!boost::algorithm::starts_with(moduleLibrary, "caer_input_")) { return; } // PAUSE. if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Key::Space) { bool pause = sshsNodeGetBool(state->eventSourceConfigNode, "pause"); sshsNodePutBool(state->eventSourceConfigNode, "pause", !pause); } // SLOW DOWN. else if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Key::S) { int timeSlice = sshsNodeGetInt(state->eventSourceConfigNode, "PacketContainerInterval"); sshsNodePutInt(state->eventSourceConfigNode, "PacketContainerInterval", timeSlice / 2); } // SPEED UP. else if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Key::F) { int timeSlice = sshsNodeGetInt(state->eventSourceConfigNode, "PacketContainerInterval"); sshsNodePutInt(state->eventSourceConfigNode, "PacketContainerInterval", timeSlice * 2); } }
static void systemConfigSend(sshsNode node, caerModuleData moduleData) { caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_PACKETS, CAER_HOST_CONFIG_PACKETS_MAX_CONTAINER_PACKET_SIZE, U32T(sshsNodeGetInt(node, "PacketContainerMaxPacketSize"))); caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_PACKETS, CAER_HOST_CONFIG_PACKETS_MAX_CONTAINER_INTERVAL, U32T(sshsNodeGetInt(node, "PacketContainerInterval"))); // Changes only take effect on module start! caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_DATAEXCHANGE, CAER_HOST_CONFIG_DATAEXCHANGE_BUFFER_SIZE, U32T(sshsNodeGetInt(node, "DataExchangeBufferSize"))); }
static void usbConfigSend(sshsNode node, caerModuleData moduleData) { caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_USB, CAER_HOST_CONFIG_USB_BUFFER_NUMBER, U32T(sshsNodeGetInt(node, "BufferNumber"))); caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_USB, CAER_HOST_CONFIG_USB_BUFFER_SIZE, U32T(sshsNodeGetInt(node, "BufferSize"))); caerDeviceConfigSet(moduleData->moduleState, DAVIS_CONFIG_USB, DAVIS_CONFIG_USB_EARLY_PACKET_DELAY, U32T(sshsNodeGetInt(node, "EarlyPacketDelay"))); caerDeviceConfigSet(moduleData->moduleState, DAVIS_CONFIG_USB, DAVIS_CONFIG_USB_RUN, sshsNodeGetBool(node, "Run")); }
static bool newOutputBuffer(outputCommonState state) { // First check if the size really changed. size_t newBufferSize = (size_t) sshsNodeGetInt(state->parentModule->moduleNode, "bufferSize"); if (state->dataBuffer != NULL && state->dataBuffer->bufferSize == newBufferSize) { // Yeah, we're already where we want to be! return (true); } // Allocate new buffer. simpleBuffer newBuffer = simpleBufferInit(newBufferSize); if (newBuffer == NULL) { return (false); } // Commit previous buffer content and then free the memory. if (state->dataBuffer != NULL) { commitOutputBuffer(state); free(state->dataBuffer); } // Use new buffer. state->dataBuffer = newBuffer; return (true); }
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); }
static bool caerOutputUnixSocketServerInit(caerModuleData moduleData) { // First, always create all needed setting nodes, set their default values // and add their listeners. sshsNodeCreateString(moduleData->moduleNode, "socketPath", "/tmp/caer.sock", 2, PATH_MAX, SSHS_FLAGS_NORMAL, "Unix Socket path for writing output data (server mode, create new socket)."); sshsNodeCreateInt( moduleData->moduleNode, "backlogSize", 5, 1, 32, SSHS_FLAGS_NORMAL, "Maximum number of pending connections."); sshsNodeCreateInt(moduleData->moduleNode, "concurrentConnections", 10, 1, 128, SSHS_FLAGS_NORMAL, "Maximum number of concurrent active connections."); // Allocate memory. size_t numClients = (size_t) sshsNodeGetInt(moduleData->moduleNode, "concurrentConnections"); outputCommonNetIO streams = malloc(sizeof(*streams) + (numClients * sizeof(uv_stream_t *))); if (streams == NULL) { caerModuleLog(moduleData, CAER_LOG_ERROR, "Failed to allocate memory for streams structure."); return (false); } streams->server = malloc(sizeof(uv_pipe_t)); if (streams->server == NULL) { free(streams); caerModuleLog(moduleData, CAER_LOG_ERROR, "Failed to allocate memory for network server."); return (false); } // Initialize common info. streams->isTCP = false; streams->isUDP = false; streams->isPipe = true; streams->activeClients = 0; streams->clientsSize = numClients; for (size_t i = 0; i < streams->clientsSize; i++) { streams->clients[i] = NULL; } // Remember address. streams->address = sshsNodeGetString(moduleData->moduleNode, "socketPath"); streams->server->data = streams; // Initialize loop and network handles. int retVal = uv_loop_init(&streams->loop); UV_RET_CHECK(retVal, moduleData->moduleSubSystemString, "uv_loop_init", free(streams->server); free(streams->address); free(streams); return (false)); retVal = uv_pipe_init(&streams->loop, (uv_pipe_t *) streams->server, false); UV_RET_CHECK(retVal, moduleData->moduleSubSystemString, "uv_pipe_init", uv_loop_close(&streams->loop); free(streams->server); free(streams->address); free(streams); return (false)); retVal = uv_pipe_bind((uv_pipe_t *) streams->server, streams->address); UV_RET_CHECK(retVal, moduleData->moduleSubSystemString, "uv_pipe_bind", libuvCloseLoopHandles(&streams->loop); uv_loop_close(&streams->loop); free(streams->address); free(streams); return (false)); retVal = uv_listen( streams->server, sshsNodeGetInt(moduleData->moduleNode, "backlogSize"), &caerOutputCommonOnServerConnection); UV_RET_CHECK(retVal, moduleData->moduleSubSystemString, "uv_listen", libuvCloseLoopHandles(&streams->loop); uv_loop_close(&streams->loop); free(streams->address); free(streams); return (false)); // Start. if (!caerOutputCommonInit(moduleData, -1, streams)) { libuvCloseLoopHandles(&streams->loop); uv_loop_close(&streams->loop); free(streams->address); free(streams); return (false); } return (true); }
static void usbConfigSend(sshsNode node, caerModuleData moduleData) { caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_USB, CAER_HOST_CONFIG_USB_BUFFER_NUMBER, U32T(sshsNodeGetInt(node, "BufferNumber"))); caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_USB, CAER_HOST_CONFIG_USB_BUFFER_SIZE, U32T(sshsNodeGetInt(node, "BufferSize"))); }
static void biasConfigSend(sshsNode node, caerModuleData moduleData) { caerDeviceConfigSet(moduleData->moduleState, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_CAS, U32T(sshsNodeGetInt(node, "cas"))); caerDeviceConfigSet(moduleData->moduleState, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_INJGND, U32T(sshsNodeGetInt(node, "injGnd"))); caerDeviceConfigSet(moduleData->moduleState, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_REQPD, U32T(sshsNodeGetInt(node, "reqPd"))); caerDeviceConfigSet(moduleData->moduleState, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_PUX, U32T(sshsNodeGetInt(node, "puX"))); caerDeviceConfigSet(moduleData->moduleState, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_DIFFOFF, U32T(sshsNodeGetInt(node, "diffOff"))); caerDeviceConfigSet(moduleData->moduleState, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_REQ, U32T(sshsNodeGetInt(node, "req"))); caerDeviceConfigSet(moduleData->moduleState, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_REFR, U32T(sshsNodeGetInt(node, "refr"))); caerDeviceConfigSet(moduleData->moduleState, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_PUY, U32T(sshsNodeGetInt(node, "puY"))); caerDeviceConfigSet(moduleData->moduleState, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_DIFFON, U32T(sshsNodeGetInt(node, "diffOn"))); caerDeviceConfigSet(moduleData->moduleState, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_DIFF, U32T(sshsNodeGetInt(node, "diff"))); caerDeviceConfigSet(moduleData->moduleState, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_FOLL, U32T(sshsNodeGetInt(node, "foll"))); caerDeviceConfigSet(moduleData->moduleState, DVS128_CONFIG_BIAS, DVS128_CONFIG_BIAS_PR, U32T(sshsNodeGetInt(node, "pr"))); }
static void serialConfigSend(sshsNode node, caerModuleData moduleData) { caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_SERIAL, CAER_HOST_CONFIG_SERIAL_READ_SIZE, U32T(sshsNodeGetInt(node, "ReadSize"))); }
static bool caerInputEDVSInit(caerModuleData moduleData) { caerModuleLog(moduleData, CAER_LOG_DEBUG, "Initializing module ..."); // Start data acquisition, and correctly notify mainloop of new data and module of exceptional // shutdown cases (device pulled, ...). char *serialPortName = sshsNodeGetString(moduleData->moduleNode, "serialPort"); moduleData->moduleState = caerDeviceOpenSerial(U16T(moduleData->moduleID), CAER_DEVICE_EDVS, serialPortName, U32T(sshsNodeGetInt(moduleData->moduleNode, "baudRate"))); free(serialPortName); if (moduleData->moduleState == NULL) { // Failed to open device. return (false); } // Initialize per-device log-level to module log-level. caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_LOG, CAER_HOST_CONFIG_LOG_LEVEL, atomic_load(&moduleData->moduleLogLevel)); // Put global source information into SSHS. struct caer_edvs_info devInfo = caerEDVSInfoGet(moduleData->moduleState); sshsNode sourceInfoNode = sshsGetRelativeNode(moduleData->moduleNode, "sourceInfo/"); sshsNodeCreateBool(sourceInfoNode, "deviceIsMaster", devInfo.deviceIsMaster, SSHS_FLAGS_READ_ONLY | SSHS_FLAGS_NO_EXPORT, "Timestamp synchronization support: device master status."); sshsNodeCreateInt(sourceInfoNode, "polaritySizeX", devInfo.dvsSizeX, devInfo.dvsSizeX, devInfo.dvsSizeX, SSHS_FLAGS_READ_ONLY | SSHS_FLAGS_NO_EXPORT, "Polarity events width."); sshsNodeCreateInt(sourceInfoNode, "polaritySizeY", devInfo.dvsSizeY, devInfo.dvsSizeY, devInfo.dvsSizeY, SSHS_FLAGS_READ_ONLY | SSHS_FLAGS_NO_EXPORT, "Polarity events height."); // Put source information for generic visualization, to be used to display and debug filter information. sshsNodeCreateInt(sourceInfoNode, "dataSizeX", devInfo.dvsSizeX, devInfo.dvsSizeX, devInfo.dvsSizeX, SSHS_FLAGS_READ_ONLY | SSHS_FLAGS_NO_EXPORT, "Data width."); sshsNodeCreateInt(sourceInfoNode, "dataSizeY", devInfo.dvsSizeY, devInfo.dvsSizeY, devInfo.dvsSizeY, SSHS_FLAGS_READ_ONLY | SSHS_FLAGS_NO_EXPORT, "Data height."); // Generate source string for output modules. size_t sourceStringLength = (size_t) snprintf(NULL, 0, "#Source %" PRIu16 ": eDVS4337\r\n", moduleData->moduleID); char sourceString[sourceStringLength + 1]; snprintf(sourceString, sourceStringLength + 1, "#Source %" PRIu16 ": eDVS4337\r\n", moduleData->moduleID); sourceString[sourceStringLength] = '\0'; sshsNodeCreateString(sourceInfoNode, "sourceString", sourceString, sourceStringLength, sourceStringLength, SSHS_FLAGS_READ_ONLY | SSHS_FLAGS_NO_EXPORT, "Device source information."); // 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. sendDefaultConfiguration(moduleData); // Start data acquisition. bool ret = caerDeviceDataStart(moduleData->moduleState, &caerMainloopDataNotifyIncrease, &caerMainloopDataNotifyDecrease, NULL, &moduleShutdownNotify, moduleData->moduleNode); if (!ret) { // Failed to start data acquisition, close device and exit. caerDeviceClose((caerDeviceHandle *) &moduleData->moduleState); return (false); } // Add config listeners last, to avoid having them dangling if Init doesn't succeed. sshsNode biasNode = sshsGetRelativeNode(moduleData->moduleNode, "bias/"); sshsNodeAddAttributeListener(biasNode, moduleData, &biasConfigListener); sshsNode dvsNode = sshsGetRelativeNode(moduleData->moduleNode, "dvs/"); sshsNodeAddAttributeListener(dvsNode, moduleData, &dvsConfigListener); sshsNode serialNode = sshsGetRelativeNode(moduleData->moduleNode, "serial/"); sshsNodeAddAttributeListener(serialNode, moduleData, &serialConfigListener); sshsNode sysNode = sshsGetRelativeNode(moduleData->moduleNode, "system/"); sshsNodeAddAttributeListener(sysNode, moduleData, &systemConfigListener); sshsNodeAddAttributeListener(moduleData->moduleNode, moduleData, &logLevelListener); return (true); }
static bool caerInputDAVISInit(caerModuleData moduleData) { caerModuleLog(moduleData, CAER_LOG_DEBUG, "Initializing module ..."); // 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(U16T(moduleData->moduleID), CAER_DEVICE_DAVIS, U8T(sshsNodeGetInt(moduleData->moduleNode, "busNumber")), U8T(sshsNodeGetInt(moduleData->moduleNode, "devAddress")), serialNumber); free(serialNumber); if (moduleData->moduleState == NULL) { // Failed to open device. return (false); } struct caer_davis_info devInfo = caerDavisInfoGet(moduleData->moduleState); caerInputDAVISCommonInit(moduleData, &devInfo); // Generate sub-system string for module. char *prevAdditionStart = strchr(moduleData->moduleSubSystemString, '['); if (prevAdditionStart != NULL) { *prevAdditionStart = '\0'; } size_t subSystemStringLength = (size_t) snprintf(NULL, 0, "%s[SN %s, %" PRIu8 ":%" PRIu8 "]", moduleData->moduleSubSystemString, devInfo.deviceSerialNumber, devInfo.deviceUSBBusNumber, devInfo.deviceUSBDeviceAddress); char subSystemString[subSystemStringLength + 1]; snprintf(subSystemString, subSystemStringLength + 1, "%s[SN %s, %" PRIu8 ":%" PRIu8 "]", moduleData->moduleSubSystemString, devInfo.deviceSerialNumber, devInfo.deviceUSBBusNumber, devInfo.deviceUSBDeviceAddress); subSystemString[subSystemStringLength] = '\0'; caerModuleSetSubSystemString(moduleData, subSystemString); // Create default settings and send them to the device. createDefaultBiasConfiguration(moduleData, chipIDToName(devInfo.chipID, true), devInfo.chipID); createDefaultLogicConfiguration(moduleData, chipIDToName(devInfo.chipID, true), &devInfo); createDefaultUSBConfiguration(moduleData, chipIDToName(devInfo.chipID, true)); sendDefaultConfiguration(moduleData, &devInfo); // Start data acquisition. bool ret = caerDeviceDataStart(moduleData->moduleState, &caerMainloopDataNotifyIncrease, &caerMainloopDataNotifyDecrease, NULL, &moduleShutdownNotify, moduleData->moduleNode); if (!ret) { // Failed to start data acquisition, close device and exit. caerDeviceClose((caerDeviceHandle *) &moduleData->moduleState); return (false); } // Device related configuration has its own sub-node. sshsNode deviceConfigNode = sshsGetRelativeNode(moduleData->moduleNode, chipIDToName(devInfo.chipID, true)); // Add config listeners last, to avoid having them dangling if Init doesn't succeed. sshsNode chipNode = sshsGetRelativeNode(deviceConfigNode, "chip/"); sshsNodeAddAttributeListener(chipNode, moduleData, &chipConfigListener); sshsNode muxNode = sshsGetRelativeNode(deviceConfigNode, "multiplexer/"); sshsNodeAddAttributeListener(muxNode, moduleData, &muxConfigListener); sshsNode dvsNode = sshsGetRelativeNode(deviceConfigNode, "dvs/"); sshsNodeAddAttributeListener(dvsNode, moduleData, &dvsConfigListener); sshsNode apsNode = sshsGetRelativeNode(deviceConfigNode, "aps/"); sshsNodeAddAttributeListener(apsNode, moduleData, &apsConfigListener); sshsNode imuNode = sshsGetRelativeNode(deviceConfigNode, "imu/"); sshsNodeAddAttributeListener(imuNode, moduleData, &imuConfigListener); sshsNode extNode = sshsGetRelativeNode(deviceConfigNode, "externalInput/"); sshsNodeAddAttributeListener(extNode, moduleData, &extInputConfigListener); sshsNode usbNode = sshsGetRelativeNode(deviceConfigNode, "usb/"); sshsNodeAddAttributeListener(usbNode, moduleData, &usbConfigListener); sshsNode sysNode = sshsGetRelativeNode(moduleData->moduleNode, "system/"); sshsNodeAddAttributeListener(sysNode, moduleData, &systemConfigListener); sshsNode biasNode = sshsGetRelativeNode(deviceConfigNode, "bias/"); size_t biasNodesLength = 0; sshsNode *biasNodes = sshsNodeGetChildren(biasNode, &biasNodesLength); if (biasNodes != NULL) { for (size_t i = 0; i < biasNodesLength; i++) { // Add listener for this particular bias. sshsNodeAddAttributeListener(biasNodes[i], moduleData, &biasConfigListener); } free(biasNodes); } sshsNodeAddAttributeListener(moduleData->moduleNode, moduleData, &logLevelListener); return (true); }