def __init__(self, parentEnvironment): self._parentEnvironment = parentEnvironment self._persistenceLayer = parentEnvironment._persistenceLayer self._tasks = {} self._signal_channel = "task_manager" self._signal_dispatcher = SignalDispatcher(self._signal_channel) self.signal_channel_prefix = "environment_"+str(self._parentEnvironment.cid)
def test_with_basic_message(self): iSignalDispatcher = SignalDispatcher("test_channel") def signalHandler(signal=None,sender=None,realsender=None,data=None,time=None,*args,**kwargs): self.assertEquals(signal,"just a test message") self.assertEquals(sender,"test_channel") iSignalDispatcher.add_handler(channel="test_channel",handler=signalHandler) iSignalDispatcher.send_message("just a test message")
def __init__(self, pathManager=None, packageCheckEnabled=False, packageCheckFrequency=240): self._path_manager = pathManager self._signal_channel = "package_manager" self._signal_dispatcher = SignalDispatcher(self._signal_channel) self.signal_channel_prefix = "package_manager" pollapli_package = Package(name="Pollapli", description="core package", version="0.5.0", installed=True) pollapli_package.cid = uuid.UUID( "23b0d813-a6b2-461a-88ad-a7020ae67742") self._installed_packages = {pollapli_package.cid: pollapli_package} self._available_packages = {} self.package_check_frequency = packageCheckFrequency self.package_check_enabled = packageCheckEnabled self.package_list_url = "http://kaosat.net/pollapli/pollapli_packages.json" self._package_check = task.LoopingCall(self.refresh_packageList) self._addon_path = "." self.updates_path = "." self._max_download_attempts = 5 self._downloader = DownloaderWithProgress()
def __init__(self, deviceType="", connectionType="", hardware_interface_klass=None, logicHandlerKlass=None, protocol=None, options={}, *args, **kwargs): self.driverType = self.__class__.__name__.lower() self.deviceType = deviceType self.connectionType = connectionType self.extra_params = options self.protocol = protocol self.hardware_interface_klass = hardware_interface_klass self.logicHandlerKlass = logicHandlerKlass self.deviceId = None """will be needed to identify a specific device, as the system does not work base on ports""" self._signal_dispatcher = None self.signal_channel_prefix = "" self._signal_channel = "" self.isConfigured = False #when the port association has not been set self.is_handshake_ok = False self.is_authentification_ok = False self.isConnected = False self.isPluggedIn = False self.autoConnect = False #if autoconnect is set to true, the device will be connected as soon as a it is plugged in and detected self.connectionErrors = 0 self.maxConnectionErrors = 2 self.connectionTimeout = 4 self.connectionMode = 1 self.deferred = defer.Deferred() """just for future reference : this is not implemented but would be a declarative way to define the different "configuration steps" of this driver" *basically a dictonary with keys beeing the connection modes, and values a list of strings representing the methods to call *would require a "validator" of sorts (certain elements need to be mandatory : such as the validation/setting of device ids """ configSteps = {} configSteps[0] = ["_handle_deviceHandshake", "_handle_deviceIdInit"] configSteps[1] = [ "_handle_deviceHandshake", "_handle_deviceIdInit", "some_other_method" ] #just a test self._signal_dispatcher = SignalDispatcher("driver_manager") """for exposing capabilites""" self.endpoints = []
def __init__(self, hardware_id=None, auto_connect=False, max_connection_errors=2, connection_timeout=4, do_hanshake=False, do_authentification=False): """ autoconnect:if autoconnect is True,device will be connected as soon as it is plugged in and detected max_connection_errors: the number of connection errors above which the driver gets disconnected connection_timeout: the number of seconds after which the driver gets disconnected (only in the initial , configuration phases by default) """ BaseComponent.__init__(self, parent=None) self.auto_connect = auto_connect self.max_connection_errors = max_connection_errors self.connection_timeout = connection_timeout self.do_authentification = do_authentification self.do_handshake = do_hanshake self._hardware_interface = None self.hardware_id = hardware_id self.is_configured = False self.is_bound = False # when port association has not been set self.is_handshake_ok = False self.is_authentification_ok = False self.is_connected = False self.is_bound = False self.is_busy = False self.errors = [] self.connection_mode = 1 self._connection_errors = 0 self._connection_timeout = None self.deferred = defer.Deferred() self._signal_channel_prefix = "" self._signal_dispatcher = SignalDispatcher("driver_manager")
def __init__(self,deviceType="",connectionType="",hardware_interface_klass=None,logicHandlerKlass=None,protocol=None,options={},*args,**kwargs): self.driverType=self.__class__.__name__.lower() self.deviceType=deviceType self.connectionType=connectionType self.extra_params=options self.protocol=protocol self.hardware_interface_klass=hardware_interface_klass self.logicHandlerKlass=logicHandlerKlass self.deviceId=None """will be needed to identify a specific device, as the system does not work base on ports""" self._signal_dispatcher=None self.signal_channel_prefix="" self._signal_channel="" self.isConfigured=False#when the port association has not been set self.is_handshake_ok=False self.is_authentification_ok=False self.isConnected=False self.isPluggedIn=False self.autoConnect=False#if autoconnect is set to true, the device will be connected as soon as a it is plugged in and detected self.connectionErrors=0 self.maxConnectionErrors=2 self.connectionTimeout=4 self.connectionMode=1 self.deferred=defer.Deferred() """just for future reference : this is not implemented but would be a declarative way to define the different "configuration steps" of this driver" *basically a dictonary with keys beeing the connection modes, and values a list of strings representing the methods to call *would require a "validator" of sorts (certain elements need to be mandatory : such as the validation/setting of device ids """ configSteps={} configSteps[0]=["_handle_deviceHandshake","_handle_deviceIdInit"] configSteps[1]=["_handle_deviceHandshake","_handle_deviceIdInit","some_other_method"] #just a test self._signal_dispatcher=SignalDispatcher("driver_manager") """for exposing capabilites""" self.endpoints=[]
def __init__(self, name="base_device", description="base device", environment="Home"): """ :param environment : just a tag to identify the environment this device belongs to """ BaseDeviceComponent.__init__(self, None, name="base_device", description="base device") self.name = name self.description = description self.status = "inactive" self.environment = environment self.driver = None self._signal_channel = "device %s" % self.cid self._signal_dispatcher = SignalDispatcher(self._signal_channel)
def __init__(self, pathManager=None, packageCheckEnabled=False, packageCheckFrequency = 240): self._path_manager = pathManager self._signal_channel = "package_manager" self._signal_dispatcher = SignalDispatcher(self._signal_channel) self.signal_channel_prefix = "package_manager" pollapli_package = Package(name="Pollapli", description="core package", version="0.5.0", installed=True) pollapli_package.cid = uuid.UUID("23b0d813-a6b2-461a-88ad-a7020ae67742") self._installed_packages = {pollapli_package.cid: pollapli_package} self._available_packages = {} self.package_check_frequency = packageCheckFrequency self.package_check_enabled = packageCheckEnabled self.package_list_url = "http://kaosat.net/pollapli/pollapli_packages.json" self._package_check = task.LoopingCall(self.refresh_packageList) self._addon_path = "." self.updates_path = "." self._max_download_attempts = 5 self._downloader = DownloaderWithProgress()
def test_with_basic_message(self): iSignalDispatcher = SignalDispatcher("test_channel") def signalHandler(signal=None, sender=None, realsender=None, data=None, time=None, *args, **kwargs): self.assertEquals(signal, "just a test message") self.assertEquals(sender, "test_channel") iSignalDispatcher.add_handler(channel="test_channel", handler=signalHandler) iSignalDispatcher.send_message("just a test message")
class DeviceManager(object): """ Class for managing devices: works as a container, a handler and a central management point for the list of available devices the signal signature for device manager events is as follows: -for example environment_id.device_created : this is for coherence, signal names use underscores, while hierarchy is represented by dots) """ def __init__(self, parentEnvironment): self._parentEnvironment = parentEnvironment self._persistenceLayer = parentEnvironment._persistenceLayer self._devices = {} self._signal_channel = "device_manager" self._signal_dispatcher = SignalDispatcher(self._signal_channel) self.signal_channel_prefix = "environment_" + str(self._parentEnvironment.cid) @defer.inlineCallbacks def setup(self): if self._persistenceLayer is None: self._persistenceLayer = self._parentEnvironment._persistenceLayer devices = yield self._persistenceLayer.load_devices(environmentId=self._parentEnvironment.cid) for device in devices: device._parent = self._parentEnvironment device._persistenceLayer = self._persistenceLayer self._devices[device.cid] = device #yield device.setup() def _send_signal(self, signal="", data=None): prefix = self.signal_channel_prefix + "." self._signal_dispatcher.send_message(prefix + signal, self, data) """ ########################################################################### The following are the "CRUD" (Create, read, update,delete) methods for the general handling of devices """ @defer.inlineCallbacks def add_device(self, name="Default device", description="", device_type=None, *args, **kwargs): """ Add a new device to the list of devices of the current environment Params: name: the name of the device Desciption: short description of device device_type: the device_type of the device : very important , as it will be used to instanciate the correct class instance Connector:the connector to use for this device Driver: the driver to use for this device's connector """ device = Device(parent=self._parentEnvironment, name=name, description=description, device_type=device_type) yield self._persistenceLayer.save_device(device) self._devices[device.cid] = device log.msg("Added device ", name, logLevel=logging.CRITICAL) self._send_signal("device_created", device) defer.returnValue(device) def get_device(self, device_id): if not device_id in self._devices.keys(): raise DeviceNotFound() return self._devices[device_id] def get_devices(self, filters=None, *args, **kwargs): """ Returns the list of devices, filtered by the filter param the filter is a dictionary of list, with each key beeing an attribute to check, and the values in the list , values of that param to check against """ deferred = defer.Deferred() def filter_check(device, filters): for key in filters.keys(): if not getattr(device, key) in filters[key]: return False return True def get(filters, device_list): if filter: return [device for device in device_list if filter_check(device, filters)] else: return device_list deferred.addCallback(get, self._devices.values()) reactor.callLater(0.5, deferred.callback, filters) return deferred @defer.inlineCallbacks def update_device(self, device_id, name=None, description=None, *args, **kwargs): """Method for device update""" device = self._devices[device_id] device.name = name device.description = description yield self._persistenceLayer.save_device(device) self._send_signal("device_updated", device) log.msg("updated device :new name", name, "new descrption", description, logLevel=logging.CRITICAL) defer.succeed(device) @defer.inlineCallbacks def delete_device(self, device_id): """ Remove a device : this needs a whole set of checks, as it would delete a device completely Params: device_id: the device_id of the device """ device = self._devices.get(device_id, None) if device is None: raise DeviceNotFound() yield self._persistenceLayer.delete_device(device) del self._devices[device_id] self._send_signal("device_deleted", device) log.msg("Removed device ", device.name, logLevel=logging.CRITICAL) defer.succeed(True) @defer.inlineCallbacks def clear_devices(self): """ Removes & deletes ALL the devices, should be used with care """ for device in self._devices.values(): yield self.delete_device(device.cid) self._send_signal("devices_cleared", self._devices) """
class TaskManager(object): def __init__(self, parentEnvironment): self._parentEnvironment = parentEnvironment self._persistenceLayer = parentEnvironment._persistenceLayer self._tasks = {} self._signal_channel = "task_manager" self._signal_dispatcher = SignalDispatcher(self._signal_channel) self.signal_channel_prefix = "environment_"+str(self._parentEnvironment.cid) @defer.inlineCallbacks def setup(self): if self._persistenceLayer is None: self._persistenceLayer = self._parentEnvironment._persistenceLayer tasks = yield self._persistenceLayer.load_tasks(environmentId = self._parentEnvironment.cid) for task in tasks: task._parent = self._parentEnvironment task._persistenceLayer = self._persistenceLayer self._tasks[task.cid] = task #yield task.setup() def _send_signal(self,signal="",data=None): prefix=self.signal_channel_prefix+"." self._signal_dispatcher.send_message(prefix+signal,self,data) """ #################################################################################### The following are the "CRUD" (Create, read, update,delete) methods for the general handling of tasks """ @defer.inlineCallbacks def add_task(self,name="Default Task",description="Default task description",status = "inactive",params={},*args,**kwargs): """ Add a new task to the list of task of the current environment Params: name: the name of the task Desciption: short description of task """ task = Task(parent= self._parentEnvironment, name = name, description = description, status = status) #yield task.setup() self._tasks[task.cid] = task yield self._persistenceLayer.save_task(task) log.msg("Added task named:",name ," description:",description,"with id",task.cid, system="task manager", logLevel=logging.CRITICAL) self._signal_dispatcher.send_message("task.created",self,task) defer.returnValue(task) def get_task(self,id): """ returns the task with given id """ if not id in self._tasks.keys(): raise TaskNotFound() else: defer.succeed(self._tasks[id]) def get_tasks(self,filter=None): """ Returns the list of tasks, filtered by the filter param the filter is a dictionary of list, with each key beeing an attribute to check, and the values in the list , values of that param to check against """ d=defer.Deferred() def filter_check(task,filter): for key in filter.keys(): if not getattr(task, key) in filter[key]: return False return True def get(filter,tasksList): if filter: return [task for task in tasksList if filter_check(task,filter)] else: return tasksList d.addCallback(get,self._tasks.values()) reactor.callLater(0.5,d.callback,filter) return d @defer.inlineCallbacks def remove_task(self,id,forceStop=False): """Removes the task with id==id Shuts down the task before removing it If forcestop is true, shutdown the task even if it is running """ task = self._tasks[id] if forceStop: if task.isRunning: task.shutdown() yield self._persistenceLayer.delete_task(task) del self._tasks[id] self._send_signal("task_deleted", task) log.msg("Removed task ",task.name,logLevel=logging.CRITICAL) @defer.inlineCallbacks def clear_tasks(self,forceStop=False): """Clears the task list completely Params: forceStop: if set to true, the current running task gets stopped and removed aswell """ for device in self._tasks.values(): yield self.remove_task(device.id) self._send_signal("devices_cleared", self._tasks) """ #################################################################################### The following are the methods for the more detailed manipulation of tasks """ def add_conditionToTask(self, id, condition): pass def add_actionToTask(self, id, action): pass
class Driver(object): """ Driver class: higher level handler of device connection that formats outgoing and incoming commands according to a spec before they get sent to the lower level connector. It actually mimics the way system device drivers work in a way. You can think of the events beeing sent out by the driver (dataRecieved etc) as interupts of sorts ConnectionModes : 0:setup 1:normal 2:setId 3:forced: to forcefully connect devices which have no deviceId stored Thoughts for future evolution each driver will have a series of endpoints or slots/hooks, which represent the actual subdevices it handles for example for reprap type devices, there is a "position" endpoint (abstract), 3 endpoints for the cartesian bot motors , at least an endpoint for head temperature , one for the heater etc or this could be in a hiearchy , reflecting the one off the nodes: variable endpoint : position, and sub ones for motors """ def __init__(self, deviceType="", connectionType="", hardware_interface_klass=None, logicHandlerKlass=None, protocol=None, options={}, *args, **kwargs): self.driverType = self.__class__.__name__.lower() self.deviceType = deviceType self.connectionType = connectionType self.extra_params = options self.protocol = protocol self.hardware_interface_klass = hardware_interface_klass self.logicHandlerKlass = logicHandlerKlass self.deviceId = None """will be needed to identify a specific device, as the system does not work base on ports""" self._signal_dispatcher = None self.signal_channel_prefix = "" self._signal_channel = "" self.isConfigured = False #when the port association has not been set self.is_handshake_ok = False self.is_authentification_ok = False self.isConnected = False self.isPluggedIn = False self.autoConnect = False #if autoconnect is set to true, the device will be connected as soon as a it is plugged in and detected self.connectionErrors = 0 self.maxConnectionErrors = 2 self.connectionTimeout = 4 self.connectionMode = 1 self.deferred = defer.Deferred() """just for future reference : this is not implemented but would be a declarative way to define the different "configuration steps" of this driver" *basically a dictonary with keys beeing the connection modes, and values a list of strings representing the methods to call *would require a "validator" of sorts (certain elements need to be mandatory : such as the validation/setting of device ids """ configSteps = {} configSteps[0] = ["_handle_deviceHandshake", "_handle_deviceIdInit"] configSteps[1] = [ "_handle_deviceHandshake", "_handle_deviceIdInit", "some_other_method" ] #just a test self._signal_dispatcher = SignalDispatcher("driver_manager") """for exposing capabilites""" self.endpoints = [] def afterInit(self): """this is a workaround needed when loading a driver from db""" try: if not isinstance(self.extra_params, dict): self.extra_params = ast.literal_eval(self.extra_params) except Exception as inst: log.msg("Failed to load driver extra_params from db:", inst, system="Driver", logLevel=logging.CRITICAL) @defer.inlineCallbacks def setup(self, *args, **kwargs): self.hardwareHandler = self.hardware_interface_klass( self, self.protocol, **self.extra_params) self.logicHandler = self.logicHandlerKlass(self, **self.extra_params) node = (yield self.node.get()) env = (yield node.environment.get()) self.signal_channel_prefix = "environment_" + str( env.id) + ".node_" + str(node.id) self._signal_dispatcher.add_handler(handler=self.send_command, signal="addCommand") log.msg("Driver of type", self.driverType, "setup sucessfully", system="Driver", logLevel=logging.INFO) def bind(self, port, setId=True): self.deferred = defer.Deferred() log.msg("Attemtping to bind driver", self, "with deviceId:", self.deviceId, "to port", port, system="Driver", logLevel=logging.DEBUG) self.hardwareHandler.connect(setIdMode=setId, port=port) return self.deferred def connect(self, mode=None, *args, **kwargs): if not self.isConnected: if mode is not None: self.connectionMode = mode log.msg("Connecting in mode:", self.connectionMode, system="Driver", logLevel=logging.CRITICAL) if mode == 3: """special case for forced connection""" unboundPorts = DriverManager.bindings.get_unbound_ports() if len(unboundPorts) > 0: port = unboundPorts[0] log.msg("Connecting in mode:", self.connectionMode, "to port", port, system="Driver", logLevel=logging.CRITICAL) DriverManager.bindings.bind(self, port) self.pluggedIn(port) self.hardwareHandler.connect(port=port) else: self.hardwareHandler.connect() else: self.hardwareHandler.connect() def reconnect(self, *args, **kwargs): self.hardwareHandler.reconnect(*args, **kwargs) def disconnect(self, *args, **kwargs): self.hardwareHandler.disconnect(*args, **kwargs) def pluggedIn(self, port): self._send_signal("plugged_In", port) self.isPluggedIn = True if self.autoConnect: #slight delay, to prevent certain problems when trying to send data to the device too fast reactor.callLater(1, self.connect, 1) def pluggedOut(self, port): self.isConfigured = False self.is_handshake_ok = False self.is_authentification_ok = False self.isConnected = False self.isPluggedIn = False self._send_signal("plugged_Out", port) #self._signal_dispatcher.send_message("pluggedOut",{"data":port}) def _send_signal(self, signal="", data=None): prefix = self.signal_channel_prefix + ".driver." self._signal_dispatcher.send_message(prefix + signal, self, data) def send_command(self, data, sender=None, callback=None, *args, **kwargs): # print("going to send command",data,"from",sender) if not self.isConnected: raise DeviceNotConnected() if self.logicHandler: self.logicHandler._handle_request(data=data, sender=sender, callback=callback) def _send_data(self, data, *arrgs, **kwargs): self.hardwareHandler.send_data(data) def _handle_response(self, data): if self.logicHandler: self.logicHandler._handle_response(data) """higher level methods""" def startup(self): pass def shutdown(self): pass def init(self): pass def get_firmware_version(self): pass def set_debug_level(self, level): pass def teststuff(self, params, *args, **kwargs): pass def variable_set(self, variable, params, sender=None, *args, **kwargs): pass def variable_get(self, variable, params, sender=None, *args, **kwargs): pass """ #################################################################################### Experimental """ def start_command(self): pass def close_command(self): pass def get_endpoint(self, filter=None): """return a list of endpoints, filtered by parameters""" d = defer.Deferred() def filter_check(endpoint, filter): for key in filter.keys(): if not getattr(endpoint, key) in filter[key]: return False return True def get(filter): if filter: return [ endpoint for endpoint in self.endpoints if filter_check(endpoint, filter) ] else: pass d.addCallback(get) reactor.callLater(0.5, d.callback, filter) return d
class DeviceManager(object): """ Class for managing devices: works as a container, a handler and a central management point for the list of available devices the signal signature for device manager events is as follows: -for example environment_id.device_created : this is for coherence, signal names use underscores, while hierarchy is represented by dots) """ def __init__(self, parentEnvironment): self._parentEnvironment = parentEnvironment self._persistenceLayer = parentEnvironment._persistenceLayer self._devices = {} self._signal_channel = "device_manager" self._signal_dispatcher = SignalDispatcher(self._signal_channel) self.signal_channel_prefix = "environment_" + str( self._parentEnvironment.cid) @defer.inlineCallbacks def setup(self): if self._persistenceLayer is None: self._persistenceLayer = self._parentEnvironment._persistenceLayer devices = yield self._persistenceLayer.load_devices( environmentId=self._parentEnvironment.cid) for device in devices: device._parent = self._parentEnvironment device._persistenceLayer = self._persistenceLayer self._devices[device.cid] = device #yield device.setup() def _send_signal(self, signal="", data=None): prefix = self.signal_channel_prefix + "." self._signal_dispatcher.send_message(prefix + signal, self, data) """ ########################################################################### The following are the "CRUD" (Create, read, update,delete) methods for the general handling of devices """ @defer.inlineCallbacks def add_device(self, name="Default device", description="", device_type=None, *args, **kwargs): """ Add a new device to the list of devices of the current environment Params: name: the name of the device Desciption: short description of device device_type: the device_type of the device : very important , as it will be used to instanciate the correct class instance Connector:the connector to use for this device Driver: the driver to use for this device's connector """ device = Device(parent=self._parentEnvironment, name=name, description=description, device_type=device_type) yield self._persistenceLayer.save_device(device) self._devices[device.cid] = device log.msg("Added device ", name, logLevel=logging.CRITICAL) self._send_signal("device_created", device) defer.returnValue(device) def get_device(self, device_id): if not device_id in self._devices.keys(): raise DeviceNotFound() return self._devices[device_id] def get_devices(self, filters=None, *args, **kwargs): """ Returns the list of devices, filtered by the filter param the filter is a dictionary of list, with each key beeing an attribute to check, and the values in the list , values of that param to check against """ deferred = defer.Deferred() def filter_check(device, filters): for key in filters.keys(): if not getattr(device, key) in filters[key]: return False return True def get(filters, device_list): if filter: return [ device for device in device_list if filter_check(device, filters) ] else: return device_list deferred.addCallback(get, self._devices.values()) reactor.callLater(0.5, deferred.callback, filters) return deferred @defer.inlineCallbacks def update_device(self, device_id, name=None, description=None, *args, **kwargs): """Method for device update""" device = self._devices[device_id] device.name = name device.description = description yield self._persistenceLayer.save_device(device) self._send_signal("device_updated", device) log.msg("updated device :new name", name, "new descrption", description, logLevel=logging.CRITICAL) defer.succeed(device) @defer.inlineCallbacks def delete_device(self, device_id): """ Remove a device : this needs a whole set of checks, as it would delete a device completely Params: device_id: the device_id of the device """ device = self._devices.get(device_id, None) if device is None: raise DeviceNotFound() yield self._persistenceLayer.delete_device(device) del self._devices[device_id] self._send_signal("device_deleted", device) log.msg("Removed device ", device.name, logLevel=logging.CRITICAL) defer.succeed(True) @defer.inlineCallbacks def clear_devices(self): """ Removes & deletes ALL the devices, should be used with care """ for device in self._devices.values(): yield self.delete_device(device.cid) self._send_signal("devices_cleared", self._devices) """
class PackageManager(object): """ Class for managing updates/addons: works as a container, a handler and a central management point for the list of available and installed addons/updates """ def __init__(self, pathManager=None, packageCheckEnabled=False, packageCheckFrequency = 240): self._path_manager = pathManager self._signal_channel = "package_manager" self._signal_dispatcher = SignalDispatcher(self._signal_channel) self.signal_channel_prefix = "package_manager" pollapli_package = Package(name="Pollapli", description="core package", version="0.5.0", installed=True) pollapli_package.cid = uuid.UUID("23b0d813-a6b2-461a-88ad-a7020ae67742") self._installed_packages = {pollapli_package.cid: pollapli_package} self._available_packages = {} self.package_check_frequency = packageCheckFrequency self.package_check_enabled = packageCheckEnabled self.package_list_url = "http://kaosat.net/pollapli/pollapli_packages.json" self._package_check = task.LoopingCall(self.refresh_packageList) self._addon_path = "." self.updates_path = "." self._max_download_attempts = 5 self._downloader = DownloaderWithProgress() @defer.inlineCallbacks def setup(self): """initial configuration method : * first it checks for locally copied addons and extracts/installs them * then tries to fetch the list of available packages from the update server * if any new (newer version number or never seen before) update is available, it adds it to the dictionary of available updates, but it does NOT download or install those updates """ if self.package_check_enabled: self._package_check.start(interval=self.package_check_frequency, now=False) yield self._list_localPackages() # updates = yield self._persistenceLayer.load_updates() # for update in updates: # self._updates[update.name]=update log.msg("Package Manager setup succesfully ", system="Package Manager", logLevel=logging.INFO) def tearDown(self): pass def _send_signal(self, signal="", data=None): prefix = "%s." % self.signal_channel_prefix self._signal_dispatcher.send_message(prefix + signal, self, data) def enable_package_checking(self): if not self.package_check_enabled: self._package_check.start(interval=self.package_check_frequency, now=False) self.package_check_enabled = True def disable_package_checking(self): if self.package_check_enabled: self._package_check.stop() self.package_check_enabled = False """ #################################################################################### The following are the "CRUD" (Create, read, update,delete) methods for the general handling of updates/addons """ def get_package(self, id=None): package = self._installed_packages.get(id) if package is None: raise UnknownPackage() return package def get_packages(self, filters=None): """ Returns the list of packages, filtered by the filter param the filter is a dictionary of list, with each key beeing an attribute to check, and the values in the list , values of that param to check against """ deferred = defer.Deferred() def filter_check(package, filters): for key in filters.keys(): if not getattr(package, key) in filters[key]: return False return True @defer.inlineCallbacks def _get_packages(filters, package_list): yield self.refresh_packageList() if filter: defer.returnValue([package for package in package_list if filter_check(package, filters)]) else: defer.returnValue(package_list) deferred.addCallback(_get_packages, self._installed_packages.values()) reactor.callLater(0.5, deferred.callback, filters) return deferred def delete_package(self, package_id): """ Remove an package : this needs a whole set of checks, as it would delete an package completely Params: id: the id of the package """ deferred = defer.Deferred() def remove(package_id, packages): package = packages[id] if package.type == "package": raise Exception("only addons can be removed") packages[id].delete() del packages[id] log.msg("Removed addOn ", package.name, "with id ", package_id, logLevel=logging.CRITICAL) deferred.addCallback(remove, self._installed_packages) reactor.callLater(0, deferred.callback, id) return deferred @defer.inlineCallbacks def clear_packages(self): """ Removes & deletes ALL the packages that are addons "simple" packages cannot be deleted This should be used with care,as well as checks on client side """ for package_id in self._installed_packages.keys(): yield self.delete_package(package_id=package_id) @defer.inlineCallbacks def get_plugins(self, interface=None, addOnName=None): """ find a specific plugin in the list of available addOns, by interface and/or addOn """ plugins = [] @defer.inlineCallbacks def scan(path): plugins = [] try: addonpackages = pkgutil.walk_packages(path=[path], prefix='') for loader, name,isPkg in addonpackages: mod = pkgutil.get_loader(name).load_module(name) try: plugins.extend((yield getPlugins(interface, mod))) except Exception as inst: log.msg("error in fetching plugin: ", str(inst), system="Package Manager", logLevel=logging.CRITICAL) except Exception as inst: log.msg("error %s in listing packages in path : %s " % (str(inst), path), system="Package Manager", logLevel=logging.CRITICAL) defer.returnValue(plugins) for addOn in self._installed_packages.itervalues(): if addOn.type == "addon": if addOnName: if addOn.name == addOnName and addOn.enabled: plugins.extend((yield scan(addOn.installPath))) else: if addOn.enabled: plugins.extend((yield scan(addOn.installPath))) log.msg("Got plugins: ", str(plugins), system="Package Manager", logLevel=logging.DEBUG) defer.returnValue(plugins) def enable_addon(self, id=None): package = self._installed_packages.get(id) if package is None: raise UnknownPackage() package.enabled = True def disable_addon(self, id=None): package = self._installed_packages.get(id) if package is None: raise UnknownPackage() package.enabled = False """ #################################################################################### Package download and installation methods """ @defer.inlineCallbacks def refresh_packageList(self): """Fetches the remote package list, downloads it, and if there were any changes, packages the in memory package list accordingly """ log.msg("checking for new packages : time", time.time(), logLevel=logging.CRITICAL) packageInfoPath = os.path.join(self._path_manager.tmpPath, "packages.txt") try: yield DownloaderWithProgress.download(url=self.package_list_url, destination=packageInfoPath) self._parse_packageListFile() except Exception as inst: log.msg("Failed to download package master list: error:", inst, system="Package Manager", logLevel=logging.CRITICAL) @defer.inlineCallbacks def setup_package(self,id): try: yield self._downloadPackage(id) yield self.installPackage(id) except Exception as inst: log.msg("Failed to setup package ", id ,"because of error", inst, system="Package Manager", logLevel=logging.CRITICAL) @defer.inlineCallbacks def _download_package(self, id): """downloads the specified package, if the download was successfull, it checks the package's md5 checksum versus the one stored in the package info for integretiy, and if succefull, dispatches a success message """ package = self._available_packages.get(id) if package is None: raise PackageNotFound() try: downloadPath = os.path.join(self._path_manager.tmpPath,package.file) yield DownloaderWithProgress.download(url = package.downloadUrl, destination= downloadPath, object=package, refChecksum=package.fileHash) package.downloaded = True #update.installPath=self._addon_path self._send_signal("package_download_succeeded", package) log.msg("Successfully downloaded package ",package.name,system="Package Manager",logLevel=logging.DEBUG) except Exception as inst: self._send_signal("package_download_failed", package) log.msg("Failed to download package",package.name," error:",inst,system="Package Manager",logLevel=logging.CRITICAL) @defer.inlineCallbacks def install_package(self,id): """installs the specified package""" package = self._available_packages.get(id) if package is None: raise PackageNotFound() if not package.downloaded: raise Exception("Cannot install package that was not downloaded first") """Steps: extract package in tmp folder create list of files it is going to replace backup those files to a "rollback" directory copy the new files to the correct place restart the whole system check if all starts well if not, delete new files, copy back rollback files, and restart again if yes all done """ baseName = os.path.splitext(package.file)[0] baseName = baseName.replace('-','_').replace('.','_') tmpPath = os.path.join(self._path_manager.tmpPath,baseName) sourcePath = os.path.join(self._path_manager.tmpPath,package.file) if package.type == "addon": try: destinationPath = os.path.join(self._path_manager._addon_path,baseName) extractedPackageDir = yield self._extract_package(sourcePath,tmpPath) extractedPackageDir = os.path.join(tmpPath,extractedPackageDir) shutil.copytree(extractedPackageDir, destinationPath) shutil.rmtree(tmpPath) os.remove(sourcePath) self._installed_packages[package.cid] = package package.installPath = os.path.join(self._path_manager._addon_path,os.path.basename(extractedPackageDir)) package.enabled = True self.add_package_toPythonPath(package.installPath) self._send_signal("addon_install_succeeded", package) except Exception as inst: raise Exception("Failed to install addOn: error %s" %(str(inst))) elif package.type == "update": #TODO: add restart of system try: packageToUpdate = self._installed_packages.get(package.targetId) if packageToUpdate is None: raise Exception("Attempting to update not installed package, aborting") destinationPath = packageToUpdate.installPath extractedPackagePath = yield self._extract_package(sourcePath,tmpPath) extractedPackagePath = os.path.join(tmpPath,extractedPackagePath) # yield self._backup_files(".") dirCompare = filecmp.dircmp(destinationPath,extractedPackagePath,hide = []) filesToBackup = dirCompare.common backupFolderName = os.path.splitext(packageToUpdate.file)[0] backupFolderName = backupFolderName.replace('-','_').replace('.','_').replace(' ','')+"_back" backupFolderName = os.path.join(self._path_manager.tmpPath,backupFolderName) #TODO: add id at end ? os.makedirs(backupFolderName) for fileDir in filesToBackup: fileDir = os.path.join(destinationPath,fileDir) if os.path.isdir(fileDir): dir_util.copy_tree(fileDir, backupFolderName) else: file_util.copy_file(fileDir, backupFolderName) dir_util.copy_tree(extractedPackagePath, destinationPath) shutil.rmtree(backupFolderName) packageToUpdate.version = package.toVersion self._send_signal("update_install_succeeded", package) except Exception as inst: raise Exception("Failed to install update: error %s" %(str(inst))) else: raise Exception("Unknown package type") """ #################################################################################### Helper Methods """ def _list_localPackages(self): """ Scans the packages path for addons etc , and adds them to the list of currently available packages """ #TODO : also parse info for main (pollapli) package packageDirs = os.listdir(self._path_manager._addon_path) for fileDir in packageDirs : if zipfile.is_zipfile(fileDir): sourcePath = fileDir baseName = os.path.splitext(sourcePath)[0] baseName = baseName.replace('-','_').replace('.','_') tmpPath = os.path.join(self._path_manager.tmpPath,baseName) destinationPath = os.path.join(self._path_manager._addon_path,baseName) extractedPackageDir = yield self._extract_package(sourcePath,tmpPath) extractedPackageDir = os.path.join(sourcePath,extractedPackageDir) shutil.copytree(extractedPackageDir, destinationPath) shutil.rmtree(tmpPath) os.remove(sourcePath) fileDir = destinationPath self.add_package_toPythonPath(fileDir) def _parse_packageListFile(self): packagesFileName = os.path.join(self._path_manager.tmpPath,"pollapli_packages.json") packageFile = file(packagesFileName,"r") packageInfos = json.loads(packageFile.read(),"iso-8859-1") packageFile.close() packageList = [Package.from_dict(package) for package in packageInfos["packages"]] newPackageCount = 0 for package in packageList: if package.type == "update": if package.targetId in self._installed_packages.keys(): if package.fromVersion == self._installed_packages[package.targetId].version: if self._available_packages.get(package.cid,None) is None: self._available_packages[package.cid] = package newPackageCount +=1 elif package.type == "addon": if package.cid not in self._installed_packages.keys(): if self._available_packages.get(package.cid,None) is None: self._available_packages[package.cid] = package newPackageCount +=1 if newPackageCount>0: self._send_signal("new_packages_available", newPackageCount) def _backup_packageFiles(self,rootPath): d = defer.Deferred() return d def _extract_package(self,sourcePath,destinationPath): d = defer.Deferred() def extract(sourcePath,destinationPath): if not zipfile.is_zipfile(sourcePath): raise Exception("invalid package extension") packageFile = ZipFile(sourcePath, 'r') packageFile.extractall(path = destinationPath) packageFile.close() return os.listdir(destinationPath)[0] d.addCallback(extract,destinationPath=destinationPath) reactor.callLater(0,d.callback,sourcePath) return d @defer.inlineCallbacks def _extract_allPackages(self): """ helper "hack" function to extract egg/zip files into their adapted directories """ #TODO: fix this packageDirs = os.listdir(self._path_manager._addon_path) for fileDir in packageDirs : if zipfile.is_zipfile(fileDir): yield self._extract_package(fileDir) if os.path.isdir(fileDir): self.add_package_toPythonPath(fileDir) #should perhaps be like this: #if it is a zip file, use the install_package method #if it is a directory, check if is in the path, and if not , add it to the path def add_package_toPythonPath(self,path): if os.path.isdir(path): if not path in sys.path: sys.path.insert(0, path) @defer.inlineCallbacks def update_addOns(self): """ wrapper method, for extraction+ list package in case of newly installed addons """ yield self.extract_addons() yield self.list_addons() def save_package_list(self): for package in self._installed_packages: import json json.dumps(package)
def __init__(self,persistenceLayer = None): self._persistenceLayer = persistenceLayer self._logger=log.PythonLoggingObserver("dobozweb.core.components.environments.environmentManager") self._environments = {} self._signal_channel="environment_manager" self._signal_dispatcher = SignalDispatcher(self._signal_channel)
class EnvironmentManager(object): """ Class acting as a central access point for all the functionality of environments """ def __init__(self,persistenceLayer = None): self._persistenceLayer = persistenceLayer self._logger=log.PythonLoggingObserver("dobozweb.core.components.environments.environmentManager") self._environments = {} self._signal_channel="environment_manager" self._signal_dispatcher = SignalDispatcher(self._signal_channel) def __getattr__(self, attr_name): for env in self._environments.values(): if hasattr(env, attr_name): return getattr(env, attr_name) raise AttributeError(attr_name) @defer.inlineCallbacks def setup(self,*args,**kwargs): """Retrieve all existing environments from disk""" environments = yield self._persistenceLayer.load_environments() for environment in environments: self._environments[environment.cid] = environment environment._persistenceLayer = self._persistenceLayer yield environment.setup() log.msg("Environment manager setup correctly", system="environement manager", logLevel=logging.INFO) def teardown(self): """ Shuts down the environment manager and everything associated with it : ie EVERYTHING !! Should not be called in most cases """ pass def _send_signal(self, signal="", data=None): prefix=self._signal_channel+"." self._signal_dispatcher.send_message(prefix+signal,self,data) """ #################################################################################### The following are the "CRUD" (Create, read, update,delete) methods for the general handling of environements """ @defer.inlineCallbacks def add_environment(self,name="Default Environment",description="Add Description here",status="inactive"): """ Add an environment to the list of managed environements : Automatically creates a new folder and launches the new environement auto creation Params: name: the name of the environment description:a short description of the environment status: either frozen or live : whether the environment is active or not """ for environment in self._environments.values(): if environment.name == name: raise EnvironmentAlreadyExists() environment = Environment(persistenceLayer=self._persistenceLayer, name=name,description=description,status=status) yield self._persistenceLayer.save_environment(environment) self._environments[environment.cid] = environment self._send_signal("environment.created",environment) log.msg("Added environment named:",name ," description:",description,"with id",environment.cid, system="environment manager", logLevel=logging.CRITICAL) defer.returnValue(environment) def get_environments(self,filter=None): """ Returns the list of environments, filtered by the filter param the filter is a dictionary of list, with each key beeing an attribute to check, and the values in the list , values of that param to check against """ d=defer.Deferred() def filter_check(env,filter): for key in filter.keys(): if not getattr(env, key) in filter[key]: return False return True def _get_envs(filter,envsList): if filter: return [env for env in envsList if filter_check(env,filter)] else: return envsList d.addCallback(_get_envs,self._environments.values()) reactor.callLater(0,d.callback,filter) return d def get_environment(self,id, *args, **kwargs): if not id in self._environments.keys(): raise EnvironmentNotFound() else: #raise EnvironmentNotFound() #defer.succeed(self._environments[id]) return self._environments[id] def update_environment(self,id,name,description,status): environment = self._environments[id] environment.update(name,description,status) self._send_signal("environment_updated", environment) #return self.environments[id].update(name,description,status) @defer.inlineCallbacks def remove_environment(self,id=None,name=None): """ Remove an environment : this needs a whole set of checks, as it would delete an environment completely (very dangerous) Params: name: the name of the environment """ try: environment = self._environments[id] yield self._persistenceLayer.delete_environment(environment) #self.environments[envName].teardown() del self._environments[id] self._send_signal("environment_deleted", environment) log.msg("Removed environment ",environment.name, system="environment manager",logLevel=logging.CRITICAL) except Exception as inst: raise Exception("Failed to delete environment because of error %s" %str(inst)) # d = defer.Deferred() # def remove(id,envs): # try: # environment = envs[id] # yield self._persistenceLayer.delete_environment(environment) # envPath=os.path.join(FileManager.dataPath,environment._name) # #self.environments[envName].teardown() # del envs[id] # if os.path.isdir(envPath): # shutil.rmtree(envPath) # log.msg("Removed environment ",environment._name, system="environment manager",logLevel=logging.CRITICAL) # except: # raise Exception("Failed to delete environment") # #should raise specific exception # # d.addCallback(remove,self._environments) # reactor.callLater(0,d.callback,id) # return d @defer.inlineCallbacks def clear_environments(self): """ Removes & deletes ALL the environments, should be used with care """ for envId in self._environments.keys(): yield self.remove_environment(id = envId) self._send_signal("environments_cleared", self._environments) """
class Driver(object): """ Driver class: higher level handler of device connection that formats outgoing and incoming commands according to a spec before they get sent to the lower level connector. It actually mimics the way system device drivers work in a way. You can think of the events beeing sent out by the driver (dataRecieved etc) as interupts of sorts ConnectionModes : 0:setup 1:normal 2:setId 3:forced: to forcefully connect devices which have no deviceId stored Thoughts for future evolution each driver will have a series of endpoints or slots/hooks, which represent the actual subdevices it handles for example for reprap type devices, there is a "position" endpoint (abstract), 3 endpoints for the cartesian bot motors , at least an endpoint for head temperature , one for the heater etc or this could be in a hiearchy , reflecting the one off the nodes: variable endpoint : position, and sub ones for motors """ def __init__(self,deviceType="",connectionType="",hardware_interface_klass=None,logicHandlerKlass=None,protocol=None,options={},*args,**kwargs): self.driverType=self.__class__.__name__.lower() self.deviceType=deviceType self.connectionType=connectionType self.extra_params=options self.protocol=protocol self.hardware_interface_klass=hardware_interface_klass self.logicHandlerKlass=logicHandlerKlass self.deviceId=None """will be needed to identify a specific device, as the system does not work base on ports""" self._signal_dispatcher=None self.signal_channel_prefix="" self._signal_channel="" self.isConfigured=False#when the port association has not been set self.is_handshake_ok=False self.is_authentification_ok=False self.isConnected=False self.isPluggedIn=False self.autoConnect=False#if autoconnect is set to true, the device will be connected as soon as a it is plugged in and detected self.connectionErrors=0 self.maxConnectionErrors=2 self.connectionTimeout=4 self.connectionMode=1 self.deferred=defer.Deferred() """just for future reference : this is not implemented but would be a declarative way to define the different "configuration steps" of this driver" *basically a dictonary with keys beeing the connection modes, and values a list of strings representing the methods to call *would require a "validator" of sorts (certain elements need to be mandatory : such as the validation/setting of device ids """ configSteps={} configSteps[0]=["_handle_deviceHandshake","_handle_deviceIdInit"] configSteps[1]=["_handle_deviceHandshake","_handle_deviceIdInit","some_other_method"] #just a test self._signal_dispatcher=SignalDispatcher("driver_manager") """for exposing capabilites""" self.endpoints=[] def afterInit(self): """this is a workaround needed when loading a driver from db""" try: if not isinstance(self.extra_params,dict): self.extra_params=ast.literal_eval(self.extra_params) except Exception as inst: log.msg("Failed to load driver extra_params from db:",inst,system="Driver",logLevel=logging.CRITICAL) @defer.inlineCallbacks def setup(self,*args,**kwargs): self.hardwareHandler=self.hardware_interface_klass(self,self.protocol,**self.extra_params) self.logicHandler=self.logicHandlerKlass(self,**self.extra_params) node= (yield self.node.get()) env= (yield node.environment.get()) self.signal_channel_prefix="environment_"+str(env.id)+".node_"+str(node.id) self._signal_dispatcher.add_handler(handler=self.send_command,signal="addCommand") log.msg("Driver of type",self.driverType ,"setup sucessfully",system="Driver",logLevel=logging.INFO) def bind(self,port,setId=True): self.deferred=defer.Deferred() log.msg("Attemtping to bind driver",self ,"with deviceId:",self.deviceId,"to port",port,system="Driver",logLevel=logging.DEBUG) self.hardwareHandler.connect(setIdMode=setId,port=port) return self.deferred def connect(self,mode=None,*args,**kwargs): if not self.isConnected: if mode is not None: self.connectionMode=mode log.msg("Connecting in mode:",self.connectionMode,system="Driver",logLevel=logging.CRITICAL) if mode==3: """special case for forced connection""" unboundPorts=DriverManager.bindings.get_unbound_ports() if len(unboundPorts)>0: port=unboundPorts[0] log.msg("Connecting in mode:",self.connectionMode,"to port",port,system="Driver",logLevel=logging.CRITICAL) DriverManager.bindings.bind(self,port) self.pluggedIn(port) self.hardwareHandler.connect(port=port) else: self.hardwareHandler.connect() else: self.hardwareHandler.connect() def reconnect(self,*args,**kwargs): self.hardwareHandler.reconnect(*args,**kwargs) def disconnect(self,*args,**kwargs): self.hardwareHandler.disconnect(*args,**kwargs) def pluggedIn(self,port): self._send_signal("plugged_In",port) self.isPluggedIn=True if self.autoConnect: #slight delay, to prevent certain problems when trying to send data to the device too fast reactor.callLater(1,self.connect,1) def pluggedOut(self,port): self.isConfigured=False self.is_handshake_ok=False self.is_authentification_ok=False self.isConnected=False self.isPluggedIn=False self._send_signal("plugged_Out",port) #self._signal_dispatcher.send_message("pluggedOut",{"data":port}) def _send_signal(self,signal="",data=None): prefix=self.signal_channel_prefix+".driver." self._signal_dispatcher.send_message(prefix+signal,self,data) def send_command(self,data,sender=None,callback=None,*args,**kwargs): # print("going to send command",data,"from",sender) if not self.isConnected: raise DeviceNotConnected() if self.logicHandler: self.logicHandler._handle_request(data=data,sender=sender,callback=callback) def _send_data(self,data,*arrgs,**kwargs): self.hardwareHandler.send_data(data) def _handle_response(self,data): if self.logicHandler: self.logicHandler._handle_response(data) """higher level methods""" def startup(self): pass def shutdown(self): pass def init(self): pass def get_firmware_version(self): pass def set_debug_level(self,level): pass def teststuff(self,params,*args,**kwargs): pass def variable_set(self,variable,params,sender=None,*args,**kwargs): pass def variable_get(self,variable,params,sender=None,*args,**kwargs): pass """ #################################################################################### Experimental """ def start_command(self): pass def close_command(self): pass def get_endpoint(self,filter=None): """return a list of endpoints, filtered by parameters""" d=defer.Deferred() def filter_check(endpoint,filter): for key in filter.keys(): if not getattr(endpoint, key) in filter[key]: return False return True def get(filter): if filter: return [endpoint for endpoint in self.endpoints if filter_check(endpoint,filter)] else: pass d.addCallback(get) reactor.callLater(0.5,d.callback,filter) return d
class Driver(BaseComponent): """ Driver class: higher level handler of device connection that formats outgoing and incoming commands according to a spec before they get sent to the lower level connector. It actually mimics the way system device drivers work in a way. You can think of the events beeing sent out by the driver(dataRecieved...) as interupts of sorts """ def __init__(self, hardware_id=None, auto_connect=False, max_connection_errors=2, connection_timeout=4, do_hanshake=False, do_authentification=False): """ autoconnect:if autoconnect is True,device will be connected as soon as it is plugged in and detected max_connection_errors: the number of connection errors above which the driver gets disconnected connection_timeout: the number of seconds after which the driver gets disconnected (only in the initial , configuration phases by default) """ BaseComponent.__init__(self, parent=None) self.auto_connect = auto_connect self.max_connection_errors = max_connection_errors self.connection_timeout = connection_timeout self.do_authentification = do_authentification self.do_handshake = do_hanshake self._hardware_interface = None self.hardware_id = hardware_id self.is_configured = False self.is_bound = False # when port association has not been set self.is_handshake_ok = False self.is_authentification_ok = False self.is_connected = False self.is_bound = False self.is_busy = False self.errors = [] self.connection_mode = 1 self._connection_errors = 0 self._connection_timeout = None self.deferred = defer.Deferred() self._signal_channel_prefix = "" self._signal_dispatcher = SignalDispatcher("driver_manager") def __eq__(self, other): return self.__class__ == other.__class__ def __ne__(self, other): return not self.__eq__(other) def setup(self, *args, **kwargs): """do the driver setup phase""" self._signal_dispatcher.add_handler(handler=self.send_command, signal="addCommand") class_name = self.__class__.__name__.lower() log.msg("Driver of type", class_name, "setup sucessfully", system="Driver", logLevel=logging.INFO) def _send_signal(self, signal="", data=None): prefix = self._signal_channel_prefix + ".driver." self._signal_dispatcher.send_message(prefix + signal, self, data) @property def hardware_interface_class(self): """Get the current voltage.""" return self._hardware_interface.__class__ @property def connection_errors(self): """Get the current voltage.""" return self._connection_errors @connection_errors.setter def connection_errors(self, value): self._connection_errors = value if self._connection_errors >= self.max_connection_errors: self.disconnect(clear_port=True) log.msg( "cricital error while (re-)starting serial connection : please check your driver settings and device id, as well as cables, and make sure no other process is using the port ", system="Driver", logLevel=logging.CRITICAL) self.deferred.errback(self.errors[-1]) #self._hardware_interface._connect() #weird way of handling disconnect """ ########################################################################### The following are the timeout related methods """ def set_connection_timeout(self): """sets internal timeout""" if self.connection_timeout > 0: log.msg("Setting timeout at ", time.time(), system="Driver", logLevel=logging.DEBUG) self._connection_timeout = reactor.callLater( self.connection_timeout, self._connection_timeout_check) def cancel_connection_timeout(self): """cancels internal timeout""" if self._connection_timeout is not None: try: self._connection_timeout.cancel() log.msg("Canceling timeout at ", time.time(), system="Driver", logLevel=logging.DEBUG) except: pass def _connection_timeout_check(self): """checks the timeout""" log.msg("Timeout check at ", time.time(), logLevel=logging.DEBUG) self.cancel_connection_timeout() self.errors.append(TimeoutError()) self.connection_errors += 1 if self.connection_errors < self.max_connection_errors: self.reconnect() """ ########################################################################### The following are the connection related methods """ def connect(self, port=None, connection_mode=None): """ connection_mode : 0:setup 1:normal 2:forced: to forcefully connect devices which have no deviceId stored """ if self.is_connected: raise Exception("Driver already connected") if connection_mode is None: raise Exception("Invalid connection mode") self.deferred = defer.Deferred() self.connection_mode = connection_mode self.errors = [] self._connection_errors = 0 self.is_busy = True mode_str = "Normal" if self.connection_mode == 1: mode_str = "Setup" log.msg("Connecting driver in %s mode:" % mode_str, system="Driver", logLevel=logging.CRITICAL) reactor.callLater(0.1, self._hardware_interface.connect, port) return self.deferred def reconnect(self, *args, **kwargs): """Reconnect driver""" self._hardware_interface.reconnect(*args, **kwargs) def disconnect(self, *args, **kwargs): """Disconnect driver""" log.msg("Disconnecting driver", system="Driver", logLevel=logging.CRITICAL) self.is_connected = False self.is_busy = False self.cancel_connection_timeout() self._hardware_interface.disconnect(*args, **kwargs) """ ########################################################################### The following are the methods dealing with communication with the hardware """ def send_command(self, command): """send a command to the physical device""" if not self.is_connected: raise DeviceNotConnected() self.command_deferred = defer.Deferred() reactor.callLater(0.01, self._hardware_interface.send_data, command) return self.command_deferred def _handle_response(self, data): """handle hardware response""" self.command_deferred.callback(data) """ ########################################################################### The following are the higher level methods """ def startup(self): """send startup command to hardware""" raise NotImplementedError() def shutdown(self): """send shutdown command to hardware""" raise NotImplementedError() def get_firmware_info(self): """retrieve firmware version from hardware""" raise NotImplementedError() def set_debug_level(self, level): """set hardware debug level, if any""" raise NotImplementedError()
class Driver(BaseComponent): """ Driver class: higher level handler of device connection that formats outgoing and incoming commands according to a spec before they get sent to the lower level connector. It actually mimics the way system device drivers work in a way. You can think of the events beeing sent out by the driver(dataRecieved...) as interupts of sorts """ def __init__(self, hardware_id=None, auto_connect=False, max_connection_errors=2, connection_timeout=4, do_hanshake=False, do_authentification=False): """ autoconnect:if autoconnect is True,device will be connected as soon as it is plugged in and detected max_connection_errors: the number of connection errors above which the driver gets disconnected connection_timeout: the number of seconds after which the driver gets disconnected (only in the initial , configuration phases by default) """ BaseComponent.__init__(self, parent=None) self.auto_connect = auto_connect self.max_connection_errors = max_connection_errors self.connection_timeout = connection_timeout self.do_authentification = do_authentification self.do_handshake = do_hanshake self._hardware_interface = None self.hardware_id = hardware_id self.is_configured = False self.is_bound = False # when port association has not been set self.is_handshake_ok = False self.is_authentification_ok = False self.is_connected = False self.is_bound = False self.is_busy = False self.errors = [] self.connection_mode = 1 self._connection_errors = 0 self._connection_timeout = None self.deferred = defer.Deferred() self._signal_channel_prefix = "" self._signal_dispatcher = SignalDispatcher("driver_manager") def __eq__(self, other): return self.__class__ == other.__class__ def __ne__(self, other): return not self.__eq__(other) def setup(self, *args, **kwargs): """do the driver setup phase""" self._signal_dispatcher.add_handler(handler=self.send_command, signal="addCommand") class_name = self.__class__.__name__.lower() log.msg("Driver of type", class_name, "setup sucessfully", system="Driver", logLevel=logging.INFO) def _send_signal(self, signal="", data=None): prefix = self._signal_channel_prefix + ".driver." self._signal_dispatcher.send_message(prefix + signal, self, data) @property def hardware_interface_class(self): """Get the current voltage.""" return self._hardware_interface.__class__ @property def connection_errors(self): """Get the current voltage.""" return self._connection_errors @connection_errors.setter def connection_errors(self, value): self._connection_errors = value if self._connection_errors >= self.max_connection_errors: self.disconnect(clear_port=True) log.msg("cricital error while (re-)starting serial connection : please check your driver settings and device id, as well as cables, and make sure no other process is using the port ", system="Driver", logLevel=logging.CRITICAL) self.deferred.errback(self.errors[-1]) #self._hardware_interface._connect() #weird way of handling disconnect """ ########################################################################### The following are the timeout related methods """ def set_connection_timeout(self): """sets internal timeout""" if self.connection_timeout > 0: log.msg("Setting timeout at ", time.time(), system="Driver", logLevel=logging.DEBUG) self._connection_timeout = reactor.callLater(self.connection_timeout, self._connection_timeout_check) def cancel_connection_timeout(self): """cancels internal timeout""" if self._connection_timeout is not None: try: self._connection_timeout.cancel() log.msg("Canceling timeout at ", time.time(), system="Driver", logLevel=logging.DEBUG) except: pass def _connection_timeout_check(self): """checks the timeout""" log.msg("Timeout check at ", time.time(), logLevel=logging.DEBUG) self.cancel_connection_timeout() self.errors.append(TimeoutError()) self.connection_errors += 1 if self.connection_errors < self.max_connection_errors: self.reconnect() """ ########################################################################### The following are the connection related methods """ def connect(self, port=None, connection_mode=None): """ connection_mode : 0:setup 1:normal 2:forced: to forcefully connect devices which have no deviceId stored """ if self.is_connected: raise Exception("Driver already connected") if connection_mode is None: raise Exception("Invalid connection mode") self.deferred = defer.Deferred() self.connection_mode = connection_mode self.errors = [] self._connection_errors = 0 self.is_busy = True mode_str = "Normal" if self.connection_mode == 1: mode_str = "Setup" log.msg("Connecting driver in %s mode:" % mode_str, system="Driver", logLevel=logging.CRITICAL) reactor.callLater(0.1, self._hardware_interface.connect, port) return self.deferred def reconnect(self, *args, **kwargs): """Reconnect driver""" self._hardware_interface.reconnect(*args, **kwargs) def disconnect(self, *args, **kwargs): """Disconnect driver""" log.msg("Disconnecting driver", system="Driver", logLevel=logging.CRITICAL) self.is_connected = False self.is_busy = False self.cancel_connection_timeout() self._hardware_interface.disconnect(*args, **kwargs) """ ########################################################################### The following are the methods dealing with communication with the hardware """ def send_command(self, command): """send a command to the physical device""" if not self.is_connected: raise DeviceNotConnected() self.command_deferred = defer.Deferred() reactor.callLater(0.01, self._hardware_interface.send_data, command) return self.command_deferred def _handle_response(self, data): """handle hardware response""" self.command_deferred.callback(data) """ ########################################################################### The following are the higher level methods """ def startup(self): """send startup command to hardware""" raise NotImplementedError() def shutdown(self): """send shutdown command to hardware""" raise NotImplementedError() def get_firmware_info(self): """retrieve firmware version from hardware""" raise NotImplementedError() def set_debug_level(self, level): """set hardware debug level, if any""" raise NotImplementedError()
class PackageManager(object): """ Class for managing updates/addons: works as a container, a handler and a central management point for the list of available and installed addons/updates """ def __init__(self, pathManager=None, packageCheckEnabled=False, packageCheckFrequency=240): self._path_manager = pathManager self._signal_channel = "package_manager" self._signal_dispatcher = SignalDispatcher(self._signal_channel) self.signal_channel_prefix = "package_manager" pollapli_package = Package(name="Pollapli", description="core package", version="0.5.0", installed=True) pollapli_package.cid = uuid.UUID( "23b0d813-a6b2-461a-88ad-a7020ae67742") self._installed_packages = {pollapli_package.cid: pollapli_package} self._available_packages = {} self.package_check_frequency = packageCheckFrequency self.package_check_enabled = packageCheckEnabled self.package_list_url = "http://kaosat.net/pollapli/pollapli_packages.json" self._package_check = task.LoopingCall(self.refresh_packageList) self._addon_path = "." self.updates_path = "." self._max_download_attempts = 5 self._downloader = DownloaderWithProgress() @defer.inlineCallbacks def setup(self): """initial configuration method : * first it checks for locally copied addons and extracts/installs them * then tries to fetch the list of available packages from the update server * if any new (newer version number or never seen before) update is available, it adds it to the dictionary of available updates, but it does NOT download or install those updates """ if self.package_check_enabled: self._package_check.start(interval=self.package_check_frequency, now=False) yield self._list_localPackages() # updates = yield self._persistenceLayer.load_updates() # for update in updates: # self._updates[update.name]=update log.msg("Package Manager setup succesfully ", system="Package Manager", logLevel=logging.INFO) def tearDown(self): pass def _send_signal(self, signal="", data=None): prefix = "%s." % self.signal_channel_prefix self._signal_dispatcher.send_message(prefix + signal, self, data) def enable_package_checking(self): if not self.package_check_enabled: self._package_check.start(interval=self.package_check_frequency, now=False) self.package_check_enabled = True def disable_package_checking(self): if self.package_check_enabled: self._package_check.stop() self.package_check_enabled = False """ #################################################################################### The following are the "CRUD" (Create, read, update,delete) methods for the general handling of updates/addons """ def get_package(self, id=None): package = self._installed_packages.get(id) if package is None: raise UnknownPackage() return package def get_packages(self, filters=None): """ Returns the list of packages, filtered by the filter param the filter is a dictionary of list, with each key beeing an attribute to check, and the values in the list , values of that param to check against """ deferred = defer.Deferred() def filter_check(package, filters): for key in filters.keys(): if not getattr(package, key) in filters[key]: return False return True @defer.inlineCallbacks def _get_packages(filters, package_list): yield self.refresh_packageList() if filter: defer.returnValue([ package for package in package_list if filter_check(package, filters) ]) else: defer.returnValue(package_list) deferred.addCallback(_get_packages, self._installed_packages.values()) reactor.callLater(0.5, deferred.callback, filters) return deferred def delete_package(self, package_id): """ Remove an package : this needs a whole set of checks, as it would delete an package completely Params: id: the id of the package """ deferred = defer.Deferred() def remove(package_id, packages): package = packages[id] if package.type == "package": raise Exception("only addons can be removed") packages[id].delete() del packages[id] log.msg("Removed addOn ", package.name, "with id ", package_id, logLevel=logging.CRITICAL) deferred.addCallback(remove, self._installed_packages) reactor.callLater(0, deferred.callback, id) return deferred @defer.inlineCallbacks def clear_packages(self): """ Removes & deletes ALL the packages that are addons "simple" packages cannot be deleted This should be used with care,as well as checks on client side """ for package_id in self._installed_packages.keys(): yield self.delete_package(package_id=package_id) @defer.inlineCallbacks def get_plugins(self, interface=None, addOnName=None): """ find a specific plugin in the list of available addOns, by interface and/or addOn """ plugins = [] @defer.inlineCallbacks def scan(path): plugins = [] try: addonpackages = pkgutil.walk_packages(path=[path], prefix='') for loader, name, isPkg in addonpackages: mod = pkgutil.get_loader(name).load_module(name) try: plugins.extend((yield getPlugins(interface, mod))) except Exception as inst: log.msg("error in fetching plugin: ", str(inst), system="Package Manager", logLevel=logging.CRITICAL) except Exception as inst: log.msg("error %s in listing packages in path : %s " % (str(inst), path), system="Package Manager", logLevel=logging.CRITICAL) defer.returnValue(plugins) for addOn in self._installed_packages.itervalues(): if addOn.type == "addon": if addOnName: if addOn.name == addOnName and addOn.enabled: plugins.extend((yield scan(addOn.installPath))) else: if addOn.enabled: plugins.extend((yield scan(addOn.installPath))) log.msg("Got plugins: ", str(plugins), system="Package Manager", logLevel=logging.DEBUG) defer.returnValue(plugins) def enable_addon(self, id=None): package = self._installed_packages.get(id) if package is None: raise UnknownPackage() package.enabled = True def disable_addon(self, id=None): package = self._installed_packages.get(id) if package is None: raise UnknownPackage() package.enabled = False """ #################################################################################### Package download and installation methods """ @defer.inlineCallbacks def refresh_packageList(self): """Fetches the remote package list, downloads it, and if there were any changes, packages the in memory package list accordingly """ log.msg("checking for new packages : time", time.time(), logLevel=logging.CRITICAL) packageInfoPath = os.path.join(self._path_manager.tmpPath, "packages.txt") try: yield DownloaderWithProgress.download(url=self.package_list_url, destination=packageInfoPath) self._parse_packageListFile() except Exception as inst: log.msg("Failed to download package master list: error:", inst, system="Package Manager", logLevel=logging.CRITICAL) @defer.inlineCallbacks def setup_package(self, id): try: yield self._downloadPackage(id) yield self.installPackage(id) except Exception as inst: log.msg("Failed to setup package ", id, "because of error", inst, system="Package Manager", logLevel=logging.CRITICAL) @defer.inlineCallbacks def _download_package(self, id): """downloads the specified package, if the download was successfull, it checks the package's md5 checksum versus the one stored in the package info for integretiy, and if succefull, dispatches a success message """ package = self._available_packages.get(id) if package is None: raise PackageNotFound() try: downloadPath = os.path.join(self._path_manager.tmpPath, package.file) yield DownloaderWithProgress.download(url=package.downloadUrl, destination=downloadPath, object=package, refChecksum=package.fileHash) package.downloaded = True #update.installPath=self._addon_path self._send_signal("package_download_succeeded", package) log.msg("Successfully downloaded package ", package.name, system="Package Manager", logLevel=logging.DEBUG) except Exception as inst: self._send_signal("package_download_failed", package) log.msg("Failed to download package", package.name, " error:", inst, system="Package Manager", logLevel=logging.CRITICAL) @defer.inlineCallbacks def install_package(self, id): """installs the specified package""" package = self._available_packages.get(id) if package is None: raise PackageNotFound() if not package.downloaded: raise Exception( "Cannot install package that was not downloaded first") """Steps: extract package in tmp folder create list of files it is going to replace backup those files to a "rollback" directory copy the new files to the correct place restart the whole system check if all starts well if not, delete new files, copy back rollback files, and restart again if yes all done """ baseName = os.path.splitext(package.file)[0] baseName = baseName.replace('-', '_').replace('.', '_') tmpPath = os.path.join(self._path_manager.tmpPath, baseName) sourcePath = os.path.join(self._path_manager.tmpPath, package.file) if package.type == "addon": try: destinationPath = os.path.join(self._path_manager._addon_path, baseName) extractedPackageDir = yield self._extract_package( sourcePath, tmpPath) extractedPackageDir = os.path.join(tmpPath, extractedPackageDir) shutil.copytree(extractedPackageDir, destinationPath) shutil.rmtree(tmpPath) os.remove(sourcePath) self._installed_packages[package.cid] = package package.installPath = os.path.join( self._path_manager._addon_path, os.path.basename(extractedPackageDir)) package.enabled = True self.add_package_toPythonPath(package.installPath) self._send_signal("addon_install_succeeded", package) except Exception as inst: raise Exception("Failed to install addOn: error %s" % (str(inst))) elif package.type == "update": #TODO: add restart of system try: packageToUpdate = self._installed_packages.get( package.targetId) if packageToUpdate is None: raise Exception( "Attempting to update not installed package, aborting") destinationPath = packageToUpdate.installPath extractedPackagePath = yield self._extract_package( sourcePath, tmpPath) extractedPackagePath = os.path.join(tmpPath, extractedPackagePath) # yield self._backup_files(".") dirCompare = filecmp.dircmp(destinationPath, extractedPackagePath, hide=[]) filesToBackup = dirCompare.common backupFolderName = os.path.splitext(packageToUpdate.file)[0] backupFolderName = backupFolderName.replace('-', '_').replace( '.', '_').replace(' ', '') + "_back" backupFolderName = os.path.join(self._path_manager.tmpPath, backupFolderName) #TODO: add id at end ? os.makedirs(backupFolderName) for fileDir in filesToBackup: fileDir = os.path.join(destinationPath, fileDir) if os.path.isdir(fileDir): dir_util.copy_tree(fileDir, backupFolderName) else: file_util.copy_file(fileDir, backupFolderName) dir_util.copy_tree(extractedPackagePath, destinationPath) shutil.rmtree(backupFolderName) packageToUpdate.version = package.toVersion self._send_signal("update_install_succeeded", package) except Exception as inst: raise Exception("Failed to install update: error %s" % (str(inst))) else: raise Exception("Unknown package type") """ #################################################################################### Helper Methods """ def _list_localPackages(self): """ Scans the packages path for addons etc , and adds them to the list of currently available packages """ #TODO : also parse info for main (pollapli) package packageDirs = os.listdir(self._path_manager._addon_path) for fileDir in packageDirs: if zipfile.is_zipfile(fileDir): sourcePath = fileDir baseName = os.path.splitext(sourcePath)[0] baseName = baseName.replace('-', '_').replace('.', '_') tmpPath = os.path.join(self._path_manager.tmpPath, baseName) destinationPath = os.path.join(self._path_manager._addon_path, baseName) extractedPackageDir = yield self._extract_package( sourcePath, tmpPath) extractedPackageDir = os.path.join(sourcePath, extractedPackageDir) shutil.copytree(extractedPackageDir, destinationPath) shutil.rmtree(tmpPath) os.remove(sourcePath) fileDir = destinationPath self.add_package_toPythonPath(fileDir) def _parse_packageListFile(self): packagesFileName = os.path.join(self._path_manager.tmpPath, "pollapli_packages.json") packageFile = file(packagesFileName, "r") packageInfos = json.loads(packageFile.read(), "iso-8859-1") packageFile.close() packageList = [ Package.from_dict(package) for package in packageInfos["packages"] ] newPackageCount = 0 for package in packageList: if package.type == "update": if package.targetId in self._installed_packages.keys(): if package.fromVersion == self._installed_packages[ package.targetId].version: if self._available_packages.get(package.cid, None) is None: self._available_packages[package.cid] = package newPackageCount += 1 elif package.type == "addon": if package.cid not in self._installed_packages.keys(): if self._available_packages.get(package.cid, None) is None: self._available_packages[package.cid] = package newPackageCount += 1 if newPackageCount > 0: self._send_signal("new_packages_available", newPackageCount) def _backup_packageFiles(self, rootPath): d = defer.Deferred() return d def _extract_package(self, sourcePath, destinationPath): d = defer.Deferred() def extract(sourcePath, destinationPath): if not zipfile.is_zipfile(sourcePath): raise Exception("invalid package extension") packageFile = ZipFile(sourcePath, 'r') packageFile.extractall(path=destinationPath) packageFile.close() return os.listdir(destinationPath)[0] d.addCallback(extract, destinationPath=destinationPath) reactor.callLater(0, d.callback, sourcePath) return d @defer.inlineCallbacks def _extract_allPackages(self): """ helper "hack" function to extract egg/zip files into their adapted directories """ #TODO: fix this packageDirs = os.listdir(self._path_manager._addon_path) for fileDir in packageDirs: if zipfile.is_zipfile(fileDir): yield self._extract_package(fileDir) if os.path.isdir(fileDir): self.add_package_toPythonPath(fileDir) #should perhaps be like this: #if it is a zip file, use the install_package method #if it is a directory, check if is in the path, and if not , add it to the path def add_package_toPythonPath(self, path): if os.path.isdir(path): if not path in sys.path: sys.path.insert(0, path) @defer.inlineCallbacks def update_addOns(self): """ wrapper method, for extraction+ list package in case of newly installed addons """ yield self.extract_addons() yield self.list_addons() def save_package_list(self): for package in self._installed_packages: import json json.dumps(package)