Use plugintimer, fix to random album, zeroconf to module

master
loosrob 2021-08-26 14:44:38 +02:00
parent 4cd11e2378
commit 6acc037030
2 changed files with 102 additions and 89 deletions

View File

@ -5,50 +5,10 @@ import json
import requests import requests
import random import random
from xml.sax.saxutils import unescape from xml.sax.saxutils import unescape
from zeroconf import IPVersion, ServiceBrowser, ServiceInfo, Zeroconf from zeroconfbrowser import ZeroconfDevice, ZeroconfListener, discover
from typing import Callable, List
class ZeroconfDevice(object):
# To do: replace with nymea serviceBrowser
def __init__(self, name: str, ip: str, port: int, model: str, id: str) -> None:
self.name = name
self.ip = ip
self.port = port
self.model = model
self.id = id
def __repr__(self) -> str:
return f"{type(self).__name__}({self.__dict__})"
def __eq__(self, other) -> bool:
return self is other or self.__dict__ == other.__dict__
class ZeroconfListener(object):
# To do: replace with nymea serviceBrowser
"""Basic zeroconf listener."""
def __init__(self, func: Callable[[ServiceInfo], None]) -> None:
"""Initialize zeroconf listener with function callback."""
self._func = func
def __repr__(self) -> str:
return f"{type(self).__name__}({self.__dict__})"
def __eq__(self, other) -> bool:
return self is other or self.__dict__ == other.__dict__
def add_service(self, zeroconf: Zeroconf, type: str, name: str) -> None:
"""Callback function when zeroconf service is discovered."""
self._func(zeroconf.get_service_info(type, name))
def update_service(self, zeroconf: Zeroconf, type: str, name: str) -> None:
return
def remove_service(self, zeroconf: Zeroconf, type: str, name: str) -> None:
return
pollTimer = None pollTimer = None
pollFrequency = 40 pollFrequency = 30
def discoverThings(info): def discoverThings(info):
if info.thingClassId == receiverThingClassId: if info.thingClassId == receiverThingClassId:
@ -202,38 +162,6 @@ def findIps():
discoveredIps.append(deviceInfo.ip) discoveredIps.append(deviceInfo.ip)
return discoveredIps return discoveredIps
def discover(service_type: str, timeout: int = 5) -> List[ZeroconfDevice]:
# To do: replace with nymea serviceBrowser
"""From pyvizio: Return all discovered zeroconf services of a given service type over given timeout period."""
services = []
def append_service(info: ServiceInfo) -> None:
"""Append discovered zeroconf service to service list."""
name = info.name[: -(len(info.type) + 1)]
ip = info.parsed_addresses(IPVersion.V4Only)[0]
port = info.port
model = info.properties.get(b"name", "")
id = info.properties.get(b"id")
# handle id decode for various discovered use cases
if isinstance(id, bytes):
try:
int(id, 16)
except Exception:
id = id.hex()
else:
id = None
service = ZeroconfDevice(name, ip, port, model, id)
services.append(service)
zeroconf = Zeroconf()
ServiceBrowser(zeroconf, service_type, ZeroconfListener(append_service))
time.sleep(timeout)
zeroconf.close()
return services
def setupThing(info): def setupThing(info):
if info.thing.thingClassId == receiverThingClassId: if info.thing.thingClassId == receiverThingClassId:
searchSystemId = info.thing.paramValue(receiverThingSerialParamTypeId) searchSystemId = info.thing.paramValue(receiverThingSerialParamTypeId)
@ -278,10 +206,11 @@ def setupThing(info):
# If no poll timer is set up yet, start it now # If no poll timer is set up yet, start it now
logger.log("Creating pollService") logger.log("Creating pollService")
global pollTimer global pollTimer
global pollFrequency
if pollTimer == None: if pollTimer == None:
logger.log("Starting timer @ setupThing") logger.log("Starting timer @ setupThing")
pollTimer = threading.Timer(30, pollService) pollTimer = nymea.PluginTimer(pollFrequency, pollService)
pollTimer.start() logger.log("timer interval @ setupThing", pollTimer.interval)
else: else:
logger.log("Timer already exists @ setupThing") logger.log("Timer already exists @ setupThing")
@ -638,13 +567,12 @@ def pollReceiver(info):
zone.setStateValue(zoneConnectedStateTypeId, False) zone.setStateValue(zoneConnectedStateTypeId, False)
def pollService(): def pollService():
logger.log("pollService!!!") logger.log("pollTimer triggered")
global pollTimer global pollTimer
global pollFrequency global pollFrequency
# restart the timer for next poll logger.log("adjusting timer interval")
logger.log("Restarting timer @ pollService, with frequency", pollFrequency) pollTimer.interval = pollFrequency
pollTimer = threading.Timer(pollFrequency, pollService) logger.log("timer interval @ pollService", pollTimer.interval)
pollTimer.start()
pollFrequency = 30 pollFrequency = 30
# Poll all receivers we know # Poll all receivers we know
for thing in myThings(): for thing in myThings():
@ -1016,10 +944,21 @@ def playRandomAlbum(rUrl, source):
logger.log("Source not supported for this action") logger.log("Source not supported for this action")
# navigate browseTree (first item select random server, then folder "Music", ...) # navigate browseTree (first item select random server, then folder "Music", ...)
menuLayer = browseInTree(rUrl, source, browseTree) menuLayer = browseInTree(rUrl, source, browseTree)
# play album by selecting first line --> what if first line is not selectable? filter out non-selectable lines first?
if menuLayer == len(browseTree)+1 and menuLayer > 0: # don't do anything unless browsing to the required menu item succeeded:
# don't do anything unless browsing to the required menu item succeeded if menuLayer == len(browseTree)+1 and menuLayer > 0:
selectLine(rUrl, source, 1) # play album by selecting first "selectable" line (attribute = "Item")
browseResponse, menuLayer = browseMenuReady(rUrl, source)
selectable = False
line = 1
while selectable == False:
itemTxt, itemAttr = readLine(browseResponse, line)
if itemAttr == "Item":
selectable = True
else:
line += 1
logger.log("Selecting line %s with label %s" % (line, itemTxt))
selectLine(rUrl, source, line)
return return
def browseInTree(rUrl, source, browseTree): def browseInTree(rUrl, source, browseTree):
@ -1037,7 +976,6 @@ def browseInTree(rUrl, source, browseTree):
# navigate browseTree # navigate browseTree
for i in range (0, len(browseTree)): for i in range (0, len(browseTree)):
if browseTree[i] == "Random": if browseTree[i] == "Random":
#browseResponse, menuLayer = browseMenuReady(rUrl, source)
currentLine, maxLine = getLineNbrs(browseResponse) currentLine, maxLine = getLineNbrs(browseResponse)
selItem = random.randint(1, maxLine) selItem = random.randint(1, maxLine)
selectLine(rUrl, source, selItem) selectLine(rUrl, source, selItem)
@ -1391,10 +1329,11 @@ def deinit():
global pollTimer global pollTimer
# If we started a poll timer, cancel it on shutdown. # If we started a poll timer, cancel it on shutdown.
if pollTimer is not None: if pollTimer is not None:
pollTimer.cancel() pollTimer = None
def thingRemoved(thing): def thingRemoved(thing):
global pollTimer
logger.log("removeThing called for", thing.name) logger.log("removeThing called for", thing.name)
# Clean up all data related to this thing # Clean up all data related to this thing
if pollTimer is not None: if len(myThings()) == 0 and pollTimer is not None:
pollTimer.cancel() pollTimer = None

