class NodeHandler(QObject): newNodeDiscovered = Signal(dict) nodeConnected = Signal(dict) nodeDisconnected = Signal(dict) nodeDisappeared = Signal(dict) nodeAliveMessage = Signal(dict) cleanupDone = Signal() threadReady = Signal() def __init__(self, mainwindow): super(NodeHandler, self).__init__() self.visibleNodes = {} self.connectedNodes = {} self.nodeListMutex = QMutex() self.mainwindow = mainwindow @Slot() def onRun(self): # multithreading hack to prevent threading sigsev conditions # all the stuff should be executed in this threads event loop # if we do stuff here, the thread context could be different self.threadReady.connect(self.onThreadReady) self.threadReady.emit() @Slot() def onThreadReady(self): self.udpSocket = QUdpSocket(self) #self.udpSocket.bind(13370, QUdpSocket.ShareAddress) self.udpSocket.bind(13370, QUdpSocket.ReuseAddressHint) self.udpSocket.readyRead.connect(self.onSocketReadyRead) # check every second self.disconnectTimer = QTimer(self) self.disconnectTimer.moveToThread(self.thread()) self.disconnectTimer.timeout.connect(self.onDisconnectTimerFire) self.disconnectTimer.start(100) @Slot() def onSocketReadyRead(self): msg = self.udpSocket.readDatagram(self.udpSocket.pendingDatagramSize()) if msg[0][0:2] == "CB": msg_split = msg[0].split('|') device_id = msg_split[1].data().decode('ascii') device_version = msg_split[2].data().decode('ascii') now = datetime.datetime.now() ip = QHostAddress(msg[1].toIPv4Address()).toString() device = {"id": device_id, "version": device_version, "ip": ip, "last_seen": now} self.nodeListMutex.lock() if (device_id not in self.connectedNodes.keys()) and \ (device_id not in self.visibleNodes.keys()): self.visibleNodes[device_id] = device self.newNodeDiscovered.emit(device) print(f"discovered {device_id}") # update timestamps for known visible/connected devices if device_id in self.visibleNodes.keys(): self.visibleNodes[device_id]["last_seen"] = now self.nodeAliveMessage.emit(self.visibleNodes[device_id]) if device_id in self.connectedNodes.keys(): self.connectedNodes[device_id]["last_seen"] = now self.nodeAliveMessage.emit(self.connectedNodes[device_id]) self.nodeListMutex.unlock() @Slot(str) def onSocketCanDiscovered(self, vcan): now = datetime.datetime.now() device = {"id": vcan, "version": "socket_can", "ip": None, "last_seen": now} self.nodeListMutex.lock() if (vcan not in self.connectedNodes.keys()) and \ (vcan not in self.visibleNodes.keys()): self.visibleNodes[vcan] = device self.newNodeDiscovered.emit(device) if vcan in self.visibleNodes.keys(): self.visibleNodes[vcan]["last_seen"] = now self.nodeAliveMessage.emit(self.visibleNodes[vcan]) if vcan in self.connectedNodes.keys(): self.connectedNodes[vcan]["last_seen"] = now self.nodeAliveMessage.emit(self.connectedNodes[vcan]) self.nodeListMutex.unlock() @Slot() def onDisconnectTimerFire(self): now = datetime.datetime.now() self.nodeListMutex.lock() ids_to_delete = [] for id, node in self.visibleNodes.items(): # check time difference if (now - node["last_seen"]) > datetime.timedelta(seconds=6): ids_to_delete.append(id) self.nodeDisappeared.emit(node) for id in ids_to_delete: del self.visibleNodes[id] ids_to_delete = [] for id, node in self.connectedNodes.items(): # check time difference if (now - node["last_seen"]) > datetime.timedelta(seconds=10): ids_to_delete.append(id) self.nodeDisconnected.emit(node) for id in ids_to_delete: # check for running connection process if self.connectedNodes[id]["connection"].isConnected: self.connectedNodes[id]["connection"].resetConnection() del self.connectedNodes[id] self.nodeListMutex.unlock() @Slot(dict) def onConnectToNode(self, node): print(f"Node handler creating connection to {node['id']}.") if ("connection" not in node.keys() or node["connection"] is None) and node["ip"] is not None: # start a connection to a canbadger con = NodeConnection(node) node["connection"] = con con.connectionSucceeded.connect(self.onConnectionSucceeded) con.connectionFailed.connect(self.onConnectionFailed) con.newSettingsData.connect(self.mainwindow.onSettingsReceived) self.mainwindow.exiting.connect(con.resetConnection) con.onRun() elif ("connection" not in node.keys() or node["connection"] is None) and node["ip"] is None: # start a socketCan connection con = SocketCanConnection(node) node["connection"] = con con.connectionSucceeded.connect(self.onConnectionSucceeded) con.connectionFailed.connect(self.onConnectionFailed) self.mainwindow.exiting.connect(con.cleanup) con.onRun() @Slot() def onConnectionSucceeded(self): node_connection = self.sender() node = node_connection.node self.nodeListMutex.lock() if node["id"] in self.visibleNodes.keys(): del self.visibleNodes[node["id"]] self.connectedNodes[node["id"]] = node self.nodeListMutex.unlock() self.nodeConnected.emit(node) node_connection.nodeDisconnected.connect(self.onDisconnectNode) @Slot() def onConnectionFailed(self): # signal red buttonFeedback(False, self.mainwindow.interfaceSelection) @Slot(dict) def onDisconnectNode(self, node): # reset connection and terminate thread if "connection" in node.keys(): node["connection"].resetConnection() if "thread" in node.keys(): node["thread"].exit() pass # delete the node from dicts self.nodeListMutex.lock() if node["id"] in self.connectedNodes: del self.connectedNodes[node["id"]] if node["id"] in self.visibleNodes: del self.visibleNodes[node["id"]] self.nodeListMutex.unlock() self.nodeDisconnected.emit(node) @Slot(str, str) def onIDChange(self, old, new): self.nodeListMutex.lock() if old in self.connectedNodes: self.connectedNodes[new] = self.connectedNodes[old] del self.connectedNodes[old] if old in self.visibleNodes: self.visibleNodes[new] = self.visibleNodes[old] del self.visibleNodes[old] self.nodeListMutex.unlock() @Slot() def onExiting(self): self.disconnectTimer.stop() self.udpSocket.close() self.cleanupDone.emit()