# HG changeset patch # User Edouard Tisserant # Date 1731934360 -3600 # Node ID 9ff45581769170d8580d9f571df472a742253da7 # Parent 4b47b4ce0f12738a60545c955b6e1547b29ff434 IDE: refactor discovery panel / zeroconf Bundle ZeroConf listener and interfaces changes monitor into separate module. Prepare to extend with USB device browsing. diff -r 4b47b4ce0f12 -r 9ff455817691 connectors/ZeroConfListener.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/connectors/ZeroConfListener.py Mon Nov 18 13:52:40 2024 +0100 @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# See COPYING file for copyrights details. + +import weakref +from zeroconf import ServiceBrowser, Zeroconf, get_all_addresses +import threading + +service_type = '_Beremiz._tcp.local.' + +class ZeroConfListenerClass: + def __init__(self, dialog): + self.dialog = weakref.ref(dialog) + + self.IfacesMonitorState = None + self.IfacesMonitorTimer = None + self.Browser = None + self.ZeroConfInstance = None + self.PublishedServices = set() + + self.start() + + def __del__(self): + self.stop() + + def start(self): + self.ZeroConfInstance = Zeroconf() + self.Browser = ServiceBrowser(self.ZeroConfInstance, service_type, self) + # Start the ifaces_monitor timer thread + self.IfacesMonitorTimer = threading.Timer(1.0, self.ifaces_monitor) + self.IfacesMonitorTimer.start() + + def stop(self): + if self.IfacesMonitorTimer is not None: + self.IfacesMonitorTimer.cancel() + self.IfacesMonitorTimer = None + + if self.Browser is not None: + self.Browser.cancel() + self.Browser = None + + if self.ZeroConfInstance is not None: + self.ZeroConfInstance.close() + self.ZeroConfInstance = None + + def update_service(self, zeroconf, _type, name): + self.remove_service(zeroconf, _type, name) + self.add_service(zeroconf, _type, name) + + def add_service(self, zeroconf, _type, name): + dialog = self.dialog() + if not dialog: + return + + info = self.ZeroConfInstance.get_service_info(_type, name) + if info is None: + return + + typename = info.properties.get(b"protocol", None).decode() + ip = str(info.parsed_addresses()[0]) + port = info.port + dialog.addService(typename, ip, port, name) + self.PublishedServices.add(name) + + def remove_service(self, zeroconf, _type, name): + dialog = self.dialog() + if not dialog: + return + + if name in self.PublishedServices: + dialog.removeService(name) + self.PublishedServices.discard(name) + + def ifaces_monitor(self): + dialog = self.dialog() + if not dialog: + return + + NewState = get_all_addresses() + OldState = self.IfacesMonitorState + self.IfacesMonitorState = NewState + do_restart = False + + if OldState is not None: + # detect if a new address appeared + for addr in NewState: + if addr not in OldState: + do_restart = True + break + else: + OldState.remove(addr) + # detect if an address disappeared + if len(OldState) > 0: + do_restart = True + + + if do_restart: + self.stop() + + while self.PublishedServices: + dialog.removeService(self.PublishedServices.pop()) + + self.start() + else: + # Restart the ifaces_monitor timer thread + self.IfacesMonitorTimer = threading.Timer(1.0, self.ifaces_monitor) + self.IfacesMonitorTimer.start() diff -r 4b47b4ce0f12 -r 9ff455817691 controls/DiscoveryPanel.py --- a/controls/DiscoveryPanel.py Mon Nov 18 13:37:08 2024 +0100 +++ b/controls/DiscoveryPanel.py Mon Nov 18 13:52:40 2024 +0100 @@ -24,57 +24,9 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -import socket -import weakref import wx import wx.lib.mixins.listctrl as listmix -from zeroconf import ServiceBrowser, Zeroconf, get_all_addresses - -service_type = '_Beremiz._tcp.local.' - -class ZeroConfListenerClass: - def __init__(self, dialog): - self.dialog = weakref.ref(dialog) - self.ZeroConfInstance = Zeroconf() - self.Browser = ServiceBrowser(self.ZeroConfInstance, service_type, self) - - def clean(self): - if self.Browser is not None: - self.Browser.cancel() - self.Browser = None - if self.ZeroConfInstance is not None: - self.ZeroConfInstance.close() - self.ZeroConfInstance = None - - def __del__(self): - self.clean() - - def update_service(self, zeroconf, _type, name): - self.remove_service(zeroconf, _type, name) - self.add_service(zeroconf, _type, name) - - def add_service(self, zeroconf, _type, name): - info = self.ZeroConfInstance.get_service_info(_type, name) - if info is None: - return - typename = info.properties.get(b"protocol", None).decode() - ip = str(info.parsed_addresses()[0]) - port = info.port - - wx.CallAfter(self._add_service, typename, ip, port, name) - - def _add_service(self, typename, ip, port, name): - dialog = self.dialog() - if not dialog: return - dialog._addService(typename, ip, port, name) - - def remove_service(self, zeroconf, _type, name): - wx.CallAfter(self._remove_service, name) - - def _remove_service(self, name): - dialog = self.dialog() - if not dialog: return - dialog._removeService(name) +from connectors.ZeroConfListener import ZeroConfListenerClass class AutoWidthListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin): def __init__(self, parent, name, pos=wx.DefaultPosition, @@ -82,7 +34,6 @@ wx.ListCtrl.__init__(self, parent, wx.ID_ANY, pos, size, style, name=name) listmix.ListCtrlAutoWidthMixin.__init__(self) - class DiscoveryPanel(wx.Panel, listmix.ColumnSorterMixin): def _init_coll_MainSizer_Items(self, parent): @@ -118,15 +69,11 @@ self.ServicesList = AutoWidthListCtrl( name='ServicesList', parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 0), style=wx.LC_REPORT | wx.LC_EDIT_LABELS | wx.LC_SORT_ASCENDING | wx.LC_SINGLE_SEL) - self.ServicesList.InsertColumn(0, _('NAME')) - self.ServicesList.InsertColumn(1, _('TYPE')) - self.ServicesList.InsertColumn(2, _('IP')) - self.ServicesList.InsertColumn(3, _('PORT')) - self.ServicesList.SetColumnWidth(0, 150) - self.ServicesList.SetColumnWidth(1, 150) - self.ServicesList.SetColumnWidth(2, 150) - self.ServicesList.SetColumnWidth(3, 150) + for col, (label, width) in enumerate([ + (_('NAME'), 150), (_('TYPE'), 150), (_('IP'), 150), (_('PORT'), 150)]): + self.ServicesList.InsertColumn(col, label) self.ServicesList.SetInitialSize(wx.Size(-1, 300)) + self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.ServicesList) self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated, self.ServicesList) @@ -156,46 +103,27 @@ self._init_ctrls(parent) + self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy) + self.itemDataMap = {} self.nextItemId = 0 self.URI = None + self.LatestSelection = None + + self.ZeroConfListener = None + self.RefreshList() - self.LatestSelection = None - - self.IfacesMonitorState = None - self.IfacesMonitorTimer = wx.Timer(self) - self.IfacesMonitorTimer.Start(2000) - self.ZeroConfListener = None - self.Bind(wx.EVT_TIMER, self.IfacesMonitor, self.IfacesMonitorTimer) + def _cleanup(self): - if self.IfacesMonitorTimer is not None: - self.IfacesMonitorTimer.Stop() - self.IfacesMonitorTimer = None if self.ZeroConfListener is not None: - self.ZeroConfListener.clean() + self.ZeroConfListener.stop() self.ZeroConfListener = None - def __del__(self): + def OnDestroy(self, event): self._cleanup() - - def Destroy(self): - self._cleanup() - wx.Panel.Destroy(self) - - def IfacesMonitor(self, event): - NewState = get_all_addresses() - - if self.IfacesMonitorState != NewState: - if self.IfacesMonitorState is not None: - # refresh only if a new address appeared - for addr in NewState: - if addr not in self.IfacesMonitorState: - self.RefreshList() - break - self.IfacesMonitorState = NewState event.Skip() def RefreshList(self): @@ -246,6 +174,9 @@ # return str("MDNS://%s" % svcname) return None + def removeService(self, name): + wx.CallAfter(self._removeService, name) + def _removeService(self, name): ''' called when a service with the desired type goes offline. @@ -263,6 +194,8 @@ self.ServicesList.DeleteItem(idx) break + def addService(self, typename, ip, port, name): + wx.CallAfter(self._addService, typename, ip, port, name) def _addService(self, typename, ip, port, name): '''