View File

@ -0,0 +1,74 @@
import time
from zeroconf import IPVersion, ServiceBrowser, ServiceInfo, Zeroconf
from typing import Callable, List
class ZeroconfDevice(object):
# To do: replace with nymea serviceBrowser
def __init__(self, name: str, ip: str, port: int, model: str, id: str) -> None:
self.name = name
self.ip = ip
self.port = port
self.model = model
self.id = id
def __repr__(self) -> str:
return f"{type(self).__name__}({self.__dict__})"
def __eq__(self, other) -> bool:
return self is other or self.__dict__ == other.__dict__
class ZeroconfListener(object):
# To do: replace with nymea serviceBrowser
"""Basic zeroconf listener."""
def __init__(self, func: Callable[[ServiceInfo], None]) -> None:
"""Initialize zeroconf listener with function callback."""
self._func = func
def __repr__(self) -> str:
return f"{type(self).__name__}({self.__dict__})"
def __eq__(self, other) -> bool:
return self is other or self.__dict__ == other.__dict__
def add_service(self, zeroconf: Zeroconf, type: str, name: str) -> None:
"""Callback function when zeroconf service is discovered."""
self._func(zeroconf.get_service_info(type, name))
def update_service(self, zeroconf: Zeroconf, type: str, name: str) -> None:
return
def remove_service(self, zeroconf: Zeroconf, type: str, name: str) -> None:
return
def discover(service_type: str, timeout: int = 5) -> List[ZeroconfDevice]:
# To do: replace with nymea serviceBrowser
"""From pyvizio: Return all discovered zeroconf services of a given service type over given timeout period."""
services = []
def append_service(info: ServiceInfo) -> None:
"""Append discovered zeroconf service to service list."""
name = info.name[: -(len(info.type) + 1)]
ip = info.parsed_addresses(IPVersion.V4Only)[0]
port = info.port
model = info.properties.get(b"name", "")
id = info.properties.get(b"id")
# handle id decode for various discovered use cases
if isinstance(id, bytes):
try:
int(id, 16)
except Exception:
id = id.hex()
else:
id = None
service = ZeroconfDevice(name, ip, port, model, id)
services.append(service)
zeroconf = Zeroconf()
ServiceBrowser(zeroconf, service_type, ZeroconfListener(append_service))
time.sleep(timeout)
zeroconf.close()
return services