def disconnect_client(self, timeout=DEFAULT_TIMEOUT): future = asyncio.Future(loop=self.loop) self.subscriptions = [] self.router = PathRouter() def off(): if hasattr(self, '_on_off_events'): del self._on_off_events if (_.get(self, 'client._state') != mqtt_cs_connected): off() if (hasattr(self, 'client')): del self.client future.set_result('done') return future else: def on_disconnect(*args, **kwargs): if future.done(): return off() if (hasattr(self, 'client')): del self.client future.set_result('done') if hasattr(self, 'client'): self.client.on_disconnect = self.safe(on_disconnect) self.client.disconnect() set_timeout(self.safe(on_disconnect), timeout) else: if (future.done() == False): future.set_result('done') return future
def router_option_extender(options): from wheezy.routing import PathRouter from wheezy.web.middleware.routing import PathRoutingMiddleware path_router = PathRouter() path_router.add_routes(urls() if callable(urls) else urls) options['path_router'] = path_router options['path_for'] = path_router.path_for return PathRoutingMiddleware(path_router)
def __init__(self, host="localhost", port=1883, base="microdrop"): super().__init__() self.router = PathRouter() self.host = host self.port = port self.base = base self.subscriptions = [] self.client_id = self.ClientID() self.client = self.Client() self.connected = False
def __init__(self, app_name, host="localhost", port=None, name=None, version='0.0.0', loop=None): if (app_name is None): raise ("app_name is undefined") if (port is None): port = 1884 if (name is None): name = get_class_name(self) self.router = PathRouter() client_id = generate_client_id(name, app_name) self.__listen = _.noop self.app_name = app_name self.client_id = client_id self.name = name self.schema = {} self.subscriptions = [] self.host = host self.port = port self.version = version self.last_message = None self.loop = None self.safe = None self.client = None if (loop == None): # Create thread to run event loop def start_loop(x): x.loop = asyncio.new_event_loop() x.loop.call_soon_threadsafe(x.ready_event.set) x.loop.run_forever() # Initialize loop and pass reference to main thread class X(object): pass X.ready_event = threading.Event() t = Thread(target=start_loop, args=(X, )) t.start() X.ready_event.wait() self.loop = X.loop else: self.loop = loop self.safe = safe(self.loop) # Start client self.wait_for(self.connect_client(client_id, host, port))
def __init__(self, host='localhost', port=1883, keepalive=60, base="microdrop"): self._host = host self._port = port self._keepalive = keepalive self.mqtt_client = mqtt.Client(client_id=self.client_id) self.mqtt_client.on_connect = self.on_connect self.mqtt_client.on_disconnect = self.on_disconnect self.mqtt_client.on_message = self.on_message self.should_exit = False self.router = PathRouter() self.subscriptions = [] self.base = base
def __init__(self, **kwargs): super().__init__() self.library = PicroscopyLibrary(**kwargs) self.helpers = WebHelpers(self.library) self.clients = kwargs.get('clients', IPv4Network('0.0.0.0/0')) logging.info('Clients must be on network %s', self.clients) self.static_dir = os.path.abspath( os.path.normpath( kwargs.get('static_dir', os.path.join(HERE, 'static')))) logging.info('Static files: %s', self.static_dir) self.templates_dir = os.path.abspath( os.path.normpath( kwargs.get('templates_dir', os.path.join(HERE, 'templates')))) logging.info('Chameleon templates: %s', self.templates_dir) self.templates = PageTemplateLoader(self.templates_dir, default_extension='.pt') self.layout = self.templates['layout'] # No need to make flashes a per-session thing - it's a single user app! self.flashes = [] self.router = PathRouter() # XXX Add handler for exiting system # XXX Make exit code conditional? (upgrade/reboot/shutdown/etc.) self.router.add_routes([ url('/', self.do_template, kwargs={'page': 'library'}, name='home'), url('/{page}.html', self.do_template, name='template'), url('/view/{image}.html', self.do_template, kwargs={'page': 'image'}, name='view'), url('/static/{path:any}', self.do_static, name='static'), url('/images/{image}', self.do_image, name='image'), url('/thumbs/{image}', self.do_thumb, name='thumb'), url('/delete/{image}', self.do_delete, name='delete'), url('/config', self.do_config, name='config'), url('/reset', self.do_reset, name='reset'), url('/capture', self.do_capture, name='capture'), url('/download', self.do_download, name='download'), url('/send', self.do_send, name='send'), url('/logout', self.do_logout, name='logout'), ])
def __init__(self, **kwargs): self.form = WebControlFormHelper(**kwargs) self.logger = kwargs.get('logger',) self.robot = kwargs.get('robot') #initialize speed in form with robot speed self.form.speed = self.robot.setDriveSpeed #create routes to web pages self.router = PathRouter() self.router.add_routes([ url('/', self.do_main_page, name='home'), url('/doRobotControl', self.do_process_form, name='execute'), url('/showRobotControlForm', self.do_display_form, name='view') ])
def __init__(self, **kwargs): super(MancifyWsgiApp, self).__init__() self.sms = MancifySMSService(kwargs['clockwork_api_key']) self.exec_timeout = kwargs.get('exec_timeout', 10) self.connect_timeout = kwargs.get('connect_timeout', 30) self.session_timeout = kwargs.get('session_timeout', 300) self.output_limit = kwargs.get('output_limit', 1024) self.router = PathRouter() self.router.add_routes([ url('/', self.do_index), url('/ssh', self.do_ssh), url('/translate', self.do_translate), ]) self.lock = threading.Lock() self.sessions = {} self.messages = set() self.terminate = threading.Event() self.reap_thread = threading.Thread(target=self.reap_sessions) self.reap_thread.daemon = True self.reap_thread.start()
def __init__(self, **kwargs): super().__init__() self.library = PicroscopyLibrary(**kwargs) self.helpers = WebHelpers(self.library) self.clients = kwargs.get('clients', IPv4Network('0.0.0.0/0')) logging.info('Clients must be on network %s', self.clients) self.static_dir = os.path.abspath(os.path.normpath(kwargs.get( 'static_dir', os.path.join(HERE, 'static') ))) logging.info('Static files: %s', self.static_dir) self.templates_dir = os.path.abspath(os.path.normpath(kwargs.get( 'templates_dir', os.path.join(HERE, 'templates') ))) logging.info('Chameleon templates: %s', self.templates_dir) self.templates = PageTemplateLoader( self.templates_dir, default_extension='.pt') self.layout = self.templates['layout'] # No need to make flashes a per-session thing - it's a single user app! self.flashes = [] self.router = PathRouter() # XXX Add handler for exiting system # XXX Make exit code conditional? (upgrade/reboot/shutdown/etc.) self.router.add_routes([ url('/', self.do_template, kwargs={'page': 'library'}, name='home'), url('/{page}.html', self.do_template, name='template'), url('/view/{image}.html', self.do_template, kwargs={'page': 'image'}, name='view'), url('/static/{path:any}', self.do_static, name='static'), url('/images/{image}', self.do_image, name='image'), url('/thumbs/{image}', self.do_thumb, name='thumb'), url('/delete/{image}', self.do_delete, name='delete'), url('/config', self.do_config, name='config'), url('/reset', self.do_reset, name='reset'), url('/capture', self.do_capture, name='capture'), url('/download', self.do_download, name='download'), url('/send', self.do_send, name='send'), url('/logout', self.do_logout, name='logout'), ])
class MancifyWsgiApp(object): def __init__(self, **kwargs): super(MancifyWsgiApp, self).__init__() self.sms = MancifySMSService(kwargs['clockwork_api_key']) self.exec_timeout = kwargs.get('exec_timeout', 10) self.connect_timeout = kwargs.get('connect_timeout', 30) self.session_timeout = kwargs.get('session_timeout', 300) self.output_limit = kwargs.get('output_limit', 1024) self.router = PathRouter() self.router.add_routes([ url('/', self.do_index), url('/ssh', self.do_ssh), url('/translate', self.do_translate), ]) self.lock = threading.Lock() self.sessions = {} self.messages = set() self.terminate = threading.Event() self.reap_thread = threading.Thread(target=self.reap_sessions) self.reap_thread.daemon = True self.reap_thread.start() def close(self): self.terminate.set() self.reap_thread.join(5) def reap_sessions(self): while True: reap_list = [] now = time.time() with self.lock: for recipient, session in self.sessions.iteritems(): if not session.timestamp: reap_list.append((recipient, session)) if (now - session.timestamp) > self.session_timeout: reap_list.append((recipient, session)) for recipient, session in reap_list: try: session.close(quiet=True) finally: del self.sessions[recipient] session = None if self.terminate.wait(10): break def __call__(self, environ, start_response): req = Request(environ) try: handler, kwargs = self.router.match(req.path_info) if handler: # XXX Workaround wheezy bug if 'route_name' in kwargs: del kwargs['route_name'] resp = handler(req, **kwargs) else: self.not_found(req) except exc.HTTPException as e: # The exception is the response resp = e return resp(environ, start_response) def not_found(self, req): raise exc.HTTPNotFound( "The resource at %s could not be found" % req.path_info) def do_index(self, req): resp = Response() resp.content_type = b'text/html' resp.content_encoding = b'utf-8' resp.text = """\ <html> <head><title>Mancify</title></head> <body> <h1>Mancify</h1> <p>Probably the silliest webapp in the world...</p> </body> </html> """ return resp def do_translate(self, req): # Check the request has the required parameters if not 'msg_id' in req.params: raise exc.HTTPBadRequest('Missing msg_id parameter') if not 'from' in req.params: raise exc.HTTPBadRequest('Missing from parameter') if not 'content' in req.params: raise exc.HTTPBadRequest('Missing content parameter') msg_id = req.params['msg_id'] recipient = req.params['from'] sender = req.params['to'] content = req.params['content'] # If we've seen the message before it's a duplicate. Return 200 OK so # the server doesn't keep retrying but otherwise ignore it if msg_id in self.messages: raise exc.HTTPOk('Message already processed') self.messages.add(msg_id) self.sms.send(sender, recipient, translator.translate(content, manc)) raise exc.HTTPOk('Message processed') def do_ssh(self, req): # Check the request has the required parameters if not 'msg_id' in req.params: raise exc.HTTPBadRequest('Missing msg_id parameter') if not 'from' in req.params: raise exc.HTTPBadRequest('Missing from parameter') if not 'content' in req.params: raise exc.HTTPBadRequest('Missing content parameter') msg_id = req.params['msg_id'] recipient = req.params['from'] sender = req.params['to'] content = req.params['content'] # If we've seen the message before it's a duplicate. Return 200 OK so # the server doesn't keep retrying but otherwise ignore it if msg_id in self.messages: raise exc.HTTPOk('Message already processed') self.messages.add(msg_id) try: with self.lock: try: session = self.sessions[recipient] except KeyError: session = MancifySSHSession( self.sms, sender, recipient, self.connect_timeout, self.exec_timeout) self.sessions[recipient] = session session.timestamp = time.time() session.execute(content) except Exception as e: msg = str(e) if len(msg) > 140: msg = msg[:137] + '...' self.sms.send(sender, recipient, msg) raise exc.HTTPOk('Message processed')
class BaseMqttReactor(MqttMessages): """ Base class for MQTT-based plugins. """ def __init__(self, host='localhost', port=1883, keepalive=60, base="microdrop"): self._host = host self._port = port self._keepalive = keepalive self.mqtt_client = mqtt.Client(client_id=self.client_id) self.mqtt_client.on_connect = self.on_connect self.mqtt_client.on_disconnect = self.on_disconnect self.mqtt_client.on_message = self.on_message self.should_exit = False self.router = PathRouter() self.subscriptions = [] self.base = base ########################################################################### # Attributes # ========== @property def host(self): return self._host @host.setter def host(self, value): self._host = value self._connect() @property def port(self): return self._port @port.setter def port(self, value): self._port = value self._connect() @property def keepalive(self): return self._keepalive @keepalive.setter def keepalive(self, value): self._keepalive = value self._connect() @property def plugin_path(self): """Get parent directory of class location""" return os.path.dirname( os.path.realpath(inspect.getfile(self.__class__))) @property def plugin_name(self): """Get plugin name via the basname of the plugin path """ return os.path.basename(self.plugin_path) @property def url_safe_plugin_name(self): """Make plugin name safe for mqtt and http requests""" return six.moves.urllib.parse.quote_plus(self.plugin_name) @property def client_id(self): """ ID used for mqtt client """ return (self.url_safe_plugin_name + ">>" + self.plugin_path + ">>" + datetime.datetime.now().isoformat().replace(">>", "")) def addGetRoute(self, route, handler): """Adds route along with corresponding subscription""" self.router.add_route(route, handler) # Replace characters between curly brackets with "+" wildcard self.subscriptions.append(re.sub(r"\{(.+?)\}", "+", route)) def sendMessage(self, topic, msg, retain=False, qos=0, dup=False): message = json.dumps(msg, cls=PandasJsonEncoder) self.mqtt_client.publish(topic, message, retain=retain, qos=qos) def subscribe(self): for subscription in self.subscriptions: self.mqtt_client.subscribe(subscription) ########################################################################### # Private methods # =============== def _connect(self): try: # Connect to MQTT broker. # TODO: Make connection parameters configurable. self.mqtt_client.connect(host=self.host, port=self.port, keepalive=self.keepalive) except socket.error: pass # logger.error('Error connecting to MQTT broker.') ########################################################################### # MQTT client handlers # ==================== def on_connect(self, client, userdata, flags, rc): self.addGetRoute("microdrop/" + self.url_safe_plugin_name + "/exit", self.exit) self.listen() self.subscribe() def on_disconnect(self, *args, **kwargs): # Startup Mqtt Loop after disconnected (unless should terminate) if self.should_exit: sys.exit() self._connect() self.mqtt_client.loop_forever() def on_message(self, client, userdata, msg): ''' Callback for when a ``PUBLISH`` message is received from the broker. ''' method, args = self.router.match(msg.topic) try: payload = json.loads(msg.payload, object_hook=pandas_object_hook) except ValueError: print("Message contains invalid json") print("topic: " + msg.topic) payload = None if method: method(payload, args) ########################################################################### # Control API # =========== def start(self): # Connect to MQTT broker. self._connect() # Start loop in background thread. signal.signal(signal.SIGINT, self.exit) self.mqtt_client.loop_forever() def exit(self, a=None, b=None): self.should_exit = True self.mqtt_client.disconnect() def stop(self): ''' Stop plugin thread. ''' # Stop client loop background thread (if running). self.mqtt_client.loop_stop()
class PicroscopyWsgiApp(object): def __init__(self, **kwargs): super().__init__() self.library = PicroscopyLibrary(**kwargs) self.helpers = WebHelpers(self.library) self.clients = kwargs.get('clients', IPv4Network('0.0.0.0/0')) logging.info('Clients must be on network %s', self.clients) self.static_dir = os.path.abspath( os.path.normpath( kwargs.get('static_dir', os.path.join(HERE, 'static')))) logging.info('Static files: %s', self.static_dir) self.templates_dir = os.path.abspath( os.path.normpath( kwargs.get('templates_dir', os.path.join(HERE, 'templates')))) logging.info('Chameleon templates: %s', self.templates_dir) self.templates = PageTemplateLoader(self.templates_dir, default_extension='.pt') self.layout = self.templates['layout'] # No need to make flashes a per-session thing - it's a single user app! self.flashes = [] self.router = PathRouter() # XXX Add handler for exiting system # XXX Make exit code conditional? (upgrade/reboot/shutdown/etc.) self.router.add_routes([ url('/', self.do_template, kwargs={'page': 'library'}, name='home'), url('/{page}.html', self.do_template, name='template'), url('/view/{image}.html', self.do_template, kwargs={'page': 'image'}, name='view'), url('/static/{path:any}', self.do_static, name='static'), url('/images/{image}', self.do_image, name='image'), url('/thumbs/{image}', self.do_thumb, name='thumb'), url('/delete/{image}', self.do_delete, name='delete'), url('/config', self.do_config, name='config'), url('/reset', self.do_reset, name='reset'), url('/capture', self.do_capture, name='capture'), url('/download', self.do_download, name='download'), url('/send', self.do_send, name='send'), url('/logout', self.do_logout, name='logout'), ]) def __call__(self, environ, start_response): req = Request(environ) try: if not IPv4Address(req.remote_addr) in self.clients: raise exc.HTTPForbidden() handler, kwargs = self.router.match(req.path_info) if handler: # XXX Why does route_name only appear in kwargs sometimes?! if 'route_name' in kwargs: del kwargs['route_name'] resp = handler(req, **kwargs) else: self.not_found(req) except exc.HTTPException as e: # The exception itself is a WSGI response resp = e return resp(environ, start_response) def not_found(self, req): """ Handler for unknown locations (404) """ raise exc.HTTPNotFound('The resource at %s could not be found' % req.path_info) def do_reset(self, req): """ Reset all settings to their defaults """ self.library.camera_reset() self.flashes.append('Camera settings reset to defaults') raise exc.HTTPFound( location=self.router.path_for('template', page='library')) def do_config(self, req): """ Configure the library and camera settings """ # Resolution is handled specially as the camera needs to stop the # preview in order to change it try: new_resolution = tuple( int(i) for i in req.params['resolution'].split('x', 1)) if len(new_resolution) != 2: raise ValueError() except ValueError: self.flashes.append('Invalid resolution: %s' % req.params['resolution']) if self.library.camera.resolution != new_resolution: try: self.library.camera.stop_preview() try: self.library.camera.resolution = new_resolution finally: self.library.camera.start_preview() except PiCameraError: self.flashes.append('Unable to change camera resolution ' 'to %s' % req.params['resolution']) # Everything else is handled generically... for setting in ( 'sharpness', 'contrast', 'brightness', 'saturation', #'ISO', 'exposure-compensation'): try: setattr(self.library.camera, setting.replace('-', '_'), int(req.params[setting])) except ValueError: self.flashes.append('Invalid %s: %s' % (setting, req.params[setting])) for setting in ('hflip', 'vflip'): try: setattr(self.library.camera, setting.replace('-', '_'), bool(req.params.get(setting, 0))) except ValueError: self.flashes.append('Invalid %s: %s' % (setting, req.params[setting])) for setting in ('meter-mode', 'awb-mode', 'exposure-mode'): try: setattr(self.library.camera, setting.replace('-', '_'), req.params[setting]) except ValueError: self.flashes.append('Invalid %s: %s' % (setting, req.params[setting])) for setting in ('artist', 'email', 'copyright', 'description', 'filename-template', 'format'): try: setattr(self.library, setting.replace('-', '_'), req.params[setting]) except ValueError: self.flashes.append('Invalid %s: %s' % (setting, req.params[setting])) # If any settings failed, re-render the settings form if self.flashes: return self.do_template(req, 'settings') raise exc.HTTPFound( location=self.router.path_for('template', page='library')) def do_capture(self, req): """ Take a new image with the camera and add it to the library """ self.library.capture() raise exc.HTTPFound( location=self.router.path_for('template', page='library')) def do_download(self, req): """ Send the library as a .zip archive """ archive = self.library.archive() size = archive.seek(0, io.SEEK_END) archive.seek(0) resp = Response() resp.content_type = 'application/zip' resp.content_length = size resp.content_disposition = 'attachment; filename=images.zip' resp.app_iter = FileWrapper(archive) return resp def do_send(self, req): """ Send the library as a set of attachments to an email """ self.library.send() self.flashes.append('Email sent to %s' % library.email) raise exc.HTTPFound( location=self.router.path_for('template', page='library')) def do_delete(self, req, image): """ Delete the selected images from library """ self.library.remove(image) raise exc.HTTPFound( location=self.router.path_for('template', page='library')) def do_logout(self, req): """ Clear the library of all images, reset all settings """ self.library.clear() self.library.user_reset() self.library.camera_reset() raise exc.HTTPFound( location=self.router.path_for('template', page='settings')) def do_image(self, req, image): """ Serve an image from the library """ if not image in self.library: self.not_found(req) resp = Response() resp.content_type, resp.content_encoding = mimetypes.guess_type( image, strict=False) resp.content_length = self.library.stat_image(image).st_size resp.app_iter = FileWrapper(self.library.open_image(image)) return resp def do_thumb(self, req, image): """ Serve a thumbnail of an image from the library """ if not image in self.library: self.not_found(req) resp = Response() resp.content_type = 'image/jpeg' resp.content_length = self.library.stat_thumbnail(image).st_size resp.app_iter = FileWrapper(self.library.open_thumbnail(image)) return resp def do_static(self, req, path): """ Serve static files from disk """ path = os.path.normpath(os.path.join(self.static_dir, path)) if not path.startswith(self.static_dir): self.not_found(req) resp = Response() resp.content_type, resp.content_encoding = mimetypes.guess_type( path, strict=False) if resp.content_type is None: resp.content_type = 'application/octet-stream' resp.content_length = os.stat(path).st_size resp.app_iter = FileWrapper(io.open(path, 'rb')) return resp def do_template(self, req, page, image=None): """ Serve a Chameleon template-based page """ resp = Response() resp.content_type = 'text/html' resp.content_encoding = 'utf-8' try: template = self.templates[page] except ValueError: self.not_found(req) resp.text = template(req=req, page=page, image=image, helpers=self.helpers, layout=self.layout, flashes=self.flashes, library=self.library, camera=self.library.camera, router=self.router) del self.flashes[:] return resp
import json from io import BytesIO from functools import partial from urllib import parse from rit.core.web.wsgi import get_application from rit.core.web.urls import all_urls from wheezy.http.response import HTTP_STATUS from wheezy.http import HTTPResponse, HTTPRequest from wheezy.routing import PathRouter from rit.app.conf import settings application = get_application() router = PathRouter() for url in all_urls: router.add_route(*url) HTTP_STATUS_STRING_TO_CODE = dict(zip(HTTP_STATUS.values(), HTTP_STATUS.keys())) class FakePayload(object): """ A wrapper around BytesIO that restricts what can be read since data from the network can't be seeked and cannot be read outside of its content_type length. This makes sure that views can't do anything under the test client that wouldn't work in Real Life. """ def __init__(self, content=None): self.__content = BytesIO() self.__len = 0 self.read_started = False
class PicroscopyWsgiApp(object): def __init__(self, **kwargs): super().__init__() self.library = PicroscopyLibrary(**kwargs) self.helpers = WebHelpers(self.library) self.clients = kwargs.get('clients', IPv4Network('0.0.0.0/0')) logging.info('Clients must be on network %s', self.clients) self.static_dir = os.path.abspath(os.path.normpath(kwargs.get( 'static_dir', os.path.join(HERE, 'static') ))) logging.info('Static files: %s', self.static_dir) self.templates_dir = os.path.abspath(os.path.normpath(kwargs.get( 'templates_dir', os.path.join(HERE, 'templates') ))) logging.info('Chameleon templates: %s', self.templates_dir) self.templates = PageTemplateLoader( self.templates_dir, default_extension='.pt') self.layout = self.templates['layout'] # No need to make flashes a per-session thing - it's a single user app! self.flashes = [] self.router = PathRouter() # XXX Add handler for exiting system # XXX Make exit code conditional? (upgrade/reboot/shutdown/etc.) self.router.add_routes([ url('/', self.do_template, kwargs={'page': 'library'}, name='home'), url('/{page}.html', self.do_template, name='template'), url('/view/{image}.html', self.do_template, kwargs={'page': 'image'}, name='view'), url('/static/{path:any}', self.do_static, name='static'), url('/images/{image}', self.do_image, name='image'), url('/thumbs/{image}', self.do_thumb, name='thumb'), url('/delete/{image}', self.do_delete, name='delete'), url('/config', self.do_config, name='config'), url('/reset', self.do_reset, name='reset'), url('/capture', self.do_capture, name='capture'), url('/download', self.do_download, name='download'), url('/send', self.do_send, name='send'), url('/logout', self.do_logout, name='logout'), ]) def __call__(self, environ, start_response): req = Request(environ) try: if not IPv4Address(req.remote_addr) in self.clients: raise exc.HTTPForbidden() handler, kwargs = self.router.match(req.path_info) if handler: # XXX Why does route_name only appear in kwargs sometimes?! if 'route_name' in kwargs: del kwargs['route_name'] resp = handler(req, **kwargs) else: self.not_found(req) except exc.HTTPException as e: # The exception itself is a WSGI response resp = e return resp(environ, start_response) def not_found(self, req): """ Handler for unknown locations (404) """ raise exc.HTTPNotFound( 'The resource at %s could not be found' % req.path_info) def do_reset(self, req): """ Reset all settings to their defaults """ self.library.camera_reset() self.flashes.append('Camera settings reset to defaults') raise exc.HTTPFound( location=self.router.path_for('template', page='library')) def do_config(self, req): """ Configure the library and camera settings """ # Resolution is handled specially as the camera needs to stop the # preview in order to change it try: new_resolution = tuple( int(i) for i in req.params['resolution'].split('x', 1)) if len(new_resolution) != 2: raise ValueError() except ValueError: self.flashes.append( 'Invalid resolution: %s' % req.params['resolution']) if self.library.camera.resolution != new_resolution: try: self.library.camera.stop_preview() try: self.library.camera.resolution = new_resolution finally: self.library.camera.start_preview() except PiCameraError: self.flashes.append( 'Unable to change camera resolution ' 'to %s' % req.params['resolution']) # Everything else is handled generically... for setting in ( 'sharpness', 'contrast', 'brightness', 'saturation', #'ISO', 'exposure-compensation'): try: setattr( self.library.camera, setting.replace('-', '_'), int(req.params[setting]) ) except ValueError: self.flashes.append( 'Invalid %s: %s' % (setting, req.params[setting])) for setting in ('hflip', 'vflip'): try: setattr( self.library.camera, setting.replace('-', '_'), bool(req.params.get(setting, 0)) ) except ValueError: self.flashes.append( 'Invalid %s: %s' % (setting, req.params[setting])) for setting in ('meter-mode', 'awb-mode', 'exposure-mode'): try: setattr( self.library.camera, setting.replace('-', '_'), req.params[setting] ) except ValueError: self.flashes.append( 'Invalid %s: %s' % (setting, req.params[setting])) for setting in ( 'artist', 'email', 'copyright', 'description', 'filename-template', 'format'): try: setattr( self.library, setting.replace('-', '_'), req.params[setting] ) except ValueError: self.flashes.append( 'Invalid %s: %s' % (setting, req.params[setting])) # If any settings failed, re-render the settings form if self.flashes: return self.do_template(req, 'settings') raise exc.HTTPFound( location=self.router.path_for('template', page='library')) def do_capture(self, req): """ Take a new image with the camera and add it to the library """ self.library.capture() raise exc.HTTPFound( location=self.router.path_for('template', page='library')) def do_download(self, req): """ Send the library as a .zip archive """ archive = self.library.archive() size = archive.seek(0, io.SEEK_END) archive.seek(0) resp = Response() resp.content_type = 'application/zip' resp.content_length = size resp.content_disposition = 'attachment; filename=images.zip' resp.app_iter = FileWrapper(archive) return resp def do_send(self, req): """ Send the library as a set of attachments to an email """ self.library.send() self.flashes.append('Email sent to %s' % library.email) raise exc.HTTPFound( location=self.router.path_for('template', page='library')) def do_delete(self, req, image): """ Delete the selected images from library """ self.library.remove(image) raise exc.HTTPFound( location=self.router.path_for('template', page='library')) def do_logout(self, req): """ Clear the library of all images, reset all settings """ self.library.clear() self.library.user_reset() self.library.camera_reset() raise exc.HTTPFound( location=self.router.path_for('template', page='settings')) def do_image(self, req, image): """ Serve an image from the library """ if not image in self.library: self.not_found(req) resp = Response() resp.content_type, resp.content_encoding = mimetypes.guess_type( image, strict=False) resp.content_length = self.library.stat_image(image).st_size resp.app_iter = FileWrapper(self.library.open_image(image)) return resp def do_thumb(self, req, image): """ Serve a thumbnail of an image from the library """ if not image in self.library: self.not_found(req) resp = Response() resp.content_type = 'image/jpeg' resp.content_length = self.library.stat_thumbnail(image).st_size resp.app_iter = FileWrapper(self.library.open_thumbnail(image)) return resp def do_static(self, req, path): """ Serve static files from disk """ path = os.path.normpath(os.path.join(self.static_dir, path)) if not path.startswith(self.static_dir): self.not_found(req) resp = Response() resp.content_type, resp.content_encoding = mimetypes.guess_type( path, strict=False) if resp.content_type is None: resp.content_type = 'application/octet-stream' resp.content_length = os.stat(path).st_size resp.app_iter = FileWrapper(io.open(path, 'rb')) return resp def do_template(self, req, page, image=None): """ Serve a Chameleon template-based page """ resp = Response() resp.content_type = 'text/html' resp.content_encoding = 'utf-8' try: template = self.templates[page] except ValueError: self.not_found(req) resp.text = template( req=req, page=page, image=image, helpers=self.helpers, layout=self.layout, flashes=self.flashes, library=self.library, camera=self.library.camera, router=self.router) del self.flashes[:] return resp
class MqttClient(MqttMessages): """ Example MqttClient for Application Frameworks (such as Microdrop) Used with the following broker: https://github.com/sci-bots/microdrop-3.0/blob/master/MoscaServer.js """ def __init__(self, host="localhost", port=1883, base="microdrop"): super().__init__() self.router = PathRouter() self.host = host self.port = port self.base = base self.subscriptions = [] self.client_id = self.ClientID() self.client = self.Client() self.connected = False @property def filepath(self): try: return os.path.dirname(inspect.getfile(self.__class__)) except: return 'unknown' @property def name(self): safe_chars = '~@#$&()*!+=:;,.?/\'' return urllib.parse.quote(camelToSnake(self.__class__.__name__), safe=safe_chars) @property def version(self): return '0.0' def send_message(self, topic, msg={}, retain=False, qos=0, dup=False): message = json.dumps(msg) self.client.publish(topic, message, retain=retain, qos=qos) def on_connect(self, client, userdata, flags, rc): self.connected = True self.listen() self.trigger('start', 'null') def on_disconnect(self, client, userdata, rc): self.connected = False def listen(self): print(f'No listen method implemented for {self.name}') def on_message(self, client, userdata, msg): method, args = self.router.match(msg.topic) try: payload = json.loads(msg.payload) except ValueError: print("Message contains invalid json") print(f'topic: {msg.topic}') payload = None if method: method(payload, args) def wrap_data(self, key, val): msg = {} if isinstance(val, dict) and val is not None: msg = val else: msg[key] = val msg['__head__'] = self.DefaultHeader() return msg def Client(self, keepalive=60): client = mqtt.Client(self.client_id) client.on_connect = self.on_connect client.on_message = self.on_message client.on_disconnect = self.on_disconnect client.connect(host=self.host, port=self.port) client.loop_start() return client def ClientID(self): timestamp = str(time.time()).replace(".", "") randnum = random.randint(1, 1000) return f'{self.name}>>{self.filepath}>>{timestamp}.{randnum}' def DefaultHeader(self): header = {} header['plugin_name'] = self.name header['plugin_version'] = self.version return header
class MicropedeClient(Topics): """ Python based client for Micropede Application Framework Used with the following broker: https://github.com/The-Brainery/SciCAD/blob/master/MoscaServer.js """ def __init__(self, app_name, host="localhost", port=None, name=None, version='0.0.0', loop=None): if (app_name is None): raise ("app_name is undefined") if (port is None): port = 1884 if (name is None): name = get_class_name(self) self.router = PathRouter() client_id = generate_client_id(name, app_name) self.__listen = _.noop self.app_name = app_name self.client_id = client_id self.name = name self.schema = {} self.subscriptions = [] self.host = host self.port = port self.version = version self.last_message = None self.loop = None self.safe = None self.client = None if (loop == None): # Create thread to run event loop def start_loop(x): x.loop = asyncio.new_event_loop() x.loop.call_soon_threadsafe(x.ready_event.set) x.loop.run_forever() # Initialize loop and pass reference to main thread class X(object): pass X.ready_event = threading.Event() t = Thread(target=start_loop, args=(X, )) t.start() X.ready_event.wait() self.loop = X.loop else: self.loop = loop self.safe = safe(self.loop) # Start client self.wait_for(self.connect_client(client_id, host, port)) def wait_for(self, f): if (isinstance(f, (asyncio.Future, types.CoroutineType))): asyncio.ensure_future(f, loop=self.loop) def wrap(self, func): return lambda *args, **kwargs: self.wait_for(func(*args, **kwargs)) @property def is_plugin(self): return not _.is_equal(self.listen, _.noop) @property def listen(self): return self.__listen @listen.setter def listen(self, val): self.__listen = val def exit(self, *args, **kwargs): pass def add_binding(self, channel, event, retain=False, qos=0, dup=False): return self.on( event, lambda d: self.send_message(channel, d, retain, qos, dup)) def get_schema(self, payload, name): LABEL = f'{self.app_name}::get_schema' return self.notify_sender(payload, self.schema, 'get-schema') def validate_schema(self, payload): return validate(payload, self.schema) def ping(self, payload, params): return self.notify_sender(payload, "pong", "ping") def add_subscription(self, channel, handler): path = channel_to_route_path(channel) sub = channel_to_subscription(channel) route_name = f'{uuid.uuid1()}-{uuid.uuid4()}' future = asyncio.Future(loop=self.loop) try: if self.client._state != mqtt_cs_connected: if (future.done() == False): future.set_exception( Exception( f'Failed to add subscription. ' + + 'Client is not connected {self.name}, {self.channel}' )) return future def add_sub(*args, **kwargs): self.client.on_unsubscribe = _.noop def on_sub(client, userdata, mid, granted_qos): self.client.on_subscribe = _.noop if (future.done() == False): future.set_result('done') self.client.on_subscribe = self.safe(on_sub) self.client.subscribe(sub) if sub in self.subscriptions: self.client.on_unsubscribe = self.safe(add_sub) self.client.unsubscribe(sub) else: self.subscriptions.append(sub) self.router.add_route(path, self.wrap(handler)) add_sub() except Exception as e: if (future.done() == False): future.set_exception(e) return future def remove_subscription(self, channel): sub = channel_to_subscription(channel) future = asyncio.Future(loop=self.loop) def on_unsub(client, userdata, mid): self.client.on_unsubscribe = _.noop _.pull(self.subscriptions, sub) if (future.done() == False): future.set_result('done') self.client.unsubscribe(sub) return future def _get_subscriptions(self, payload, name): LABEL = f'{self.app_name}::get_subscriptions' return self.notify_sender(payload, self.subscriptions, 'get-subscriptions') def notify_sender(self, payload, response, endpoint, status='success'): if (status != 'success'): response = _.flatten_deep(response) receiver = get_receiver(payload) self.send_message( f'{self.app_name}/{self.name}/notify/{receiver}/{endpoint}', wrap_data(None, { 'status': status, 'response': response }, self.name, self.version)) return response def connect_client(self, client_id, host, port, timeout=DEFAULT_TIMEOUT): self.client = mqtt.Client(client_id) future = asyncio.Future(loop=self.loop) def on_connect(client, userdata, flags, rc): if future.done(): return self.subscriptions = [] if self.is_plugin: def on_done_1(d): def on_done_2(d): if future.done(): return self.listen() self.default_sub_count = len(self.subscriptions) self.client.on_disconnect = self.safe(self.exit) future.set_result('done') f2 = self.on_trigger_msg("exit", self.safe(self.exit)) f2.add_done_callback(self.safe(on_done_2)) # TODO: Run futures sequentially self.on_trigger_msg("ping", self.safe(self.ping)) self.on_trigger_msg("update-version", self.safe(self.update_version)) f = self.on_trigger_msg("get-schema", self.safe(self.get_schema)) self.wait_for(self.set_state('schema', self.schema)) f1 = self.on_trigger_msg("get-subscriptions", self.safe(self._get_subscriptions)) f1.add_done_callback(self.safe(on_done_1)) else: self.listen() self.default_sub_count = 0 if (future.done() == False): future.set_result('done') self.client.on_connect = self.safe(on_connect) self.client.on_message = self.safe(self.on_message) self.client.connect(host=self.host, port=self.port) self.client.loop_start() def on_timeout(): if (future.done() == False): future.set_exception(Exception(f'timeout {timeout}ms')) set_timeout(self.safe(on_timeout), timeout) return future def disconnect_client(self, timeout=DEFAULT_TIMEOUT): future = asyncio.Future(loop=self.loop) self.subscriptions = [] self.router = PathRouter() def off(): if hasattr(self, '_on_off_events'): del self._on_off_events if (_.get(self, 'client._state') != mqtt_cs_connected): off() if (hasattr(self, 'client')): del self.client future.set_result('done') return future else: def on_disconnect(*args, **kwargs): if future.done(): return off() if (hasattr(self, 'client')): del self.client future.set_result('done') if hasattr(self, 'client'): self.client.on_disconnect = self.safe(on_disconnect) self.client.disconnect() set_timeout(self.safe(on_disconnect), timeout) else: if (future.done() == False): future.set_result('done') return future def on_message(self, client, userdata, msg): try: payload = json.loads(msg.payload) except ValueError: print("Message contains invalid json") print(f'topic: {msg.topic}') payload = None topic = msg.topic if (topic is None or topic is ''): return method, args = self.router.match(topic) if method: method(payload, args) def send_message(self, topic, msg={}, retain=False, qos=0, dup=False, timeout=DEFAULT_TIMEOUT): future = asyncio.Future(loop=self.loop) if (_.is_dict(msg) and _.get(msg, '__head__') is None): head = wrap_data(None, None, self.name, self.version)['__head__'] _.set_(msg, '__head__', head) message = json.dumps(msg) _mid = None def on_publish(client, userdata, mid): if (mid == _mid): if (future.done() == False): future.set_result('done') def on_timeout(): if (future.done() == False): future.set_exception(Exception(f'timeout {timeout}ms')) self.client.on_publish = self.safe(on_publish) (qos, _mid) = self.client.publish(topic, payload=message, qos=qos, retain=retain) set_timeout(self.safe(on_timeout), timeout) return future async def dangerously_set_state(self, key, value, plugin): """ Dangerously set the state of another plugin (skip validation) key: str value: any plugin: str """ plugin = plugin or self.name topic = f'{self.app_name}/{plugin}/state/{key}' await self.send_message(topic, value, True, 0, False) async def set_state(self, key, value): topic = f'{self.app_name}/{self.name}/state/{key}' await self.send_message(topic, value, True, 0, False) def update_version(self, payload, params): try: state = payload["state"] storage_version = payload["storageVersion"] plugin_version = payload["pluginVersion"] state = self._update_version(state, storage_version, plugin_version) return self.notify_sender(payload, state, "update-version") except Exception as e: stack = dump_stack(self.name, e) return self.notify_sender(payload, stack, "update-version", "failed") @staticmethod def _version(cls): # Override Me! return "0.0.0" @staticmethod def _update_version(cls, state, storageVersion, pluginVersion): # Override Me! return state
import json from io import BytesIO from functools import partial from urllib import parse from rit.core.web.wsgi import get_application from rit.core.web.urls import all_urls from wheezy.http.response import HTTP_STATUS from wheezy.http import HTTPResponse, HTTPRequest from wheezy.routing import PathRouter from rit.app.conf import settings application = get_application() router = PathRouter() for url in all_urls: router.add_route(*url) HTTP_STATUS_STRING_TO_CODE = dict(zip(HTTP_STATUS.values(), HTTP_STATUS.keys())) class FakePayload(object): """ A wrapper around BytesIO that restricts what can be read since data from the network can't be seeked and cannot be read outside of its content_type length. This makes sure that views can't do anything under the test client that wouldn't work in Real Life. """ def __init__(self, content=None): self.__content = BytesIO() self.__len = 0
class PololuRobotWebControlApp(object): def __init__(self, **kwargs): self.form = WebControlFormHelper(**kwargs) self.logger = kwargs.get('logger',) self.robot = kwargs.get('robot') #initialize speed in form with robot speed self.form.speed = self.robot.setDriveSpeed #create routes to web pages self.router = PathRouter() self.router.add_routes([ url('/', self.do_main_page, name='home'), url('/doRobotControl', self.do_process_form, name='execute'), url('/showRobotControlForm', self.do_display_form, name='view') ]) def __call__(self, environ, start_response): req = Request(environ) try: handler, kwargs = self.router.match(req.path_info) if handler: # XXX Why does route_name only appear in kwargs sometimes?! if 'route_name' in kwargs: del kwargs['route_name'] resp = handler(req, **kwargs) else: self.not_found(req) except exc.HTTPException as e: # The exception itself is a WSGI response resp = e return resp(environ, start_response) def not_found(self, req): """Handler for unknown locations (404)""" raise exc.HTTPNotFound('The resource at %s could not be found' % req.path_info) #------------------------------------------------------------------------------# # do_main_page: load the mainpage. The main page contains an iFrame which # # triggers the consecutive loading of the control form # # The main page contains a link to mjpgStreamServer, which shows # # a video stream from the attached raspicam. The url for this # # server is configured in etc/config.ini # #------------------------------------------------------------------------------# # version who when description # # 1.00 hta 15.05.2014 Initial version # #------------------------------------------------------------------------------# def do_main_page(self, req): res = Response() res.content_type = 'text/html' res.text = self.form.mainPage % {'mjpgStreamServer':self.form.mjpgStreamServer} return res #------------------------------------------------------------------------------# # do_display_form: the control form, and set the color of the button text and # # set the message to be shown to the user (if any) # # parameters: message: Text message to be shown to user # # speed : Speed, value for the slider bar # # toggleRovingButtonText: Roving OFF, Roving ON # # backwardButtonColor, forwardButtonColor, leftButtonColor, # # rightButtonColor, stopButtonColor, toggleRovingButtonColor: # # Color for the text on the control buttons. The purpose # # is to highlight the currently active action suchs as # # forward, left etc. # #------------------------------------------------------------------------------# # version who when description # # 1.00 hta 15.05.2014 Initial version # #------------------------------------------------------------------------------# def do_display_form(self, req): res = Response() res.content_type = 'text/html' res.text = self.form.controlForm % {'message':self.form.message, 'speed': self.form.speed, 'backwardButtonColor': self.form.backwardButtonColor, 'forwardButtonColor': self.form.forwardButtonColor, 'leftButtonColor': self.form.leftButtonColor, 'rightButtonColor': self.form.rightButtonColor, 'stopButtonColor': self.form.stopButtonColor, 'toggleRovingButtonColor': self.form.toggleRovingButtonColor, 'toggleRovingButtonText': self.form.toggleRovingButtonText} return res #------------------------------------------------------------------------------# # process_form: Processes the user input from the control form, executes the # # desired action (e.g. forward, backward) and give feedback to # # the user indicating what action has been taken # # is highlighted # # parameters: params: dictionary object containing parameters and their values # # from the form. # # action: possbile values: forward, backward, left, right, # # stop, setSpeed # # speed: possible values: 0-127 #------------------------------------------------------------------------------# # version who when description # # 1.00 hta 15.05.2014 Initial version # #------------------------------------------------------------------------------# def do_process_form(self, req): action = req.params['action'] speedSliderValue = req.params['speed'] self.logger.debug('action['+action+'] speedSliderValue['+speedSliderValue+']') if action == 'forward': self.form.message='Going '+action self.form.setButtonColors(backward=False, forward=True, left=False, right=False, stop=False, roving=False) self.robot.stopRoving() self.robot.driveForwards() elif action == 'backward': self.form.message='Going '+action self.form.setButtonColors(backward=True, forward=False, left=False, right=False, stop=False, roving=False) self.robot.stopRoving() self.robot.driveBackwards() elif action == 'left': self.form.message='Turning '+action self.form.setButtonColors(backward=False, forward=False, left=True, right=False, stop=False, roving=False) self.robot.stopRoving() self.robot.turnLeft() elif action == 'right': self.form.message='Turning '+action self.form.setButtonColors(backward=False, forward=False, left=False, right=True, stop=False, roving=False) self.robot.stopRoving() self.robot.turnRight() elif action == 'setSpeed': self.form.message='Setting speed to '+speedSliderValue self.form.speed = int(speedSliderValue) self.robot.setSpeed(self.form.speed) elif action == 'stop': self.form.message='Stopping' self.form.setButtonColors(backward=False, forward=False, left=False, right=False, stop=True, roving=False) self.robot.stop() if self.robot.isRoving: self.robot.stopRoving() elif action == 'roving': #Toggle roving, of roving on then turn it of and vice versa if self.robot.isRoving: self.form.message='End roving' self.form.setButtonColors(backward=False, forward=False, left=False, right=False, stop=False, roving=False) self.robot.stopRoving() else: self.form.message='Start roving' self.form.setButtonColors(backward=False, forward=False, left=False, right=False, stop=False, roving=True) self.robot.runRoving() #return updated control form to web client return self.do_display_form (req)