class ScriptHost(object): def __init__(self): self._script = "" self.is_running = False self.ui = ScriptUI() # Rising/falling edge dict self._rising_dict = {} self._falling_dict = {} self.runtime = None self.runtime_thread = None # Callbacks self.on_print = None self.on_error = None self.on_start = None self.on_stop = None def __del__(self): if self.is_running: self.stop_script() def setup_runtime(self): """ Creates a new lua runtime and initializes all globals. Used by start_script(), should not be called directly. """ # Create new lua instance self.runtime = lupa.LuaRuntime(unpack_returned_tuples=True) # Reset keys and button states self.ui._keys = {} self.ui._buttons = {} # Reset rising/falling edge dict self._rising_dict = {} self._falling_dict = {} # Set up API g = self.runtime.globals() g["Sound"] = Sound g["Expression"] = Expression g["Hardware"] = LuaHardware(self.runtime) g["Animate"] = LuaAnimate g["AnimatePeriodic"] = LuaAnimatePeriodic g["UI"] = self.ui g["print"] = callback(self.on_print) g["sleep"] = self._sleep g["rising_edge"] = self._rising_edge g["falling_edge"] = self._falling_edge g["seconds"] = time.time # Basic Arduino functions g["delay"] = lambda t: self._sleep(t / 1000) g["min"] = min g["max"] = max g["abs"] = abs g["constrain"] = lambda x, a, b: max(a, min(x, b)) g["map"] = lambda x, in_min, in_max, out_min, out_max: (x - in_min) * ( out_max - out_min) / (in_max - in_min) + out_min g["millis"] = lambda: time.time() * 1000.0 def start_script(self, script): """ Start a new script. This method will create a new runtime, pass the script to the runtime, and start a thread to continuously call the script's loop function. Can only be used if no other script is running. """ # Check if running if self.is_running: raise RuntimeError("A script is already running!") self._script = script callback(self.on_start)() self.is_running = True # Initialize a new runtime self.setup_runtime() self.runtime_thread = StoppableThread(target=self._run) self.runtime_thread.start() def stop_script(self): """ Attempts to stop the current script. Returns immediately if no script is running. If a script is running, this method will send a stop signal to to the script thread, and then block until the thread is stopped. Note that the thread's stopped condition is only checked during sleep() and at the end of loop() calls, this function will not stop infinite loops. """ if self.is_running and self.runtime_thread is not None: self.runtime_thread.stop() self.runtime_thread.join() def generate_lua_error(self, message): """ If a script is running, this method will generate an error inside the script. Useful to signal script errors (e.g. bad parameter) to the user. """ if self.is_running and self.runtime is not None: g = self.runtime.globals() g["error"](message) def _report_error(self, e): """ Helper function that prefixes the type of error to the exception, and then sends the error message to the application through the on_error callback. """ if type(e) == lupa.LuaSyntaxError: callback(self.on_error)("Syntax error: %s" % str(e)) elif type(e) == lupa.LuaError: callback(self.on_error)("Lua error: %s" % str(e)) else: exc_type, exc_value, exc_traceback = sys.exc_info() tb_str = "".join(traceback.format_tb(exc_traceback)).replace( "\n", "<br>") callback(self.on_error)("Python error: %s<br>%s" % (str(e), tb_str)) def _sleep(self, time): """ Lua API Sleep function that pauses the thread for a number of seconds. This sleep function will return immediately if the thread's stop flag is set. This means that loop function should come to an end instantaneously, after which the thread is ended. """ if self.runtime_thread is not None: self.runtime_thread.sleep(time) def _rising_edge(self, identifier, status): """ Lua API Helper function to detect a rising edge of a signal (e.g. button, key, capacitive touch pad, etc). Identifier is an arbitrary string that is used to distinguish between different signals. Internally, it's used as a key for the dictionary that keeps track of different signals. Usage: if rising_edge("mybutton", UI:is_key_pressed("up")) then -- Do something end """ last_status = False if identifier in self._rising_dict: last_status = self._rising_dict[identifier] self._rising_dict[identifier] = status return status and not last_status def _falling_edge(self, identifier, status): """ Lua API Helper function to detect a falling edge of a signal (e.g. button, key, capacitive touch pad, etc). Identifier is an arbitrary string that is used to distinguish between different signals. Internally, it's used as a key for the dictionary that keeps track of different signals. Usage: if falling_edge("mybutton", UI:is_key_pressed("up")) then -- Do something end """ last_status = False if identifier in self._falling_dict: last_status = self._falling_dict[identifier] self._falling_dict[identifier] = status return last_status and not status def _remove_lua_overlays(self): for dofname, dof in Expression.dofs.iteritems(): for overlay in dof.overlays: if lupa.lua_type(overlay) is not None: # It's a Lua value, remove it! dof.overlays.remove(overlay) def _run(self): """ Called by the worker thread when the script is run. First attempts to call the script's setup function, then continuously calls the loop function. When the thread's stop flag is set, the loop breaks and the thread attempts to run the quit function. At any time, if the runtime encounters an error, the script is stopped, and the on_error and on_stop callbacks are triggered. """ g = self.runtime.globals() # Evaluate code and run setup try: self.runtime.execute(self._script) if g["setup"] is not None: g["setup"]() except Exception as e: self.runtime_thread.stop() self._report_error(e) if g["loop"] is not None: # Continuously run loop, until thread is stopped while not self.runtime_thread.stopped(): try: g["loop"]() except Exception as e: self._report_error(e) self.runtime_thread.stop() # Delay not really necessary, but can be used to limit CPU time. # Without delay, this loop consumes about 70% CPU time on a RPi1. # else: # # 10ms breathing room between loops # time.sleep(0.01) # Run quit if g["quit"] is not None: try: g["quit"]() except Exception as e: self._report_error(e) callback(self.on_stop)() self._remove_lua_overlays() self.is_running = False
class fb_client(Client): def __init__(self, email=None, password=None, user_agent=None, \ max_tries=5, session_cookies=None, logging_level=logging.INFO): """ Overrides parent constructor to make email and password optional. (This helps because we primarily use session cookies to log users back in) Args: | email: Facebook login email | password: Facebook login password | max_tries: maximum login attempts | session_cookies: Cookies from a previous session | logging_level: Configures the `logging level` \ Defaults to logging.INFO Raises: FBchatException on failed login """ super(fb_client, self).__init__(email=email, password=password, \ max_tries=max_tries, session_cookies=session_cookies, logging_level=logging_level) def _extract_mentions(self, message_text): """ Takes message in raw text form and returns a list of appropriate Mentions objects Args: message_text: message in raw text form Returns: A list of :class:`fbchat.models.Mention` objects """ # Find all occurrences of @ (for offset) index = 0 mention = [] at_indices = [] for c in message_text: if c is '@': at_indices.append(index) index += 1 # used for offset at_count = 0 elements = message_text.split(" ") for element in elements: # look for @s if element.startswith("@"): users = self.searchForUsers(element[1:]) # @ is a valid Mention if len(users) is not 0: user = users[0] mention.append( \ Mention(user.uid, offset=at_indices[at_count], \ length=len(element))) at_count += 1 return (None if len(mention) is 0 else mention) def _construct_message_from_text(self, message_text): """ Takes message in raw text form and constructs a :class:`fbchat.models.Message object` Args: message_text: message in raw text form Returns: :class:`fbchat.models.Message` object representing raw text message input """ mentions = self._extract_mentions(message_text) return Message(text=message_text, mentions=mentions) def send_message(self, message_text, user_uid=None, group_uid=None): """ Sends a appropriately constructed message given raw text message input. Args: | message_text: message in raw text form | user_uid: uid of user | group_uid: uid of group Raises: FBClientError on missing user_uid and group_uid or when both are provided """ if user_uid is None and group_uid is None: raise FBClientError("Must provide a user_uid or group_uid") if user_uid is not None and group_uid is not None: raise FBClientError("Must only provide one of either a " + \ "user_uid or group_uid") if user_uid is not None: self.send(self._construct_message_from_text(message_text), \ thread_id=user_uid, thread_type=ThreadType.USER) elif group_uid is not None: self.send(self._construct_message_from_text(message_text), \ thread_id=group_uid, thread_type=ThreadType.GROUP) def _listen_daemon(self): """ listener that runs until the thread it is part of is terminated. Not a true daemon, but purpose is to make the listening process run on a separate thread. """ self.startListening() self.onListening() while self.thread.is_running() and self.doOneListen(): pass self.stopListening() self.thread.stopped() def start_listen(self): """ Creates and starts a listener thread. Raises: FBClientError if client is already listening """ try: self.thread raise FBClientError("Called start_listen when " + \ "client is already listening") except AttributeError: logging.info('Client is listening...') self.thread = StoppableThread(name="listen", target=self._listen_daemon, args=()) self.thread.start() def stop_listen(self): """ "Terminates" the listener thread. Raises: FBClientError if client has not been listening """ try: self.thread except AttributeError: raise FBClientError("Called stop_listen when " + \ "client was not listening") if self.thread.is_running(): print "Stopping client listen..." self.thread.stop() self.thread.join() logging.info('Client stopped listening') del self.thread