def test_listener_removal(): """Removing listeners removes the correct listener from an event.""" ee = EventEmitter() # Some functions to pass to the EE def first(): return 1 ee.on('event', first) @ee.on('event') def second(): return 2 @ee.on('event') def third(): return 3 def fourth(): return 4 ee.on('event', fourth) assert ee._events['event'] == [first, second, third, fourth] ee.remove_listener('event', second) assert ee._events['event'] == [first, third, fourth] ee.remove_listener('event', first) assert ee._events['event'] == [third, fourth] ee.remove_all_listeners('event') assert ee._events['event'] == []
class MockWebsocket(): def __init__(self): self.events = EventEmitter(scheduler=asyncio.ensure_future) self.saved_items = [] self.emitted_items = [] def on(self, *args, **kwargs): self.events.on(*args, **kwargs) def once(self, *args, **kwargs): self.events.once(*args, **kwargs) def _emit(self, event, *args, **kwargs): # save published items for testing self.emitted_items += [{ 'time': int(round(time.time() * 1000)), 'data': { 'event': event, 'args': args, 'kwargs': kwargs } }] self.events.emit(event, *args, **kwargs) def remove_all_listeners(self, *args, **kwargs): self.events.remove_all_listeners(*args, **kwargs) def cancel_order(self, *args, **kawargs): pass def submit_order(self, *args, **kawargs): pass def get_emitted_items(self): return self.emitted_items def get_last_emitted_item(self): return self.emitted_items[-1:][0] def get_emitted_items_count(self): return len(self.emitted_items)
class Button(Component): """A buttons device abstraction component base class.""" def __init__(self): """Initialize a new Button.""" super(Component, self).__init__() self.__holdTimer = None self.__baseState = button_state.RELEASED self.__emitter = EventEmitter() @property def is_pressed(self): """Get a value indicating whether or not the buttons is pressed. :returns: True if the buttons is pressed; Otherwise, False. :rtype: bool """ return self.__baseState == button_state.PRESSED @property def is_released(self): """Get a value indicating whether or not the buttons is released. :returns: True if the buttons is released; Otherwise, False. :rtype: bool """ return self.__baseState == button_state.RELEASED def _set_state(self, state): """Override state with the specified state. :param int state: The state to set. """ self.__baseState = state @property def state(self): """Get the buttons state. :returns: The buttons state. :rtype: int """ return self.__baseState def is_state(self, state): """Check to see if the buttons is in the specified state. :param int state: The state to check. :returns: True if the buttons is in the specified state. :rtype: bool """ return self.__baseState == state def on(self, evt, callback): """Register an event with a callback to handle it. :param str evt: The name of the event to register a handler for. :param function callback: The callback to execute when the event fires. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Button") self.__emitter.on(evt, callback) def emit(self, evt, args): """Emit the specified event to all registered listeners. :param str evt: The name of the event to emit. :param object args: The arguments to pass to the event handlers (listeners). :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Button") self.__emitter.emit(evt, args) def on_button_hold(self, btn_event): """Fire the buttons hold event. :param raspy.components.buttons.button_event.ButtonEvent btn_event: The buttons event info. """ if self.is_disposed: raise ObjectDisposedException("Button") self.emit(EVENT_HOLD, btn_event) def _on_hold_timer_elapsed(self, btn_event): """Handle the timer elapsed event. This internal method fires the button hold event when the hold timer elapses. Should only be called by _fire_button_hold_event(). :param raspy.components.buttons.button_event.ButtonEvent btn_event: The button event info. """ if self.is_pressed: self.on_button_hold(btn_event) def _stop_hold_timer(self): """Stop the internal hold timer.""" if self.__holdTimer is not None: self.__holdTimer.cancel() self.__holdTimer = None def _fire_button_hold_event(self): """Fire the timer elapsed event handler.""" evt = ButtonEvent(self) self._on_hold_timer_elapsed(evt) def _start_hold_timer(self): """Start the button hold timer.""" self.__holdTimer = Timer(2.0, self._fire_button_hold_event) self.__holdTimer.start() def on_state_changed(self, btn_event): """Fire the buttons state changed event. :param raspy.components.buttons.button_event.ButtonEvent btn_event: The buttons event info. """ if self.is_disposed: raise ObjectDisposedException("Button") self.emit(EVENT_STATE_CHANGED, btn_event) if btn_event.is_pressed: self.on_button_pressed(btn_event) if btn_event.is_released: self.on_button_released(btn_event) def on_button_pressed(self, btn_event): """Fire the buttons pressed event. :param raspy.components.buttons.button_event.ButtonEvent btn_event: The buttons event info. """ if self.is_disposed: raise ObjectDisposedException("Button") self.emit(EVENT_PRESSED, btn_event) self._stop_hold_timer() self._start_hold_timer() def on_button_released(self, btn_event): """Fire the buttons released event. :param raspy.components.buttons.button_event.ButtonEvent btn_event: The buttons event info. """ if self.is_disposed: raise ObjectDisposedException("Button") self.emit(EVENT_RELEASED, btn_event) self._stop_hold_timer() def __str__(self): """Return the string representation of this class instance. :returns: The string representation of this class. :rtype: str """ base = Component.component_name.fget() if not string_utils.is_null_or_empty(base): return base return self.__class__.__name__ def dispose(self): """Dispose of all the managed resources used by this instance.""" if self.is_disposed: return self.__emitter.remove_all_listeners() self._stop_hold_timer() self.__emitter = None Component.dispose(self)
class GenericWebsocket: """ Websocket object used to contain the base functionality of a websocket. Inlcudes an event emitter and a standard websocket client. """ def __init__(self, host, logLevel='INFO', loop=None, max_retries=5, create_event_emitter=_start_event_worker): self.host = host self.logger = CustomLogger('BfxWebsocket', logLevel=logLevel) self.loop = loop or asyncio.get_event_loop() # overide 'error' event to stop it raising an exception # self.events.on('error', self.on_error) self.ws = None self.max_retries = max_retries self.attempt_retry = True self.sockets = {} # start seperate process for the even emitter eventLoop = create_event_emitter() self.events = EventEmitter(scheduler=asyncio.ensure_future, loop=eventLoop) def run(self): """ Starte the websocket connection. This functions spawns the initial socket thread and connection. """ self._start_new_socket() def get_task_executable(self): """ Get the run indefinitely asyncio task """ return self._run_socket() def _start_new_socket(self, socketId=None): if not socketId: socketId = len(self.sockets) def start_loop(loop): asyncio.set_event_loop(loop) loop.run_until_complete(self._run_socket()) worker_loop = asyncio.new_event_loop() worker = Thread(target=start_loop, args=(worker_loop, )) worker.start() return socketId def _wait_for_socket(self, socket_id): """ Block until the given socket connection is open """ while True: socket = self.sockets.get(socket_id, False) if socket: if socket.isConnected and socket.ws: return time.sleep(0.01) async def _connect(self, socket): async with websockets.connect(self.host) as websocket: self.sockets[socket.id].set_websocket(websocket) self.sockets[socket.id].set_connected() self.logger.info("Wesocket connected to {}".format(self.host)) while True: await asyncio.sleep(0) message = await websocket.recv() await self.on_message(socket.id, message) def get_socket(self, socketId): return self.sockets[socketId] def get_authenticated_socket(self): for socketId in self.sockets: if self.sockets[socketId].isAuthenticated: return self.sockets[socketId] return None async def _run_socket(self): retries = 0 sId = len(self.sockets) s = Socket(sId) self.sockets[sId] = s while retries < self.max_retries and self.attempt_retry: try: await self._connect(s) retries = 0 except (ConnectionClosed, socket.error) as e: self.sockets[sId].set_disconnected() self._emit('disconnected') if (not self.attempt_retry): return self.logger.error(str(e)) retries += 1 # wait 5 seconds befor retrying self.logger.info("Waiting 5 seconds before retrying...") await asyncio.sleep(5) self.logger.info("Reconnect attempt {}/{}".format( retries, self.max_retries)) self.logger.info("Unable to connect to websocket.") self._emit('stopped') def remove_all_listeners(self, event): """ Remove all listeners from event emitter """ self.events.remove_all_listeners(event) def on(self, event, func=None): """ Add a new event to the event emitter """ if not func: return self.events.on(event) self.events.on(event, func) def once(self, event, func=None): """ Add a new event to only fire once to the event emitter """ if not func: return self.events.once(event) self.events.once(event, func) def _emit(self, event, *args, **kwargs): self.events.emit(event, *args, **kwargs) async def on_error(self, error): """ On websocket error print and fire event """ self.logger.error(error) async def on_close(self): """ On websocket close print and fire event. This is used by the data server. """ self.logger.info("Websocket closed.") self.attempt_retry = False await self.ws.close() self._emit('done') async def on_open(self): """ On websocket open """ pass async def on_message(self, message): """ On websocket message """ pass
class WebsocketClient(object): def __init__(self, host=None, port=None, route=None, ssl=None): config = Configuration.get().get("websocket") host = host or config.get("host") port = port or config.get("port") route = route or config.get("route") ssl = ssl or config.get("ssl") validate_param(host, "websocket.host") validate_param(port, "websocket.port") validate_param(route, "websocket.route") self.url = WebsocketClient.build_url(host, port, route, ssl) self.emitter = EventEmitter() self.client = self.create_client() self.pool = ThreadPool(10) self.retry = 5 @staticmethod def build_url(host, port, route, ssl): scheme = "wss" if ssl else "ws" return scheme + "://" + host + ":" + str(port) + route def create_client(self): return WebSocketApp(self.url, on_open=self.on_open, on_close=self.on_close, on_error=self.on_error, on_message=self.on_message) def on_open(self, ws): LOG.info("Connected") self.emitter.emit("open") # Restore reconnect timer to 5 seconds on sucessful connect self.retry = 5 def on_close(self, ws): self.emitter.emit("close") def on_error(self, ws, error): try: self.emitter.emit('error', error) self.client.close() except Exception as e: LOG.error(repr(e)) LOG.warning("WS Client will reconnect in %d seconds." % self.retry) time.sleep(self.retry) self.retry = min(self.retry * 2, 60) self.client = self.create_client() self.run_forever() def on_message(self, ws, message): self.emitter.emit('message', message) parsed_message = Message.deserialize(message) self.pool.apply_async(self.emitter.emit, (parsed_message.type, parsed_message)) def emit(self, message): if (not self.client or not self.client.sock or not self.client.sock.connected): return if hasattr(message, 'serialize'): self.client.send(message.serialize()) else: self.client.send(json.dumps(message.__dict__)) def on(self, event_name, func): self.emitter.on(event_name, func) def once(self, event_name, func): self.emitter.once(event_name, func) def remove(self, event_name, func): self.emitter.remove_listener(event_name, func) def remove_all_listeners(self, event_name): ''' Remove all listeners connected to event_name. Args: event_name: event from which to remove listeners ''' if event_name is None: raise ValueError self.emitter.remove_all_listeners(event_name) def run_forever(self): self.client.run_forever() def close(self): self.client.close()
class GenericWebsocket: """ Websocket object used to contain the base functionality of a websocket. Inlcudes an event emitter and a standard websocket client. """ def __init__(self, host, logLevel='INFO', loop=None): self.host = host self.logger = CustomLogger('BfxWebsocket', logLevel=logLevel) self.loop = loop or asyncio.get_event_loop() self.events = EventEmitter( scheduler=asyncio.ensure_future, loop=self.loop) self.ws = None def run(self): """ Run the websocket connection indefinitely """ self.loop.run_until_complete(self._main(self.host)) def get_task_executable(self): """ Get the run indefinitely asyncio task """ return self._main(self.host) async def _main(self, host): async with websockets.connect(host) as websocket: self.ws = websocket self.logger.info("Wesocket connectedt to {}".format(self.host)) while True: await asyncio.sleep(0) message = await websocket.recv() await self.on_message(message) def remove_all_listeners(self, event): """ Remove all listeners from event emitter """ self.events.remove_all_listeners(event) def on(self, event, func=None): """ Add a new event to the event emitter """ if not func: return self.events.on(event) self.events.on(event, func) def once(self, event, func=None): """ Add a new event to only fire once to the event emitter """ if not func: return self.events.once(event) self.events.once(event, func) def _emit(self, event, *args, **kwargs): self.events.emit(event, *args, **kwargs) async def on_error(self, error): """ On websocket error print and fire event """ self.logger.error(error) self.events.emit('error', error) async def on_close(self): """ On websocket close print and fire event """ self.logger.info("Websocket closed.") await self.ws.close() self._emit('done') async def on_open(self): """ On websocket open """ pass async def on_message(self, message): """ On websocket message """ pass
class WebsocketClient: def __init__(self, host=None, port=None, route=None, ssl=None): config = Configuration.get().get("websocket") host = host or config.get("host") port = port or config.get("port") route = route or config.get("route") ssl = ssl or config.get("ssl") validate_param(host, "websocket.host") validate_param(port, "websocket.port") validate_param(route, "websocket.route") self.url = WebsocketClient.build_url(host, port, route, ssl) self.emitter = EventEmitter() self.client = self.create_client() self.pool = ThreadPool(10) self.retry = 5 self.connected_event = Event() self.started_running = False @staticmethod def build_url(host, port, route, ssl): scheme = "wss" if ssl else "ws" return scheme + "://" + host + ":" + str(port) + route def create_client(self): return WebSocketApp(self.url, on_open=self.on_open, on_close=self.on_close, on_error=self.on_error, on_message=self.on_message) def on_open(self, ws): LOG.info("Connected") self.connected_event.set() self.emitter.emit("open") # Restore reconnect timer to 5 seconds on sucessful connect self.retry = 5 def on_close(self, ws): self.emitter.emit("close") def on_error(self, ws, error): """ On error start trying to reconnect to the websocket. """ if isinstance(error, WebSocketConnectionClosedException): LOG.warning('Could not send message because connection has closed') else: LOG.exception('=== ' + repr(error) + ' ===') try: self.emitter.emit('error', error) if self.client.keep_running: self.client.close() except Exception as e: LOG.error('Exception closing websocket: ' + repr(e)) LOG.warning("WS Client will reconnect in %d seconds." % self.retry) time.sleep(self.retry) self.retry = min(self.retry * 2, 60) try: self.emitter.emit('reconnecting') self.client = self.create_client() self.run_forever() except WebSocketException: pass def on_message(self, ws, message): self.emitter.emit('message', message) parsed_message = Message.deserialize(message) self.pool.apply_async( self.emitter.emit, (parsed_message.type, parsed_message)) def emit(self, message): if not self.connected_event.wait(10): if not self.started_running: raise ValueError('You must execute run_forever() ' 'before emitting messages') self.connected_event.wait() try: if hasattr(message, 'serialize'): self.client.send(message.serialize()) else: self.client.send(json.dumps(message.__dict__)) except WebSocketConnectionClosedException: LOG.warning('Could not send {} message because connection ' 'has been closed'.format(message.type)) def wait_for_response(self, message, reply_type=None, timeout=None): """Send a message and wait for a response. Args: message (Message): message to send reply_type (str): the message type of the expected reply. Defaults to "<message.type>.response". timeout: seconds to wait before timeout, defaults to 3 Returns: The received message or None if the response timed out """ response = [] def handler(message): """Receive response data.""" response.append(message) # Setup response handler self.once(reply_type or message.type + '.response', handler) # Send request self.emit(message) # Wait for response start_time = time.monotonic() while len(response) == 0: time.sleep(0.2) if time.monotonic() - start_time > (timeout or 3.0): try: self.remove(reply_type, handler) except (ValueError, KeyError): # ValueError occurs on pyee 1.0.1 removing handlers # registered with once. # KeyError may theoretically occur if the event occurs as # the handler is removed pass return None return response[0] def on(self, event_name, func): self.emitter.on(event_name, func) def once(self, event_name, func): self.emitter.once(event_name, func) def remove(self, event_name, func): try: if event_name in self.emitter._events: LOG.debug("Removing found '"+str(event_name)+"'") else: LOG.debug("Not able to find '"+str(event_name)+"'") self.emitter.remove_listener(event_name, func) except ValueError as e: LOG.warning('Failed to remove event {}: {}'.format(event_name, str(func))) for line in traceback.format_stack(): LOG.warning(line.strip()) if event_name in self.emitter._events: LOG.debug("Removing found '"+str(event_name)+"'") else: LOG.debug("Not able to find '"+str(event_name)+"'") LOG.warning("Existing events: " + str(self.emitter._events)) for evt in self.emitter._events: LOG.warning(" "+str(evt)) LOG.warning(" "+str(self.emitter._events[evt])) if event_name in self.emitter._events: LOG.debug("Removing found '"+str(event_name)+"'") else: LOG.debug("Not able to find '"+str(event_name)+"'") LOG.warning('----- End dump -----') def remove_all_listeners(self, event_name): ''' Remove all listeners connected to event_name. Args: event_name: event from which to remove listeners ''' if event_name is None: raise ValueError self.emitter.remove_all_listeners(event_name) def run_forever(self): self.started_running = True self.client.run_forever() def close(self): self.client.close() self.connected_event.clear()
class WebsocketClient(object): def __init__(self, url): self.url = url self.emitter = EventEmitter() self.client = self.create_client() self.pool = ThreadPool(10) self.retry = 5 def create_client(self): return WebSocketApp(self.url, on_open=self.on_open, on_close=self.on_close, on_error=self.on_error, on_message=self.on_message) def on_open(self, ws): self.emitter.emit("open") # Restore reconnect timer to 5 seconds on sucessful connect self.retry = 5 def on_close(self, ws): self.emitter.emit("close") def on_error(self, ws, error): try: self.emitter.emit('error', error) self.client.close() except Exception as e: print(repr(e)) print("WS Client will reconnect in %d seconds." % self.retry) time.sleep(self.retry) self.retry = min(self.retry * 2, 60) self.client = self.create_client() self.run_forever() def on_message(self, ws, message): self.emitter.emit('message', message) parsed_message = Message.deserialize(message) self.pool.apply_async(self.emitter.emit, (parsed_message.type, parsed_message)) def emit(self, message): if (not self.client or not self.client.sock or not self.client.sock.connected): return if hasattr(message, 'serialize'): self.client.send(message.serialize()) else: self.client.send(json.dumps(message.__dict__)) def on(self, event_name, func): self.emitter.on(event_name, func) def once(self, event_name, func): self.emitter.once(event_name, func) def remove(self, event_name, func): self.emitter.remove_listener(event_name, func) def remove_all_listeners(self, event_name): ''' Remove all listeners connected to event_name. Args: event_name: event from which to remove listeners ''' if event_name is None: raise ValueError self.emitter.remove_all_listeners(event_name) def run_forever(self): self.client.run_forever() def close(self): self.client.close()
class MotionSensor(Component): """A motion sensor abstraction component interface.""" def __init__(self, pin): """Initialize a new instance of MotionSensor. :param raspy.io.gpio.Gpio pin: The input pin to check for motion on. :raises: ArgumentNullException if pin is None. """ Component.__init__(self) if pin is None: raise ArgumentNullException("'pin' param cannot be None.") self.__emitter = EventEmitter() self.__lastMotion = None self.__lastInactive = None self.__pin = pin self.__pin.provision() def dispose(self): """Release managed resources used by this component.""" if self.is_disposed: return if self.__pin is not None: self.__pin.dispose() self.__pin = None self.__lastMotion = None self.__lastInactive = None self.__emitter.remove_all_listeners() self.__emitter = None Component.dispose(self) def on(self, evt, callback): """Register an event with a callback to handle it. :param str evt: The name of the event to register a handler for. :param function callback: The callback to execute when the event fires. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("MotionSensor") self.__emitter.on(evt, callback) def emit(self, evt, args): """Emit the specified event to all registered listeners. :param str evt: The name of the event to emit. :param object args: The arguments to pass to the event handlers (listeners). :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("MotionSensor") self.__emitter.emit(evt, args) def remove_all_listeners(self): """Remove all registered event listeners.""" if self.is_disposed: return if self.__emitter is not None: self.__emitter.remove_all_listeners() def on_motion_state_changed(self, motion_evt): """Fire the motion state changed event. :param raspy.components.sensors.motion_detected_event.MotionDetectedEvent motion_evt: The motion detected event object. :raises: ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("MotionSensor") if motion_evt.is_motion_detected: self.__lastMotion = datetime.now() else: self.__lastInactive = datetime.now() _t = threading.Thread(target=self.emit, name=EVENT_MOTION_STATE_CHANGED, args=(EVENT_MOTION_STATE_CHANGED, motion_evt)) _t.daemon = True _t.start() @property def last_motion_timestamp(self): """Get the timestamp of the last time motion was detected. :returns: The timestamp of when motion was detected. :rtype: datetime.datetime """ return self.__lastMotion @property def last_inactivity_timestamp(self): """The last inactivity timestamp. :returns: The timestamp of the last time the sensor went idle. :rtype: datetime.datetime """ return self.__lastInactive @property def pin(self): """Get the pin being used to sample sensor data. :returns: The underlying physical pin. :rtype: raspy.io.gpio.Gpio """ return self.__pin @property def is_motion_detected(self): """Check to see if motion was detected. :returns: True if motion was detected. :rtype: bool """ return False
class MicrochipPotentiometer(Potentiometer): """An MCP45XX or MCP46XX IC device abstraction base type.""" def __init__(self, device=None, pin_a0=False, pin_a1=False, pin_a2=False, channel=microchip_pot_channel.NONE, non_vol_mode=microchip_pot_non_volatile_mode. VOLATILE_AND_NON_VOLATILE, init_non_vol_wiper_val=0): """Initialize a new instance of MicrochipPotentiometer. :param raspy.io.i2c.i2c_interface.I2CInterface device: The I2C bus device this instance is connected to. :param bool pin_a0: Set True if device's address pin A0 is high. :param bool pin_a1: Set True if device's address pin A1 is high. :param bool pin_a2: Set True if device's address pin A2 is high. :param int channel: The potentiometer channel. :param int non_vol_mode: The non-volatility mode. :param int init_non_vol_wiper_val: The initial value to set. :raises: raspy.argument_null_exception.ArgumentNullException if 'device' param is None. :raises: raspy.illegal_argument_exception.IllegalArgumentException if the specified channel is not supported by the device. :raises: raspy.io.io_exception.IOException if unable to open the I2C bus. """ Potentiometer.__init__(self) if device is None: msg = "Param 'device' cannot be None." raise ArgumentNullException(msg) if not self.is_channel_supported(channel): msg = "Specified channel not supported by device." raise IllegalArgumentException(msg) self.__emitter = EventEmitter() self.__channel = channel self.__currentValue = 0 self.__nonVolMode = non_vol_mode device_addr = MicrochipPotentiometer._build_i2c_address( pin_a0, pin_a1, pin_a2) self.__controller = mcp_device_controller.MCPDeviceController( device, device_addr) self.__emitter.on( EVENT_WIPER_ACTION, lambda wiper_evt: self._on_wiper_action_event(wiper_evt)) self._initialize(init_non_vol_wiper_val) @staticmethod def _build_i2c_address(pin_a0=False, pin_a1=False, pin_a2=False): """Build the I2C bus address of the device based on which pins are set. :param bool pin_a0: Set True if the device's address pinA0 is high. :param bool pin_a1: Set True if the device's address pinA1 is high. :param bool pin_a2: Set True if the device's address pinA2 is high. :returns: The I2C address based on address pins given. :rtype: int """ # Constant component. i2c_addr = 0x0101000 # Dynamic component if device knows A0. if pin_a0: i2c_addr |= 0x0000001 # Dynamic component if device knows A1. if pin_a1: i2c_addr |= 0x0000010 # Dynamic component if device knows A2. if pin_a2: i2c_addr |= 0x0000100 return i2c_addr def _get_value_according_boundaries(self, val=0): """Adjust the given value according to the boundaries (0 and max_val). :param int val: The wiper's value to be set. :returns: A valid wiper value. :rtype: int """ if val is None or not type(val) == int or val < 0: val = 0 new_val = val elif val > self.max_value: new_val = self.max_value else: new_val = val return new_val def _initialize(self, init_val): """Initialize the wiper to a defined status. For devices capable of non-volatile wipers, the non-volatile value is loaded. For devices not capable, the given value is set in the device. :param int init_val: The initial value for devices not capable of non-volatile wipers. :raises: raspy.io.io_exception.IOException if communication with the device failed or a malformed result. """ chan = device_control_channel.value_of(self.__channel) if self.is_non_volatile_wiper_capable: self.__currentValue = self.__controller.get_value(chan, True) else: new_init_val = self._get_value_according_boundaries(init_val) vol = mcp_device_controller.VOLATILE_WIPER self.__controller.set_value(chan, new_init_val, vol) self.__currentValue = new_init_val def _on_wiper_action_event(self, wiper_evt=None): """Handle the internal EVENT_WIPER_ACTION. :param WiperEvent wiper_evt: The event info. """ # Do nothing if no event specified. if wiper_evt is None: return # For volatile wiper. if (self.__nonVolMode == microchip_pot_non_volatile_mode.VOLATILE_ONLY or self.__nonVolMode == microchip_pot_non_volatile_mode.VOLATILE_AND_NON_VOLATILE): wiper_evt.set_channel_value(mcp_device_controller.VOLATILE_WIPER) # For non-volatile wiper. if (self.__nonVolMode == microchip_pot_non_volatile_mode.NON_VOLATILE_ONLY or self.__nonVolMode == microchip_pot_non_volatile_mode.VOLATILE_AND_NON_VOLATILE): wiper_evt.set_channel_value( mcp_device_controller.NON_VOLATILE_WIPER) def on(self, evt, callback): """Register an event with a callback to handle it. :param str evt: The name of the event to register a handler for. :param function callback: The callback to execute when the event fires. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("MicrochipPotentiometer") self.__emitter.on(evt, callback) def emit(self, evt, args): """Emit the specified event to all registered listeners. :param str evt: The name of the event to emit. :param object args: The arguments to pass to the event handlers (listeners). :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("MicrochipPotentiometer") self.__emitter.emit(evt, args) def remove_all_listeners(self): """Remove all registered event listeners.""" if self.is_disposed: return if self.__emitter is not None: self.__emitter.remove_all_listeners() def dispose(self): """Dispose managed resources.""" if self.is_disposed: return if self.__controller is not None: self.__controller.dispose() self.__controller = None self.__emitter.remove_all_listeners() self.__emitter = None self.__currentValue = -1 self.__nonVolMode = microchip_pot_non_volatile_mode.VOLATILE_AND_NON_VOLATILE Potentiometer.dispose(self) def _fire_wiper_action_event(self, wiper_evt): """Fire the EVENT_WIPER_ACTION event. :param WiperEvent wiper_evt: The event info. """ if self.is_disposed: raise ObjectDisposedException("MicrochipPotentiometer") self.emit(EVENT_WIPER_ACTION, wiper_evt) @property def current_value(self): """Get the wiper's current value. :returns: The current value. Values from 0 to max_value are valid. Values above or below these boundaries should be corrected quietly. :rtype: int """ return self.__currentValue @current_value.setter def current_value(self, value): """Set the wiper's current value. :param int value: The wiper value to set. """ # Check bounds. new_val = self._get_value_according_boundaries(value) # Set wipers according to mode. chan = device_control_channel.value_of(self.channel) evt = WiperEvent(chan, self.__controller, new_val) self._fire_wiper_action_event(evt) # Set value only if volatile wiper is affected. if self.__nonVolMode == microchip_pot_non_volatile_mode.NON_VOLATILE_ONLY: return self.__currentValue = new_val @property def channel(self): """Get the channel this device is configured for. :returns: The device channel. :rtype: int """ return microchip_pot_channel.NONE @property def is_non_volatile_wiper_capable(self): """Get whether or not this device is capable of non-volatile wipers. :returns: True if the device is capable of non-volatile wipers. :rtype: bool """ return False @property def non_volatile_mode(self): """Get the way non-volatile reads and/or writes are done. :returns: The non-volatile mode. :rtype: int """ return microchip_pot_non_volatile_mode.VOLATILE_AND_NON_VOLATILE @property def supported_channels(self): """Get the channels that are suppored by the underlying device. :returns: A list of `raspy.components.potentiometers.microchip.MicrochipPotChannel` that represent the supported channels by the underlying device. :rtype: list """ return list() @property def device_status(self): """Get the device and wiper status. :returns: The device status. :rtype: raspy.components.potentiometers.microchip.MicrochipPotDeviceStatus. :raises: raspy.io.io_exception.IOException if communication with the device fails. """ dev_stat = self.__controller.device_status wipe_lock_act = dev_stat.channel_b_locked if self.__channel == microchip_pot_channel.A: wipe_lock_act = dev_stat.channel_a_locked write_act = dev_stat.eeprom_write_active write_prot = dev_stat.eeprom_write_protected return MicrochipPotDevStatus(self.__channel, write_act, write_prot, wipe_lock_act) @property def terminal_configuration(self): """Get the current terminal configuration. :returns: The terminal configuration. :rtype: raspy.components.potentiometers.microchip.MCPTerminalConfiguration :raises: raspy.io.io_exception.IOException if communication with the device fails. """ chan = device_control_channel.value_of(self.__channel) tcon = self.__controller.get_terminal_config(chan) chan_enable = tcon.channel_enabled pin_a = tcon.pin_a_enabled pin_w = tcon.pin_w_enabled pin_b = tcon.pin_b_enabled return MCPTerminalConfiguration(self.__channel, chan_enable, pin_a, pin_w, pin_b) @terminal_configuration.setter def terminal_configuration(self, config): """Set the terminal configuration. :param MCPTerminalConfiguration config: The terminal configuration. :raises: raspy.io.io_exception.IOException if communication with the device fails. """ if config is None: raise ArgumentNullException("config value cannot be None.") if config.channel != self.__channel: msg = "Setting a configuration with a channel that is not the " msg += "potentiometer's channel is not supported." raise IllegalArgumentException(msg) chan = device_control_channel.value_of(self.__channel) chan_enable = config.is_channel_enabled pin_a = config.is_pin_a_enabled pin_w = config.is_pin_w_enabled pin_b = config.is_pin_b_enabled dev_con = DeviceControllerTermConfig(chan, chan_enable, pin_a, pin_w, pin_b) self.__controller.set_terminal_config(dev_con) def _set_non_volatile_mode(self, mode): """Set the non-volatility mode. :param int mode: The way non-volatile reads or writes are done. :raises: raspy.invalid_operation_exception.InvalidOperationException if this device is not capable of non-volatile wipers. """ vol_only = microchip_pot_non_volatile_mode.VOLATILE_ONLY if (not self.is_non_volatile_wiper_capable and self.__nonVolMode != vol_only): msg = "This device is not capable of non-volatile wipers." msg += "You *must* use microchip_pot_non_volatile_mode.VOLATILE_ONLY" raise InvalidOperationException(msg) self.__nonVolMode = mode def _get_non_volatile_value(self): """Get the non-volatile wiper's value. :returns: The non-volatile wiper's value. :rtype: int :raises: raspy.invalid_operation_exception.InvalidOperationException if this device is not capable of non-volatile wipers. """ if self.is_non_volatile_wiper_capable: msg = "This device is not capable of non-volatile wipers." raise InvalidOperationException(msg) chan = device_control_channel.value_of(self.__channel) return self.__controller.get_value(chan, True) def update_cache_from_device(self): """Update the cache from the wiper's value. :returns: The wiper's current value. :rtype: int :raises: raspy.io.io_exception.IOException if communication with the device fails. """ chan = device_control_channel.value_of(self.__channel) self.__currentValue = self.__controller.get_value(chan, False) return self.__currentValue def is_channel_supported(self, channel): """Get whether or not the specified channel is supported by the device. :param int channel: The channel to check. :returns: True if the channel is supported. :rtype: bool """ supported = False for chan in self.supported_channels: if chan == channel: supported = True break return supported def decrease(self, steps=0): """Decrease the wiper's value by the specified number of steps. It is not an error if the wiper hits or already hit the lower boundary (0). In such situations, the wiper sticks to the lower boundary or doesn't change. :param int steps: The number of steps to decrease by. If not specified or zero, then defaults to 1. :raises: raspy.io.io_exception.IOException if communication with the device failed. """ if self.__currentValue == 0: return if steps < 0: msg = "Only positive integer values are permitted." raise IllegalArgumentException(msg) if self.non_volatile_mode != microchip_pot_non_volatile_mode.VOLATILE_ONLY: msg = "Decrease is only permitted on volatile-only wipers." raise InvalidOperationException(msg) # Check bounds: actual_steps = steps if steps > self.__currentValue: actual_steps = self.__currentValue new_val = self.__currentValue - actual_steps if new_val == 0 or steps > 5: self.__currentValue = new_val else: chan = device_control_channel.value_of(self.__channel) self.__controller.decrease(chan, actual_steps) self.__currentValue = new_val def increase(self, steps=0): """Increase the wiper's value bye the specified number of steps. It is not an error if the wiper hits or already hit the upper boundary. In such situations, the wiper sticks to the upper boundary or doesn't change. :param int steps: How many steps to increase. If not specified or zero, then defaults to 1. If the current value is equal to the max value, then nothing happens. If steps is less than zero, then an exception is thrown. :raises: raspy.io.io_exception.IOException if communication with the device failed. """ max_val = self.max_value if self.__currentValue == max_val: return if steps < 0: msg = "Only positive integer values are permitted." raise IllegalArgumentException(msg) vol_only = microchip_pot_non_volatile_mode.VOLATILE_ONLY if self.non_volatile_mode != vol_only: msg = "Increase is only permitted for volatile-only wipers." raise InvalidOperationException(msg) # Check bounds. actual_steps = steps if (steps + self.__currentValue) > max_val: actual_steps = max_val - self.__currentValue new_val = self.__currentValue + actual_steps if new_val == max_val or steps > 5: self.__currentValue = new_val else: chan = device_control_channel.value_of(self.__channel) self.__controller.increase(chan, actual_steps) self.__currentValue = new_val def set_wiper_lock(self, enabled=False): """Enable or disable the wiper lock. :param bool enabled: Set True to enable the wiper lock. :raises: raspy.io.io_exception.IOException if communication with the device fails. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("MicrochipPotentiometer") chan = device_control_channel.value_of(self.__channel) self.__controller.set_wiper_lock(chan, enabled) def set_write_protection(self, enabled=False): """Enable or disable write protection for devices with non-vol memory. Enabling write-protection does not only protect non-volatile wipers, it also protects any other non-volatile information stored (ie. wiper-locks). :param bool enabled: Set True to enable. :raises: raspy.io.io_exception.IOException if communication with the device fails. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("MicrochipPotentiometer") self.__controller.set_write_protection(enabled)
class Light(Component): """The base type for light component abstractions.""" def __init__(self): """Initialize a new instance of Light.""" Component.__init__(self) self.__emitter = EventEmitter() def on(self, evt, callback): """Register an event with a callback to handle it. :param str evt: The name of the event to register a handler for. :param function callback: The callback to execute when the event fires. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Light") self.__emitter.on(evt, callback) def emit(self, evt, args): """Emit the specified event to all registered listeners. :param str evt: The name of the event to emit. :param object args: The arguments to pass to the event handlers (listeners). :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Light") self.__emitter.emit(evt, args) def remove_all_listeners(self): """Remove all registered event listeners.""" if self.is_disposed: return if self.__emitter is not None: self.__emitter.remove_all_listeners() @property def is_on(self): """Get a value indicating whether or not the light is on. :returns: True if the light is on. :rtype: bool """ return False @property def is_off(self): """Get a value indicating whether or not the light is off. :returns: True if the light is off. :rtype: bool """ return not self.is_on def turn_on(self): """Turn the light on. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. """ raise NotImplementedError("Method turn_on() not implemented.") def turn_off(self): """Turn the light off. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. """ raise NotImplementedError("Method turn_off() not implemented.") def on_light_state_changed(self, evt): """Fire the light state change event. :param raspy.components.lights.light_state_change_event.LightStateChangeEvent evt: The state change event object. """ if self.is_disposed: raise ObjectDisposedException("Light") _t = threading.Thread(target=self.emit, name="stateChange", args=(EVENT_STATE_CHANGED, evt)) _t.daemon = True _t.start() def dispose(self): """Dispose managed resources.""" if self.is_disposed: return if self.__emitter is not None: self.__emitter.remove_all_listeners() self.__emitter = None Component.dispose(self)
class Relay(Component): """A relay component abstraction interface/base.""" def __init__(self, pin): """Initialize a new instance of Relay. :param raspy.io.gpio.Gpio pin: The output pin being used to control the relay. :raises: ArgumentNullException if pin param is None. """ Component.__init__(self) if pin is None: raise ArgumentNullException("'pin' param cannot be None.") self.__emitter = EventEmitter() self.__state = relay_state.OPEN self.__pin = pin self.__pin.provision() def dispose(self): """Release managed resources used by this component.""" if self.is_disposed: return if self.__pin is not None: self.__pin.dispose() self.__pin = None self.__state = relay_state.OPEN self.__emitter.remove_all_listeners() self.__emitter = None Component.dispose(self) def on(self, evt, callback): """Register an event with a callback to handle it. :param str evt: The name of the event to register a handler for. :param function callback: The callback to execute when the event fires. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Relay") self.__emitter.on(evt, callback) def emit(self, evt, args): """Emit the specified event to all registered listeners. :param str evt: The name of the event to emit. :param object args: The arguments to pass to the event handlers (listeners). :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Relay") self.__emitter.emit(evt, args) def remove_all_listeners(self): """Remove all registered event listeners.""" if self.is_disposed: return if self.__emitter is not None: self.__emitter.remove_all_listeners() def on_relay_state_changed(self, change_evt): """Fire the relay state change event. :param raspy.components.relays.relay_state_change_event.RelayStateChangeEvent change_evt: The state change event info. :raises: ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("Relay") _t = threading.Thread(target=self.emit, name=EVENT_STATE_CHANGED, args=(EVENT_STATE_CHANGED, change_evt)) _t.daemon = True _t.start() def on_pulse_start(self): """Fire the pulse start event. :raises: ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("Relay") _t = threading.Thread(target=self.emit, name=EVENT_PULSE_START, args=[EVENT_PULSE_START]) _t.daemon = True _t.start() def on_pulse_stop(self): """Fire the pulse stop event. :raises: ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("Relay") _t = threading.Thread(target=self.emit, name=EVENT_PULSE_STOP, args=[EVENT_PULSE_STOP]) _t.daemon = True _t.start() @property def state(self): """Get the relay state. :returns: The relay state. :rtype: int """ return self.__state @state.setter def state(self, rel_state): """Set the relay state. :param int rel_state: The relay state to set. :raises: ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("Relay") self.__state = rel_state @property def is_open(self): """Check to see if the relay is in an open state. :returns: True if in an open state; Otherwise, False. :rtype: bool """ return self.state == relay_state.OPEN @property def is_closed(self): """Check to see if the relay is in a closed state. :returns: True if closed; Otherwise, False. :rtype: bool """ return self.state == relay_state.CLOSED @property def pin(self): """Get the pin being used to drive the relay. :returns: The underlying physical pin. :rtype: raspy.io.gpio.Gpio """ return self.__pin def open(self): """Open (deactivate) the relay.""" self.state = relay_state.OPEN def close(self): """Close (activate) the relay.""" self.state = relay_state.CLOSED def toggle(self): """Toggle the relay (switch on, then off).""" if self.is_open: self.close() else: self.open() def pulse(self, millis=0): """Pulse the relay on for the specified number of milliseconds. :param int millis: The number of milliseconds to wait before switching back off. If not specified or invalid, then pulses for DEFAULT_PULSE_MILLISECONDS. """ self.on_pulse_start() self.close() self.__pin.pulse(millis) self.open() self.on_pulse_stop()
class Switch(Component): """An interface/base type for switch device abstractions.""" def __init__(self): """Initialize a new instance of Switch.""" Component.__init__(self) self.__emitter = EventEmitter() self.__state = switch_state.OFF def dispose(self): """Release managed resources used by this component.""" if self.is_disposed: return self.__state = switch_state.OFF self.__emitter.remove_all_listeners() self.__emitter = None Component.dispose(self) def on(self, evt, callback): """Register an event with a callback to handle it. :param str evt: The name of the event to register a handler for. :param function callback: The callback to execute when the event fires. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Switch") self.__emitter.on(evt, callback) def emit(self, evt, args): """Emit the specified event to all registered listeners. :param str evt: The name of the event to emit. :param object args: The arguments to pass to the event handlers (listeners). :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Switch") self.__emitter.emit(evt, args) def remove_all_listeners(self): """Remove all registered event listeners.""" if self.is_disposed: return if self.__emitter is not None: self.__emitter.remove_all_listeners() def on_switch_state_changed(self, evt): """Fire the switch state change event. :param: raspy.components.switches.switch_state_change_event.SwitchStateChangeEvent evt: The event info object. :raises: ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("Switch") _t = threading.Thread(target=self.emit, name=EVENT_STATE_CHANGED, args=(EVENT_STATE_CHANGED, evt)) _t.daemon = True _t.start() @property def state(self): """Get the state of the switch. :returns: The switch state. :rtype: int """ return self.__state def is_state(self, sw_state): """Get the state of the switch. :param int sw_state: The state to check. :returns: The switch state. :rtype: int """ return self.state == sw_state @property def is_on(self): """Get whether or not this switch is in the on position. :returns: True if on; Otherwise, False. :rtype: bool """ return self.is_state(switch_state.ON) @property def is_off(self): """Get whether or not this switch is in the off position. :returns: True if off; Otherwise, False. :rtype: bool """ return self.is_state(switch_state.OFF) @property def pin(self): """Get the underlying physical pin. :returns: The pin. :rtype: raspy.io.gpio.Gpio """ return None
class Motor(Component): """A motor abstraction base class.""" def __init__(self): """Initialize a new instance of Motor.""" Component.__init__(self) self.__emitter = EventEmitter() self.__state = motor_state.STOP def dispose(self): """Dispose managed resources.""" if self.is_disposed: return self.__emitter.remove_all_listeners() self.__emitter = None Component.dispose(self) def remove_all_listeners(self): """Remove all registered event listeners.""" if self.is_disposed: return if self.__emitter is not None: self.__emitter.remove_all_listeners() def on(self, evt, callback): """Register an event with a callback to handle it. :param str evt: The name of the event to register a handler for. :param function callback: The callback to execute when the event fires. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Motor") self.__emitter.on(evt, callback) def emit(self, evt, args=None): """Emit the specified event to all registered listeners. :param str evt: The name of the event to emit. :param object args: The arguments to pass to the event handlers (listeners). :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Motor") self.__emitter.emit(evt, args) def _fire_events(self, change_evt): """Fire motor state change events. :param raspy.components.motors.motor_state_change_event.MotorStateChangeEvent change_evt: The event info object. """ self.emit(EVENT_STATE_CHANGED, change_evt) if change_evt.new_state == motor_state.STOP: self.emit(EVENT_STOPPED) elif change_evt.new_state == motor_state.FORWARD: self.emit(EVENT_FORWARD) elif change_evt.new_state == motor_state.REVERSE: self.emit(EVENT_REVERSE) def on_motor_state_change(self, change_evt): """Fire the motor state change event. :param raspy.components.motors.motor_state_change_event.MotorStateChangeEvent change_evt: The event info object. """ if self.is_disposed: raise ObjectDisposedException("Motor") _t = threading.Thread(target=self._fire_events, name="motorStateChange", args=[change_evt]) _t.daemon = True _t.start() @property def state(self): """Get the motor state. :returns: The motor state. :rtype: int """ return self.__state @state.setter def state(self, mot_state): """Set the motor state. :param int mot_state: The motor state. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("Motor") self.__state = mot_state @property def is_stopped(self): """Get whether or not the motor is stopped. :returns: True if the motor is stopped. :rtype: bool """ return self.is_state(motor_state.STOP) def forward(self, millis=0): """Tell the motor to move forward for the specified millis. :param int millis: The number of milliseconds to continue moving forward for. If zero or None, then moves forward continuously until stopped. """ if self.state == motor_state.FORWARD: return old_state = self.state self.state = motor_state.FORWARD evt = MotorStateChangeEvent(old_state, motor_state.FORWARD) self.on_motor_state_change(evt) if millis > 0: _t = threading.Timer(millis / 1000, self.stop) _t.start() def reverse(self, millis=0): """Tell the motor to move in reverse for the specified millis. :param int millis: The number of milliseconds to continue moving in reverse for. If zero or None, then moves in reverse continuously until stopped. """ if self.state == motor_state.REVERSE: return old_state = self.state self.state = motor_state.REVERSE evt = MotorStateChangeEvent(old_state, motor_state.REVERSE) self.on_motor_state_change(evt) if millis > 0: _t = threading.Timer(millis / 1000, self.stop) _t.start() def stop(self): """Stop the motor's movement.""" if self.state == motor_state.STOP: return old_state = self.state self.state = motor_state.STOP evt = MotorStateChangeEvent(old_state, motor_state.STOP) self.on_motor_state_change(evt) def is_state(self, state): """Determine if motor is in the specified state. :param int state: The state to check for. :returns: True if the motor is in the specified state. :rtype: bool """ return self.__state == state
class PiCameraDevice(Device): """An abstraction of the RaspiCam device. RaspiCam is a peripheral camera device designed specifically for use with the Raspberry Pi. This class provides a threaded wrapper around the raspistill utility and thus a means for still capture control. See http://www.raspberrypi.org/wp-content/uploads/2013/07/RaspiCam-Documentation.pdf for instructions on how to install and configure RaspiCam support. """ def __init__(self, settings=None): """Initialize a new instance of PiCameraDevice. :param StillCaptureSettings settings: The image capture settings. """ Device.__init__(self) self.__settings = settings self.__emitter = EventEmitter() self.__isRunning = False self.__processID = -1 self.__exitCode = -1 self.__captureProc = None self.__syncLock = threading.RLock() self.__captureThread = None def on(self, evt, callback): """Register an event with a callback to handle it. :param str evt: The name of the event to register a handler for. :param function callback: The callback to execute when the event fires. :raises: ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("PiCameraDevice") self.__emitter.on(evt, callback) def emit(self, evt, args): """Emit the specified event to all registered listeners. :param str evt: The name of the event to emit. :param object args: The arguments to pass to the event handlers (listeners). :raises: ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Gpio") self.__emitter.emit(evt, args) def remove_all_listeners(self): """Remove all registered event listeners.""" if self.is_disposed: return if self.__emitter is not None: self.__emitter.remove_all_listeners() @property def capture_settings(self): """Get the still capture settings. :returns: The still capture settings. :rtype: StillCaptureSettings """ return self.__settings @capture_settings.setter def capture_settings(self, settings): """Set the still capture settings. :param StillCaptureSettings settings: The capture settings. """ self.__settings = settings @property def process_id(self): """Get the process ID. :returns: The ID of the capture process if started; Otherwise, -1. :rtype: int """ return self.__processID @property def is_running(self): """Get whether or not the capture process is running. :returns: True if running. :rtype: bool """ return self.__isRunning @property def exit_code(self): """Get the exit code of the capture process. :returns: The process exit code if terminated normally; Otherwise, -1. """ return self.__exitCode def on_capture_started(self, start_evt): """Fire the capture started event. :param CaptureStartEvent start_evt: The event object. :raises: ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("PiCameraDevice") _t = threading.Thread(target=self.emit, name=EVENT_CAPTURE_START, args=(EVENT_CAPTURE_START, start_evt)) _t.daemon = True _t.start() def on_capture_output_received(self, out_evt): """Fire the capture output event. :param CaptureOutputEvent out_evt: The event object. :raises: ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("PiCameraDevice") _t = threading.Thread(target=self.emit, name=EVENT_CAPTURE_OUTPUT, args=(EVENT_CAPTURE_OUTPUT, out_evt)) _t.daemon = True _t.start() def on_capture_done(self, done_evt): """Fire the capture done event. :param CaptureDoneEvent done_evt: The event object. :raises: ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("PiCameraDevice") _t = threading.Thread(target=self.emit, name=EVENT_CAPTURE_DONE, args=(EVENT_CAPTURE_DONE, done_evt)) _t.daemon = True _t.start() def cancel(self): """Cancel the still capture process, if running. Should emit EVENT_CAPTURE_DONE and include the termination signal. """ if not self.__isRunning: return self.__syncLock.acquire() self.__isRunning = False self.__syncLock.release() core_utils.sleep(500) if self.__captureProc is not None: if self.__captureProc.poll() is not None: try: self.__captureProc.kill() except OSError: # Process probably already died. pass @staticmethod def _enqueue_output(out, queue): """Enqueue output from the process. :param File out: The standard output stream from the process. :param Queue queue: The queue to store the output lines in. """ for line in iter(out.readline, b''): queue.put(line) out.close() def _monitor_capture(self): """Monitor the capture process.""" if self.__settings is None: self.__settings = StillCaptureSettings() args = self.__settings.to_argument_string() cmd = ['raspistill'] + args.split(" ") # start the process and get the PID. on_posix = 'posix' in sys.builtin_module_names self.__captureProc = Popen(cmd, stdout=PIPE, stderr=PIPE, bufsize=1, close_fds=on_posix) read_queue = Queue() enqueue_args = (self.__captureProc.stdout, read_queue) queue_thread = threading.Thread(target=PiCameraDevice._enqueue_output, args=enqueue_args) queue_thread.daemon = True queue_thread.start() self.__syncLock.acquire() self.__processID = self.__captureProc.pid self.__syncLock.release() # Notify listeners that the process started and start signaling output. self.on_capture_started(CaptureStartEvent(self.__processID)) while self.__captureProc.poll() is None: try: line = read_queue.get_nowait() except Empty: pass else: self.on_capture_output_received(CaptureOutputEvent(line)) # The process finished. Get the exit code. self.__syncLock.acquire() self.__exitCode = self.__captureProc.returncode self.__isRunning = False self.__syncLock.release() # Notify listeners that the process finished. self.on_capture_done(CaptureDoneEvent(self.__exitCode)) def start(self): """Start the capture process on a separate thread. This method immediately returns and the process continues in the background firing events as they occur. """ if self.is_running: return self.__processID = -1 self.__exitCode = 0 self.__captureThread = threading.Thread(target=self._monitor_capture) self.__captureThread.name = "raspistillMonitor" self.__captureThread.daemon = True self.__captureThread.start() self.__isRunning = True def dispose(self): """Release managed resources used by this component.""" if self.is_disposed: return self.cancel() self.__captureThread = None self.__exitCode = 0 self.__processID = -1 self.__syncLock = None self.__captureProc = None self.__settings = None self.__emitter.remove_all_listeners() self.__emitter = None Device.dispose(self)
class Gpio(Pin): """Implemented by classes that represent GPIO pins on the Raspberry Pi.""" def __init__(self, pn, mode, value): """Initialize a new instance of raspy.io.Gpio. :param raspy.io.gpio_pins.GpioPin pn: The GPIO pin. :param int mode: The I/O pin mode. :param int value: The initial pin value. """ super(Pin, self).__init__() self.__emitter = EventEmitter() self.__pin = pn if self.__pin is None: self.__pin = gpio_pins.GpioNone self.__mode = mode if not isinstance(self.__mode, (int, long)): self.__mode = pin_mode.OUT self.__initValue = value if not isinstance(self.__initValue, (int, long)): self.__initValue = pin_state.LOW self.__revision = board_revision.REV2 self.__state = pin_state.LOW def on(self, evt, callback): """Register an event with a callback to handle it. :param str evt: The name of the event to register a handler for. :param function callback: The callback to execute when the event fires. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Gpio") self.__emitter.on(evt, callback) def emit(self, evt, args): """Emit the specified event to all registered listeners. :param str evt: The name of the event to emit. :param object args: The arguments to pass to the event handlers (listeners). :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Gpio") self.__emitter.emit(evt, args) def remove_all_listeners(self): """Remove all registered event listeners.""" if self.is_disposed: return if self.__emitter is not None: self.__emitter.remove_all_listeners() def write(self, ps): """Write a value to the pin. :param int ps: The pin state value to write to the pin. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("Gpio") self.__state = ps def pulse(self, millis): """Pulse the pin output for the specified number of milliseconds. :param int, long millis: The number of milliseconds to wait between states. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. """ if not isinstance(millis, (int, long)): millis = 0 if self.is_disposed: raise ObjectDisposedException("Gpio") seconds = 0 if millis > 0: seconds = millis / 1000 self.write(pin_state.HIGH) time.sleep(seconds) self.write(pin_state.LOW) def read(self): """Read a value from the pin. :returns: The state (value) of the pin. :rtype: int :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("Gpio") return pin_state.LOW def on_pin_state_change(self, psce): """Fire the pin state change event. :param raspy.io.pin_state_change_event.PinStateChangeEvent psce: The event object. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Gpio") _t = threading.Thread(target=self.emit, name="stateChange", args=(EVENT_GPIO_STATE_CHANGED, psce)) _t.daemon = True _t.start() @property def revision(self): """Get the board revision. :returns: The board revision. :rtype: int """ return self.__revision @property def state(self): """Get the state of the pin. :returns: The pin state. :rtype: int :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. """ self.__state = self.read() return self.__state @property def inner_pin(self): """Get the physical pin being represented by this instance. :returns: The underlying physical pin. :rtype: raspy.io.gpio_pins.GpioPin """ return self.__pin def provision(self): """Provision this pin. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("Gpio") self.write(self.__initValue) @property def mode(self): """Get the pin mode. :returns: The pin mode. :rtype: int """ return self.__mode @mode.setter def mode(self, p_mode): """Set the pin mode. :param int p_mode: The pin mode to set. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("Gpio") if p_mode is None: p_mode = p_mode.TRI if self.__mode != p_mode: self.__mode = p_mode self.provision() @property def address(self): """Get the pin address. :returns: The pin address. :rtype: int """ return self.__pin.value def change_board_revision(self, revision): """Change the board revision. :param int revision: The board revision. """ if revision is None or not isinstance(revision, (int, long)): revision = board_revision.REV2 self.__revision = revision def get_initial_pin_value(self): """Get the initial pin value. :returns: The initial pin value. :rtype: int """ return self.__initValue @property def pwm(self): """Get the PWM (pulse-width modulation) value. :returns: The PWM value. :rtype: int """ return 0 @pwm.setter def pwm(self, val): """Set the PWM (pulse-width modulation) value. :param int val: The PWM value. """ pass @property def pwm_range(self): """Get the PWM (pulse-width modulation) range. :returns: The PWM range. :rtype: int """ return 0 @pwm_range.setter def pwm_range(self, rng): """Set the PWM (pulse-width modulation) range. :param int rng: The PWM range. """ pass def dispose(self): """Dispose managed resources.""" if self.is_disposed: return self.__emitter.remove_all_listeners() self.__emitter = None self.__state = None self.__mode = None self.__pin = None self.__initValue = None Pin.dispose(self)
class TemperatureSensor(Component): """An abstract temperature sensor interface/base.""" def __init__(self, clock, data, reset): """Initialize a new instance of TempSensor. :param raspy.io.gpio.Gpio clock: The GPIO pin used for the clock. :param raspy.io.gpio.Gpio data: The GPIO used for data. :param raspy.io.gpio.Gpio reset: The GPIO pin used to trigger reset. :raises: ArgumentNullException if any of the pins are None. """ Component.__init__(self) if clock is None: raise ArgumentNullException("'clock' cannot be None.") if data is None: raise ArgumentNullException("'data' cannot be None.") if reset is None: raise ArgumentNullException("'reset' cannot be None.") self.__emitter = EventEmitter() self.__rawTemp = 0.0 self.__scale = temp_scale.CELCIUS self.__tempSensor = DS1620(clock, data, reset) def dispose(self): """Release managed resources used by this component.""" if self.is_disposed: return if self.__tempSensor is not None: self.__tempSensor.dispose() self.__tempSensor = None self.__rawTemp = 0.0 self.__scale = temp_scale.CELCIUS self.__emitter.remove_all_listeners() self.__emitter = None Component.dispose(self) def on(self, evt, callback): """Register an event with a callback to handle it. :param str evt: The name of the event to register a handler for. :param function callback: The callback to execute when the event fires. :raises: ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("TempSensor") self.__emitter.on(evt, callback) def emit(self, evt, args): """Emit the specified event to all registered listeners. :param str evt: The name of the event to emit. :param object args: The arguments to pass to the event handlers (listeners). :raises: ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("TempSensor") self.__emitter.emit(evt, args) def remove_all_listeners(self): """Remove all registered event listeners.""" if self.is_disposed: return if self.__emitter is not None: self.__emitter.remove_all_listeners() def get_raw_temperature(self): """Get the raw temperature value. :returns: The raw value read from the sensor. :rtype: float :raises: ObjectDisposedException if this instance has been disposed. """ return self.__rawTemp @property def scale(self): """Get the temperature scale. :returns: The temperature scale. :rtype: int """ return self.__scale @scale.setter def scale(self, the_scale): """Set the temperature scale. :param int the_scale: The temperature scale to set. """ self.__scale = the_scale def _set_raw_temp(self, temp): """Set the raw temperature. :param float temp: The temperature to set. """ self.__rawTemp = temp def on_temperature_change(self, change_evt): """Fire the temperature change event. :param raspy.components.temperature.temp_chang_event.TempChangeEvent change_evt: The event object. :raises: ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("TempSensor") _t = threading.Thread(target=self.emit, name=EVENT_TEMPERATURE_CHANGED, args=(EVENT_TEMPERATURE_CHANGED, change_evt)) _t.daemon = True _t.start() def get_temperature(self, scale): """Get the temperature value. :param int scale: The scale to use for measurement. :returns: The temperature value in the specified scale. :rtype: float :raises: ObjectDisposedException if this instance has been disposed. """ raw = self.get_raw_temperature() return temp_conversion.convert(self.scale, scale, raw) @property def sensor(self): """Get the sensor being used to measure. :returns: The temperature sensor. :rtype: raspy.sensors.ds1620.DS1620 """ return self.__tempSensor
class WebsocketClient(object): def __init__(self, host=None, port=None, route=None, ssl=None): config = Configuration.get().get("websocket") host = host or config.get("host") port = port or config.get("port") route = route or config.get("route") ssl = ssl or config.get("ssl") validate_param(host, "websocket.host") validate_param(port, "websocket.port") validate_param(route, "websocket.route") self.url = WebsocketClient.build_url(host, port, route, ssl) self.emitter = EventEmitter() self.client = self.create_client() self.pool = ThreadPool(10) self.retry = 5 @staticmethod def build_url(host, port, route, ssl): scheme = "wss" if ssl else "ws" return scheme + "://" + host + ":" + str(port) + route def create_client(self): return WebSocketApp(self.url, on_open=self.on_open, on_close=self.on_close, on_error=self.on_error, on_message=self.on_message) def on_open(self, ws): LOG.info("Connected") self.emitter.emit("open") # Restore reconnect timer to 5 seconds on sucessful connect self.retry = 5 def on_close(self, ws): self.emitter.emit("close") def on_error(self, ws, error): try: self.emitter.emit('error', error) self.client.close() except Exception as e: LOG.error(repr(e)) LOG.warning("WS Client will reconnect in %d seconds." % self.retry) time.sleep(self.retry) self.retry = min(self.retry * 2, 60) self.client = self.create_client() self.run_forever() def on_message(self, ws, message): self.emitter.emit('message', message) parsed_message = Message.deserialize(message) self.pool.apply_async( self.emitter.emit, (parsed_message.type, parsed_message)) def emit(self, message): if (not self.client or not self.client.sock or not self.client.sock.connected): return if hasattr(message, 'serialize'): self.client.send(message.serialize()) else: self.client.send(json.dumps(message.__dict__)) def on(self, event_name, func): self.emitter.on(event_name, func) def once(self, event_name, func): self.emitter.once(event_name, func) def remove(self, event_name, func): self.emitter.remove_listener(event_name, func) def remove_all_listeners(self, event_name): ''' Remove all listeners connected to event_name. Args: event_name: event from which to remove listeners ''' if event_name is None: raise ValueError self.emitter.remove_all_listeners(event_name) def run_forever(self): self.client.run_forever() def close(self): self.client.close()
class Sensor(Component): """A sensor abstraction component interface.""" def __init__(self, pin): """Initialize a new instance of Sensor. :param raspy.io.gpio.Gpio pin: The input pin to sample data from. :raises: ArgumentNullException if 'pin' param is None. """ Component.__init__(self) if pin is None: raise ArgumentNullException("'pin' param cannot be None.") self.__emitter = EventEmitter() self.__state = sensor_state.OPEN self.__pin = pin self.__pin.provision() def dispose(self): """Release managed resources used by this component.""" if self.is_disposed: return if self.__pin is not None: self.__pin.dispose() self.__pin = None self.__state = sensor_state.OPEN self.__emitter.remove_all_listeners() self.__emitter = None Component.dispose(self) def on(self, evt, callback): """Register an event with a callback to handle it. :param str evt: The name of the event to register a handler for. :param function callback: The callback to execute when the event fires. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Sensor") self.__emitter.on(evt, callback) def emit(self, evt, args): """Emit the specified event to all registered listeners. :param str evt: The name of the event to emit. :param object args: The arguments to pass to the event handlers (listeners). :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Sensor") self.__emitter.emit(evt, args) def remove_all_listeners(self): """Remove all registered event listeners.""" if self.is_disposed: return if self.__emitter is not None: self.__emitter.remove_all_listeners() def on_sensor_state_change(self, change_evt): """Fire the sensor state change event. :param sensor_state_change_event.SensorStateChangeEvent change_evt: The state change event object. :raises: ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("Sensor") _t = threading.Thread(target=self.emit, name=EVENT_STATE_CHANGED, args=(EVENT_STATE_CHANGED, change_evt)) _t.daemon = True _t.start() @property def state(self): """Get the state of the sensor. :returns: The sensor state. :rtype: int """ return self.__state def is_state(self, sens_state): """Check to see if the sensor is in the specified state. :param int sens_state: The state to check. :returns: True if in the specified state. :rtype: bool """ return self.state == sens_state @property def is_open(self): """Get a value indicating whether this is sensor is open. :returns: True if open; Otherwise, False. :rtype: bool """ return self.is_state(sensor_state.OPEN) @property def is_closed(self): """Get a value indicating whether this sensor is closed. :returns: True if closed; Otherwise, False. :rtype: bool """ return self.is_state(sensor_state.CLOSED) @property def pin(self): """Get the pin being used to sample sensor data. :returns: The underlying physical pin. :type: raspy.io.gpio.Gpio """ return self.__pin
class GenericWebsocket: """ Websocket object used to contain the base functionality of a websocket. Inlcudes an event emitter and a standard websocket client. """ def __init__(self, host, logLevel='INFO', loop=None, max_retries=5): self.host = host self.logger = CustomLogger('BfxWebsocket', logLevel=logLevel) self.loop = loop or asyncio.get_event_loop() self.events = EventEmitter(scheduler=asyncio.ensure_future, loop=self.loop) # overide 'error' event to stop it raising an exception # self.events.on('error', self.on_error) self.ws = None self.max_retries = max_retries self.attempt_retry = True def run(self): """ Run the websocket connection indefinitely """ self.loop.run_until_complete(self._main(self.host)) def get_task_executable(self): """ Get the run indefinitely asyncio task """ return self._main(self.host) async def _connect(self, host): async with websockets.connect(host) as websocket: self.ws = websocket self.logger.info("Wesocket connected to {}".format(host)) while True: await asyncio.sleep(0) message = await websocket.recv() await self.on_message(message) def get_ws(self): return self.ws async def _main(self, host): retries = 0 while retries < self.max_retries and self.attempt_retry: try: await self._connect(host) retries = 0 except (ConnectionClosed, socket.error) as e: self._emit('disconnected') if (not self.attempt_retry): return self.logger.error(str(e)) retries += 1 # wait 5 seconds befor retrying self.logger.info("Waiting 5 seconds before retrying...") await asyncio.sleep(5) self.logger.info("Reconnect attempt {}/{}".format( retries, self.max_retries)) self.logger.info("Unable to connect to websocket.") self._emit('stopped') def remove_all_listeners(self, event): """ Remove all listeners from event emitter """ self.events.remove_all_listeners(event) def on(self, event, func=None): """ Add a new event to the event emitter """ if not func: return self.events.on(event) self.events.on(event, func) def once(self, event, func=None): """ Add a new event to only fire once to the event emitter """ if not func: return self.events.once(event) self.events.once(event, func) def _emit(self, event, *args, **kwargs): self.events.emit(event, *args, **kwargs) async def on_error(self, error): """ On websocket error print and fire event """ self.logger.error(error) async def on_close(self): """ On websocket close print and fire event. This is used by the data server. """ self.logger.info("Websocket closed.") self.attempt_retry = False await self.ws.close() self._emit('done') async def on_open(self): """ On websocket open """ pass async def on_message(self, message): """ On websocket message """ pass
class Fireplace(Device): """Fireplace device abstraction interface/base type.""" def __init__(self): """Initialize a new instance of Fireplace.""" Device.__init__(self) self.__emitter = EventEmitter() self.__timeoutDelay = 0 self.__timeoutDelayMillis = 0 self.__timeoutUnit = time_unit.MINUTES self.__taskTimer = None self.__killTimer = None self.__state = fireplace_state.OFF self.__emitter.on(EVENT_STATE_CHANGED, lambda evt: self._internal_state_changed(evt)) def _cancel_timeout_task(self): """Cancel the timeout task (if running).""" if self.__taskTimer is not None: self.__taskTimer.cancel() self.__taskTimer = None def _internal_state_changed(self, evt): """An internal handler for the state change event. :param raspy.devices.fireplaces.fireplace_state_change_event.FireplaceStateChangeEvent evt: The event object. """ if evt: pass self._cancel_timeout_task() def on(self, evt, callback): """Register an event with a callback to handle it. :param str evt: The name of the event to register a handler for. :param function callback: The callback to execute when the event fires. :raises: ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Fireplace") self.__emitter.on(evt, callback) def emit(self, evt, args): """Emit the specified event to all registered listeners. :param str evt: The name of the event to emit. :param object args: The arguments to pass to the event handlers (listeners). :raises: ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Fireplace") self.__emitter.emit(evt, args) def remove_all_listeners(self): """Remove all registered event listeners.""" if self.is_disposed: return if self.__emitter is not None: self.__emitter.remove_all_listeners() def on_state_change(self, evt): """Fire the state change event. :param raspy.devices.fireplaces.fireplace_state_change_event.FireplaceStateChangeEvent evt: The event object. :raises: ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("Fireplace") _t = threading.Thread(target=self.emit, name=EVENT_STATE_CHANGED, args=(EVENT_STATE_CHANGED, evt)) _t.daemon = True _t.start() def on_operation_timeout(self, evt): """Fire the operation timeout event. :param raspy.devices.fireplaces.fireplace_timeout_event.FireplaceTimeoutEvent evt: The event object. """ if self.is_disposed: raise ObjectDisposedException("Fireplace") _t = threading.Thread(target=self.emit, name=EVENT_OPERATION_TIMEOUT, args=(EVENT_OPERATION_TIMEOUT, evt)) _t.daemon = True _t.start() def on_pilot_light_state_change(self, evt): """Fire the pilot light state change event. :param raspy.devices.fireplaces.fireplace_pilot_light_event.FireplacePilotLightEvent evt: The event object. """ if self.is_disposed: raise ObjectDisposedException("Fireplace") _t = threading.Thread(target=self.emit, name=EVENT_PILOT_LIGHT_STATE_CHANGED, args=(EVENT_PILOT_LIGHT_STATE_CHANGED, evt)) _t.daemon = True _t.start() @property def state(self): """Get the fireplace state. :returns: The fireplace state. :rtype: int """ return self.__state @state.setter def state(self, the_state): """Set the fireplace state. :param int the_state: The fireplace state. :raises: .fireplace_pilot_light_exception.FireplacePilotLightException if no pilot light sensor is present. """ self.__state = the_state @property def is_on(self): """Get a flag indicating whether the fireplace is on. :returns: True if the fireplace is on. :rtype: bool """ return self.state == fireplace_state.ON @property def is_off(self): """Get a flag indicating whether the fireplace is off. :returns: True if the fireplace is off. :rtype: bool """ return self.state == fireplace_state.OFF @property def is_pilot_light_on(self): """Get a flag indicating whether the pilot light is on. :returns: True if the pilot light is on. :rtype: bool """ return False @property def is_pilot_light_off(self): """Get a flag indicating whether the pilot light is off. :returns: True if the pilot light is off. :rtype: bool """ return False def get_timeout_delay(self): """Get the timeout delay. :returns: The timeout delay. :type: int """ return self.__timeoutDelay def get_timeout_unit(self): """Get the timeout unit of time. :returns: The time unit being used for the timeout delay. :rtype: int """ return self.__timeoutUnit def set_timeout_delay(self, delay, unit): """Get the timeout delay. :param int delay: The timeout delay. :param int unit: The time unit of measure for the timeout. :raises: raspy.invalid_operation_exception.InvalidOperationException if the fireplace is turned off. """ if self.is_off: msg = "Cannot set timeout when the fireplace is off." raise InvalidOperationException(msg) self.__timeoutDelay = delay self.__timeoutUnit = unit self.cancel_timeout() if self.__timeoutDelay > 0: wait_time = datetime.timedelta() if unit == time_unit.DAYS: wait_time = datetime.timedelta(days=delay) elif unit == time_unit.HOURS: wait_time = datetime.timedelta(hours=delay) elif unit == time_unit.MINUTES: wait_time = datetime.timedelta(minutes=delay) elif unit == time_unit.SECONDS: wait_time = datetime.timedelta(seconds=delay) elif unit == time_unit.MILLISECONDS: wait_time = datetime.timedelta(milliseconds=delay) kill_delay = datetime.timedelta( milliseconds=wait_time.total_seconds()) kill_delay += datetime.timedelta(seconds=1) sec = kill_delay.total_seconds() self.__killTimer = threading.Timer(sec, self._cancel_timeout_task) self.__killTimer.start() self._start_cancel_task() def turn_on(self, timeout_delay, timeout_unit): """Turn the fireplace on with the specified timeout. If the operation is not successful within the allotted time, the operation is cancelled for safety reasons. :param int timeout_delay: The timeout delay. If not specified or less than or equal to zero, then the fireplace is turned on without any safety delay (not recommended). :param int timeout_unit: The time unit of measure for the timeout. If not specified, `time_unit.SECONDS` is assumed. """ self.state = fireplace_state.ON if timeout_unit is None: timeout_unit = self.__timeoutUnit if timeout_delay is not None: if timeout_delay > 0: self.set_timeout_delay(timeout_delay, timeout_unit) def turn_off(self): """Turn the fireplace off.""" self.state = fireplace_state.OFF def _task_action(self): """The action for the background timeout task. This fires the operation timeout event, then turns off the fireplace. """ evt = FireplaceTimeoutEvent() self.on_operation_timeout(evt) if not evt.is_handled: self.turn_off() def _do_task(self): self._task_action() self.__killTimer.cancel() self.__killTimer = None def _start_cancel_task(self): """Start the background cancellation task.""" if self.__killTimer is not None: self.__taskTimer = threading.Timer(self.__timeoutDelayMillis, self._do_task) self.__taskTimer.start() def cancel_timeout(self): """Cancel the timeout.""" self._cancel_timeout_task() def shutdown(self): """Shutdown the fireplace.""" self.cancel_timeout() self.turn_off() def dispose(self): """Release all managed resources used by this component.""" if self.is_disposed: return self._cancel_timeout_task() if self.__killTimer is not None: self.__killTimer.cancel() self.__killTimer = None self.__emitter.remove_all_listeners() self.__emitter = None self.__state = fireplace_state.OFF self.__timeoutDelay = 0 self.__timeoutDelayMillis = 0 Device.dispose(self)
class Opener(Device): """Opener device abstraction interface.""" def __init__(self): """Initialize a new instance of Opener.""" Device.__init__(self) self.__emitter = EventEmitter() self.__state = opener_state.CLOSED def on(self, evt, callback): """Register an event with a callback to handle it. :param str evt: The name of the event to register a handler for. :param function callback: The callback to execute when the event fires. :raises: ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Opener") self.__emitter.on(evt, callback) def emit(self, evt, args): """Emit the specified event to all registered listeners. :param str evt: The name of the event to emit. :param object args: The arguments to pass to the event handlers (listeners). :raises: ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Opener") self.__emitter.emit(evt, args) def remove_all_listeners(self): """Remove all registered event listeners.""" if self.is_disposed: return if self.__emitter is not None: self.__emitter.remove_all_listeners() def on_opener_state_change(self, change_evt): """Fire the opener state change event. :param raspy.devices.access.opener_state_change_event.OpenerStateChangeEvent change_evt: The event object. :raises: ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("Opener") _t = threading.Thread(target=self.emit, name=EVENT_STATE_CHANGED, args=(EVENT_STATE_CHANGED, change_evt)) _t.daemon = True _t.start() def on_lock_state_change(self, change_evt): """Fire the lock state change event. :param raspy.devices.access.opener_lock_change_event.OpenerLockChangeEvent change_evt: The event object. :raises: ObjectDisposedException if this instance has been disposed. """ if self.is_disposed: raise ObjectDisposedException("Opener") _t = threading.Thread(target=self.emit, name=EVENT_LOCK_STATE_CHANGED, args=(EVENT_LOCK_STATE_CHANGED, change_evt)) _t.daemon = True _t.start() @property def state(self): """Get the state of the opener. :returns: The opener state. :rtype: int """ return self.__state @property def is_open(self): """Get a flag indicating whether the opener is open. :returns: True if open; Otherwise, False. :rtype: bool """ return self.state == opener_state.OPEN @property def is_opening(self): """Get a flag indicating if in the process of opening. :returns: True if opening; Otherwise, False. :rtype: bool """ return self.state == opener_state.OPENING @property def is_closed(self): """Get a flag indicating whether this opener is closed. :returns: True if closed; Otherwise, False. :rtype: bool """ return self.state == opener_state.CLOSED @property def is_closing(self): """Get a flag indicating if in the process of closing. :returns: True if closing; Otherwise, False. :rtype: bool """ return self.state == opener_state.CLOSING @property def is_locked(self): """Get a flag indicating whether this opener is locked. :returns: True if locked; Otherwise, False. :rtype: bool """ return False def open(self): """Instruct the device to open. :raises: ObjectDisposedException if this instance has been disposed. """ raise NotImplementedError("Method 'open()' not implemented.") def close(self): """Instruct the device to close. :raises: ObjectDisposedException if this instance has been disposed. """ raise NotImplementedError("Method 'close()' not implemented.") def dispose(self): """Release managed resources used by this component.""" if self.is_disposed: return self.__emitter.remove_all_listeners() self.__emitter = None self.__state = opener_state.CLOSED Device.dispose(self)
class WebsocketClient(object): def __init__(self, host=None, port=None, route=None, ssl=None): config = Configuration.get().get("websocket") host = host or config.get("host") port = port or config.get("port") route = route or config.get("route") ssl = ssl or config.get("ssl") validate_param(host, "websocket.host") validate_param(port, "websocket.port") validate_param(route, "websocket.route") self.url = WebsocketClient.build_url(host, port, route, ssl) self.emitter = EventEmitter() self.client = self.create_client() self.pool = ThreadPool(10) self.retry = 5 self.connected_event = Event() self.started_running = False @staticmethod def build_url(host, port, route, ssl): scheme = "wss" if ssl else "ws" return scheme + "://" + host + ":" + str(port) + route def create_client(self): return WebSocketApp(self.url, on_open=self.on_open, on_close=self.on_close, on_error=self.on_error, on_message=self.on_message) def on_open(self, ws): LOG.info("Connected") self.connected_event.set() self.emitter.emit("open") # Restore reconnect timer to 5 seconds on sucessful connect self.retry = 5 def on_close(self, ws): self.emitter.emit("close") def on_error(self, ws, error): try: self.emitter.emit('error', error) self.client.close() except Exception as e: LOG.error(repr(e)) LOG.warning("WS Client will reconnect in %d seconds." % self.retry) time.sleep(self.retry) self.retry = min(self.retry * 2, 60) self.client = self.create_client() self.run_forever() def on_message(self, ws, message): self.emitter.emit('message', message) parsed_message = Message.deserialize(message) self.pool.apply_async( self.emitter.emit, (parsed_message.type, parsed_message)) def emit(self, message): if not self.connected_event.wait(10): if not self.started_running: raise ValueError('You must execute run_forever() ' 'before emitting messages') self.connected_event.wait() if hasattr(message, 'serialize'): self.client.send(message.serialize()) else: self.client.send(json.dumps(message.__dict__)) def wait_for_response(self, message, reply_type=None, timeout=None): """Send a message and wait for a response. Args: message (Message): message to send reply_type (str): the message type of the expected reply. Defaults to "<message.type>.response". timeout: seconds to wait before timeout, defaults to 3 Returns: The received message or None if the response timed out """ response = [] def handler(message): """Receive response data.""" response.append(message) # Setup response handler self.once(reply_type or message.type + '.response', handler) # Send request self.emit(message) # Wait for response start_time = monotonic.monotonic() while len(response) == 0: time.sleep(0.2) if monotonic.monotonic() - start_time > (timeout or 3.0): try: self.remove(reply_type, handler) except (ValueError, KeyError): # ValueError occurs on pyee 1.0.1 removing handlers # registered with once. # KeyError may theoretically occur if the event occurs as # the handler is removbed pass return None return response[0] def on(self, event_name, func): self.emitter.on(event_name, func) def once(self, event_name, func): self.emitter.once(event_name, func) def remove(self, event_name, func): self.emitter.remove_listener(event_name, func) def remove_all_listeners(self, event_name): ''' Remove all listeners connected to event_name. Args: event_name: event from which to remove listeners ''' if event_name is None: raise ValueError self.emitter.remove_all_listeners(event_name) def run_forever(self): self.started_running = True self.client.run_forever() def close(self): self.client.close() self.connected_event.clear()
class PowerInterface(Component): """An interface base type for power components.""" def __init__(self): """Initialize a new instance of PowerInterface.""" Component.__init__(self) self.__emitter = EventEmitter() self.__state = power_state.OFF def dispose(self): """Dispose managed resources.""" if self.is_disposed: return self.__emitter.remove_all_listeners() self.__emitter = None Component.dispose(self) def remove_all_listeners(self): """Remove all registered event listeners.""" if self.is_disposed: return if self.__emitter is not None: self.__emitter.remove_all_listeners() def on(self, evt, callback): """Register an event with a callback to handle it. :param str evt: The name of the event to register a handler for. :param function callback: The callback to execute when the event fires. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Motor") self.__emitter.on(evt, callback) def emit(self, evt, args=None): """Emit the specified event to all registered listeners. :param str evt: The name of the event to emit. :param object args: The arguments to pass to the event handlers (listeners). :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance is disposed. """ if self.is_disposed: raise ObjectDisposedException("Motor") self.__emitter.emit(evt, args) def on_power_state_changed(self, event_info): """Fire the power state change event. :param raspy.components.power.power_state_change_event.PowerStateChangeEvent event_info: The event info object. """ if self.is_disposed: raise ObjectDisposedException("PowerInterface") _t = threading.Thread(target=self.emit, name=EVENT_STATE_CHANGED, args=(EVENT_STATE_CHANGED, event_info)) _t.daemon = True _t.start() @property def state(self): """Get the state of the power component. :returns: The state of the power component. :rtype: int :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. :raises: raspy.io.invalid_pin_mode_exception.InvalidPinModeException if the pin being used to control this device is not configure as an output. """ return power_state.UNKNOWN @state.setter def state(self, pwr_state): """Set the state of the power component. :param int pwr_state: The state to set. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. :raises: raspy.io.invalid_pin_mode_exception.InvalidPinModeException if the pin being used to control this device is not configure as an output. :raises: raspy.invalid_operation_exception.InvalidOperationException if an invalid state is specified. """ pass @property def is_on(self): """Check to see if the device is on. :returns: True if the device is on. :rtype: bool """ return self.state == power_state.ON @property def is_off(self): """Check to see if the device is off. :returns: True if the device is off. :rtype: bool """ return self.state == power_state.OFF def turn_on(self): """Turn the device on.""" self.state = power_state.ON def turn_off(self): """Turn the device off.""" self.state = power_state.OFF