def __init__(self): self.devices = {} self.proxies = {} self.sequencer = Sequencer(self) self.sequencer.start() self.logHandler = ControllerLogHandler() logging.getLogger().addHandler(self.logHandler) self.clients = [] self.slaves = [] self.daemon = Pyro4.Daemon(PyroUtils.getHostname())
def setUp(self): self.controller = Controller() self.sequencer = Sequencer(self.controller)
class TestSequencer(unittest.TestCase): def setUp(self): self.controller = Controller() self.sequencer = Sequencer(self.controller) def performSequenceTest(self, *events): self.sequencer.sequence(*events) self.sequencer.start() sleep(len(events) + 1) def testSimpleEvent(self): m = MagicMock() m.deviceID = "Mock" e1 = Event(m.frobnicate, "badger") self.performSequenceTest(e1) m.frobnicate.assert_called_once_with("badger") def testControllerEvent(self): self.controller.frobnicate = MagicMock(return_value=True) e = ControllerEvent("frobnicate", "badger") self.performSequenceTest(e) self.controller.frobnicate.assert_called_once_with("badger") def testDeviceEvent(self): m = MagicMock() m.deviceID = "Mock" self.controller.addDevice(m) e = DeviceEvent("Mock", "frobnicate", "badger") self.performSequenceTest(e) m.frobnicate.assert_called_once_with("badger") def testCompositeEvent(self): m = MagicMock() e1 = Event(m, "one") e2 = Event(m, "two") ce = CompositeEvent(e1, e2) self.performSequenceTest(ce) m.assert_has_calls([call("one"), call("two")]) @patch("avx.Sequencer.time") def testSleepEvent(self, time): e = SleepEvent(5) self.performSequenceTest(e) # N.B. time.sleep is also called by the sequencer itself time.sleep.assert_has_calls([call(5)]) @patch("avx.Sequencer.logging") def testLogEvent(self, logging): e = LogEvent(logging.INFO, "This is informational") self.performSequenceTest(e) logging.log.assert_called_once_with(logging.INFO, "This is informational")
class Controller(object): ''' A Controller is essentially a bucket of devices, each identified with a string deviceID. ''' pyroName = "avx.controller" version = _version.__version__ def __init__(self): self.devices = {} self.proxies = {} self.sequencer = Sequencer(self) self.sequencer.start() self.logHandler = ControllerLogHandler() logging.getLogger().addHandler(self.logHandler) self.clients = [] self.slaves = [] self.daemon = Pyro4.Daemon(PyroUtils.getHostname()) @staticmethod def fromPyro(controllerID=""): controllerAddress = "PYRONAME:" + Controller.pyroName if controllerID != "": controllerAddress += "." + controllerID logging.info("Creating proxy to controller at " + controllerAddress) controller = ControllerProxy(Pyro4.Proxy(controllerAddress)) remoteVersion = controller.getVersion() if not versionsCompatible(remoteVersion, Controller.version): raise VersionMismatchError(remoteVersion, Controller.version) return controller def loadConfig(self, configFile): try: if isinstance(configFile, file): config = json.load(configFile) else: config = json.load(open(configFile)) for d in config["devices"]: device = Device.create(d, self) self.addDevice(device) if "options" in config: if "controllerID" in config["options"]: self.controllerID = config["options"]["controllerID"] if "slaves" in config["options"]: for slave in config["options"]["slaves"]: try: sc = Controller.fromPyro(slave) if versionsCompatible(sc.getVersion(), self.getVersion()): self.slaves.append(sc) else: logging.error("This Controller is version " + str(self.getVersion()) + " but tried to add slave " + slave + " of version " + str(sc.getVersion())) except NamingError: logging.error("Could not connect to slave with controller ID " + slave) if "http" in config["options"]: if config["options"]["http"] is True: ch = ControllerHttp(self) ch.start() except ValueError: logging.exception("Cannot parse config.json:") def registerClient(self, clientURI): self.clients.append(clientURI) logging.info("Registered client at " + str(clientURI)) logging.info(str(len(self.clients)) + " client(s) now connected") def unregisterClient(self, clientURI): self.clients.remove(clientURI) logging.info("Unregistered client at " + str(clientURI)) logging.info(str(len(self.clients)) + " client(s) still connected") def callAllClients(self, function): ''' function should take a client and do things to it''' for uri in self.clients: try: logging.debug("Calling function " + function.__name__ + " with client at " + str(uri)) client = Pyro4.Proxy(uri) result = function(client) logging.debug("Client call returned " + str(result)) except: logging.exception("Failed to call function on registered client " + str(uri) + ", removing.") self.clients.pop(uri) def getVersion(self): return self.version def addDevice(self, device): if self.hasDevice(device.deviceID): raise DuplicateDeviceIDError(device.deviceID) self.devices[device.deviceID] = device if hasattr(device, "registerDispatcher") and callable(getattr(device, "registerDispatcher")): device.registerDispatcher(self) def getDevice(self, deviceID): return self.devices[deviceID] def proxyDevice(self, deviceID): if deviceID not in self.proxies.keys(): if self.hasDevice(deviceID): self.proxies[deviceID] = self.daemon.register(self.getDevice(deviceID)) else: for slave in self.slaves: if slave.hasDevice(deviceID): self.proxies[deviceID] = slave.proxyDevice(deviceID) return self.proxies[deviceID] def hasDevice(self, deviceID): return deviceID in self.devices def initialise(self): for device in self.devices.itervalues(): device.initialise() atexit.register(self.deinitialise) def deinitialise(self): for device in self.devices.itervalues(): device.deinitialise() def startServing(self): PyroUtils.setHostname() ns = Pyro4.locateNS() uri = self.daemon.register(self) if hasattr(self, "controllerID"): name = self.pyroName + "." + self.controllerID else: name = self.pyroName logging.info("Registering controller as " + name) ns.register(name, uri) atexit.register(lambda: self.daemon.shutdown()) self.daemon.requestLoop() def sequence(self, *events): self.sequencer.sequence(*events) def showPowerOnDialogOnClients(self): self.callAllClients(lambda c: c.showPowerOnDialog()) def showPowerOffDialogOnClients(self): self.callAllClients(lambda c: c.showPowerOffDialog()) def hidePowerDialogOnClients(self): self.callAllClients(lambda c: c.hidePowerDialog()) def getLog(self): return self.logHandler.entries def updateOutputMappings(self, mapping): self.callAllClients(lambda c: c.updateOutputMappings(mapping))
class Controller(object): ''' A Controller is essentially a bucket of devices, each identified with a string deviceID. ''' pyroName = "avx.controller" version = _version.__version__ def __init__(self): self.devices = {} self.proxies = {} self.sequencer = Sequencer(self) self.sequencer.start() self.logHandler = ControllerLogHandler() logging.getLogger().addHandler(self.logHandler) self.clients = [] self.slaves = [] self.daemon = Pyro4.Daemon(PyroUtils.getHostname()) @staticmethod def fromPyro(controllerID=""): controllerAddress = "PYRONAME:" + Controller.pyroName if controllerID != "": controllerAddress += "." + controllerID logging.info("Creating proxy to controller at " + controllerAddress) controller = ControllerProxy(Pyro4.Proxy(controllerAddress)) remoteVersion = controller.getVersion() if not versionsCompatible(remoteVersion, Controller.version): raise VersionMismatchError(remoteVersion, Controller.version) return controller def loadConfig(self, configFile, overrideToDebug=False): try: if isinstance(configFile, file): config = json.load(configFile) self.configFile = configFile.name else: config = json.load(open(configFile)) self.configFile = configFile self.config = config for d in config["devices"]: device = Device.create(d, self) self.addDevice(device) if "options" in config: if "controllerID" in config["options"]: self.controllerID = config["options"]["controllerID"] if "slaves" in config["options"]: for slave in config["options"]["slaves"]: try: sc = Controller.fromPyro(slave) if versionsCompatible(sc.getVersion(), self.getVersion()): self.slaves.append(sc) else: logging.error("This Controller is version " + str(self.getVersion()) + " but tried to add slave " + slave + " of version " + str(sc.getVersion())) except NamingError: logging.error("Could not connect to slave with controller ID " + slave) if "http" in config["options"]: if config["options"]["http"] is True: ch = ControllerHttp(self) ch.start() if "logging" in config: logging.config.dictConfig(config["logging"]) if overrideToDebug: logging.getLogger().setLevel(logging.DEBUG) logging.info("-d specified, overriding any specified default logger level to DEBUG") if "clients" in config: self.clients = list(config["clients"]) self.config["clients"] = self.clients except ValueError: logging.exception("Cannot parse config.json!") def saveConfig(self): if hasattr(self, "configFile"): try: with open(self.configFile, "w") as cfout: json.dump(self.config, cfout) except IOError: logging.exception("Cannot save config file!") def registerClient(self, clientURI): self.clients.append(str(clientURI)) logging.info("Registered client at " + str(clientURI)) logging.info(str(len(self.clients)) + " client(s) now connected") self.saveConfig() def unregisterClient(self, clientURI): self.clients.remove(str(clientURI)) logging.info("Unregistered client at " + str(clientURI)) logging.info(str(len(self.clients)) + " client(s) still connected") self.saveConfig() def broadcast(self, msgType, source, data=None): ''' Send a message to all clients ''' logging.debug("Broadcast: {}, {}, {}".format(msgType, source, data)) for uri in list(self.clients): try: logging.debug("Calling handleMessage on client at {}".format(uri)) client = Pyro4.Proxy(uri) client._pyroTimeout = 1 result = client.handleMessage(msgType, source, data) logging.debug("Client call returned " + str(result)) except Exception: logging.exception("Failed to call handleMessage on registered client {}, removing.".format(uri)) self.unregisterClient(uri) def getVersion(self): return self.version def addDevice(self, device): if self.hasDevice(device.deviceID): raise DuplicateDeviceIDError(device.deviceID) self.devices[device.deviceID] = device device.broadcast = lambda t, b=None: self.broadcast(t, device.deviceID, b) def getDevice(self, deviceID): return self.devices[deviceID] def proxyDevice(self, deviceID): if deviceID not in self.proxies.keys(): if self.hasDevice(deviceID): self.proxies[deviceID] = self.daemon.register(self.getDevice(deviceID)) else: for slave in self.slaves: if slave.hasDevice(deviceID): self.proxies[deviceID] = slave.proxyDevice(deviceID) return self.proxies[deviceID] def hasDevice(self, deviceID): return deviceID in self.devices def initialise(self): for device in self.devices.itervalues(): device.initialise() atexit.register(self.deinitialise) def deinitialise(self): for device in self.devices.itervalues(): device.deinitialise() def startServing(self): PyroUtils.setHostname() ns = Pyro4.locateNS() uri = self.daemon.register(self) if hasattr(self, "controllerID"): name = self.pyroName + "." + self.controllerID else: name = self.pyroName logging.info("Registering controller as " + name) ns.register(name, uri) atexit.register(lambda: self.daemon.shutdown()) self.daemon.requestLoop() def sequence(self, *events): self.sequencer.sequence(*events) def getLog(self): return self.logHandler.entries
class Controller(object): ''' A Controller is essentially a bucket of devices, each identified with a string deviceID. ''' pyroName = "avx.controller" version = _version.__version__ def __init__(self): self.devices = {} self.proxies = {} self.sequencer = Sequencer(self) self.sequencer.start() self.logHandler = ControllerLogHandler() logging.getLogger().addHandler(self.logHandler) self.clients = [] self.slaves = [] self.daemon = Pyro4.Daemon(PyroUtils.getHostname()) @staticmethod def fromPyro(controllerID=""): controllerAddress = "PYRONAME:" + Controller.pyroName if controllerID != "": controllerAddress += "." + controllerID logging.info("Creating proxy to controller at " + controllerAddress) controller = ControllerProxy(Pyro4.Proxy(controllerAddress)) remoteVersion = controller.getVersion() if not versionsCompatible(remoteVersion, Controller.version): raise VersionMismatchError(remoteVersion, Controller.version) return controller def loadConfig(self, configFile): try: if isinstance(configFile, file): config = json.load(configFile) else: config = json.load(open(configFile)) for d in config["devices"]: device = Device.create(d, self) self.addDevice(device) if "options" in config: if "controllerID" in config["options"]: self.controllerID = config["options"]["controllerID"] if "slaves" in config["options"]: for slave in config["options"]["slaves"]: try: sc = Controller.fromPyro(slave) if versionsCompatible(sc.getVersion(), self.getVersion()): self.slaves.append(sc) else: logging.error("This Controller is version " + str(self.getVersion()) + " but tried to add slave " + slave + " of version " + str(sc.getVersion())) except NamingError: logging.error( "Could not connect to slave with controller ID " + slave) if "http" in config["options"]: if config["options"]["http"] is True: ch = ControllerHttp(self) ch.start() except ValueError: logging.exception("Cannot parse config.json:") def registerClient(self, clientURI): self.clients.append(clientURI) logging.info("Registered client at " + str(clientURI)) logging.info(str(len(self.clients)) + " client(s) now connected") def unregisterClient(self, clientURI): self.clients.remove(clientURI) logging.info("Unregistered client at " + str(clientURI)) logging.info(str(len(self.clients)) + " client(s) still connected") def callAllClients(self, function): ''' function should take a client and do things to it''' for uri in self.clients: try: logging.debug("Calling function " + function.__name__ + " with client at " + str(uri)) client = Pyro4.Proxy(uri) result = function(client) logging.debug("Client call returned " + str(result)) except: logging.exception( "Failed to call function on registered client " + str(uri) + ", removing.") self.clients.pop(uri) def getVersion(self): return self.version def addDevice(self, device): if self.hasDevice(device.deviceID): raise DuplicateDeviceIDError(device.deviceID) self.devices[device.deviceID] = device if hasattr(device, "registerDispatcher") and callable( getattr(device, "registerDispatcher")): device.registerDispatcher(self) def getDevice(self, deviceID): return self.devices[deviceID] def proxyDevice(self, deviceID): if deviceID not in self.proxies.keys(): if self.hasDevice(deviceID): self.proxies[deviceID] = self.daemon.register( self.getDevice(deviceID)) else: for slave in self.slaves: if slave.hasDevice(deviceID): self.proxies[deviceID] = slave.proxyDevice(deviceID) return self.proxies[deviceID] def hasDevice(self, deviceID): return deviceID in self.devices def initialise(self): for device in self.devices.itervalues(): device.initialise() atexit.register(self.deinitialise) def deinitialise(self): for device in self.devices.itervalues(): device.deinitialise() def startServing(self): PyroUtils.setHostname() ns = Pyro4.locateNS() uri = self.daemon.register(self) if hasattr(self, "controllerID"): name = self.pyroName + "." + self.controllerID else: name = self.pyroName logging.info("Registering controller as " + name) ns.register(name, uri) atexit.register(lambda: self.daemon.shutdown()) self.daemon.requestLoop() def sequence(self, *events): self.sequencer.sequence(*events) def showPowerOnDialogOnClients(self): self.callAllClients(lambda c: c.showPowerOnDialog()) def showPowerOffDialogOnClients(self): self.callAllClients(lambda c: c.showPowerOffDialog()) def hidePowerDialogOnClients(self): self.callAllClients(lambda c: c.hidePowerDialog()) def getLog(self): return self.logHandler.entries def updateOutputMappings(self, mapping): self.callAllClients(lambda c: c.updateOutputMappings(mapping))