Example #1
0
    time.sleep(TIME_FOR_WAVEFORM + 2)

    # Strings we'll recognize for the plotting commands. This is pretty
    #   rudimentary and can be improved with some better parsing/processing/NLP
    sinx_strings = ["show sin", "show sign", "show sine"]
    cosx_strings = [
        "show cos", "show cosine", "show coast", "show coats", "show cosign"
    ]
    tanx_strings = ["show tan", "showtime"]

    print("listening..")
    last_id = element._get_redis_timestamp()

    while True:
        entries = element.entry_read_since("voice",
                                           "string",
                                           last_id=last_id,
                                           block=1000)
        if entries:
            last_id = entries[0]["id"]
            #voice_string = entries[0]["data"].decode().strip().lower().replace("-", "").split(" ")
            voice_string = entries[0]["data"].decode().lower()
            print("Got voice string {}".format(voice_string))

            if any(x in voice_string for x in sinx_strings):

                print("Plotting sinx")
                data = {
                    "name": "example",
                    "msgpack": True,
                    "plots": [{
                        "data": [["x", ["sin"], "value"]]
Example #2
0
class AtomCLI:

    def __init__(self):
        self.element = Element(f"atom-cli_{uname().nodename}_{uuid4().hex}")
        self.indent = 2
        self.style = Style.from_dict({
            "logo_color": "#6039C8",
        })
        self.session = PromptSession(style=self.style)
        self.print_atom_os_logo()
        self.serialization = "msgpack"
        self.cmd_map = {
            "help": self.cmd_help,
            "list": self.cmd_list,
            "records": self.cmd_records,
            "command": self.cmd_command,
            "read": self.cmd_read,
            "exit": self.cmd_exit,
            "serialization": self.cmd_serialization,
        }
        self.usage = {
            "cmd_help": cleandoc("""
                Displays available commands and shows usage for commands.

                Usage:
                  help [<command>]"""),

            "cmd_list": cleandoc("""
                Displays available elements, streams, or commands.
                Can filter streams and commands based on element.

                Usage:
                  list elements
                  list streams [<element>]
                  list commands [<element>]"""),

            "cmd_records": cleandoc("""
                Displays log records or command and response records.
                Can filter records from the last N seconds or from certain elements.

                Usage:
                  records log [<last_N_seconds>] [<element>...]
                  records cmdres [<last_N_seconds>] <element>..."""),

            "cmd_command": cleandoc("""
                Sends a command to an element and displays the response.

                Usage:
                  command <element> <element_command> [<data>]"""),

            "cmd_read": cleandoc("""
                Displays the entries of an element's stream.
                Can provide a rate to print the entries for ease of reading.

                Usage:
                  read <element> <stream> [<rate_hz>]"""),

            "cmd_exit": cleandoc("""
                Exits the atom-cli tool.
                Can also use the shortcut CTRL+D.

                Usage:
                  exit"""),

            "cmd_serialization": cleandoc("""
                Sets serialization/deserialization setting to either use msgpack,
                Apache arrow, or no (de)serialization. Defaults to msgpack serialization.
                This setting is overriden by deserialization keys received in stream.

                Usage:
                  serialization (msgpack | arrow | none)"""),
        }

    def run(self):
        """
        The main loop of the CLI.
        Reads the user input, verifies the command exists and calls the command.
        """
        while True:
            try:
                inp = self.session.prompt(
                    "\n> ", auto_suggest=AutoSuggestFromHistory()).split(" ")
                if not inp:
                    continue
                command, args = inp[0], inp[1:]
                if command not in self.cmd_map.keys():
                    print("Invalid command. Type 'help' for valid commands.")
                else:
                    self.cmd_map[command](*args)
            # Handle CTRL+C so user can break loops without exiting
            except KeyboardInterrupt:
                pass
            # Exit on CTRL+D
            except EOFError:
                self.cmd_exit()
            except Exception as e:
                print(str(type(e)) + " " + str(e))

    def print_atom_os_logo(self):
        f = Figlet(font="slant")
        logo = f.renderText("ATOM OS")
        print(HTML(f"<logo_color>{logo}</logo_color>"), style=self.style)

    def format_record(self, record):
        """
        Takes a record out of Redis, decodes the keys and values (if possible)
        and returns a formatted json string sorted by keys.
        """
        formatted_record = {}
        for k, v in record.items():
            if type(k) is bytes:
                k = k.decode()
            if not self.serialization:
                try:
                    v = v.decode()
                except:
                    v = str(v)
            formatted_record[k] = v

        sorted_record = {k: v for k, v in sorted(
            formatted_record.items(), key=lambda x: x[0])}
        try:
            ret = json.dumps(sorted_record, indent=self.indent)
        except TypeError as te:
            ret = sorted_record
        finally:
            return ret

    def cmd_help(self, *args):
        usage = self.usage["cmd_help"]
        if len(args) > 1:
            print(usage)
            print("\nToo many arguments to 'help'.")
            return
        if args:
            # Prints the usage of the command
            if args[0] in self.cmd_map.keys():
                print(self.usage[f"cmd_{args[0]}"])
            else:
                print(f"Command {args[0]} does not exist.")
        else:
            print("Try 'help <command>' for usage on a command")
            print("Available commands:")
            for command in self.cmd_map.keys():
                print(f"  {command}")

    def cmd_list(self, *args):
        usage = self.usage["cmd_list"]
        mode_map = {
            "elements": self.element.get_all_elements,
            "streams": self.element.get_all_streams,
            "commands": self.element.get_all_commands
        }
        if not args:
            print(usage)
            print("\n'list' must have an argument.")
            return
        mode = args[0]
        if mode not in mode_map.keys():
            print(usage)
            print("\nInvalid argument to 'list'.")
            return
        if len(args) > 1 and mode == "elements":
            print(usage)
            print(f"\nInvalid number of arguments for command 'list elements'.")
            return
        if len(args) > 2:
            print(usage)
            print("\n'list' takes at most 2 arguments.")
            return
        items = mode_map[mode](*args[1:])
        if not items:
            print(f"No {mode} exist.")
            return
        for item in items:
            print(item)

    def cmd_records(self, *args):
        usage = self.usage["cmd_records"]
        if not args:
            print(usage)
            print("\n'records' must have an argument.")
            return
        mode = args[0]

        # Check for start time
        if len(args) > 1 and args[1].isdigit():
            ms = int(args[1]) * 1000
            start_time = str(int(self.element._get_redis_timestamp()) - ms)
            elements = set(args[2:])
        # If no start time, go from the very beginning
        else:
            start_time = "0"
            elements = set(args[1:])

        if mode == "log":
            records = self.mode_log(start_time, elements)
        elif mode == "cmdres":
            if not elements:
                print(usage)
                print(
                    "\nMust provide elements from which to get command response streams from.")
                return
            records = self.mode_cmdres(start_time, elements)
        else:
            print(usage)
            print("\nInvalid argument to 'records'.")
            return

        if not records:
            print("No records.")
            return
        for record in records:
            print(self.format_record(record))

    def mode_log(self, start_time, elements):
        """
        Reads the logs from Atom's log stream.

        Args:
            start_time (str): The time from which to start reading logs.
            elements (list): The elements on which to filter the logs for.
        """
        records = []
        all_records = self.element.entry_read_since(
            None, "log", start_time, serialization=None)
        for record in all_records:
            if not elements or record["element"].decode() in elements:
                record = {key: (value if isinstance(value, str) else value.decode(
                )) for key, value in record.items()}  # Decode strings only which are required to
                records.append(record)
        return records

    def mode_cmdres(self, start_time, elements):
        """
        Reads the command and response records from the provided elements.
        Args:
            start_time (str): The time from which to start reading logs.
            elements (list): The elements to get the command and response records from.
        """
        streams, records = [], []
        for element in elements:
            streams.append(self.element._make_response_id(element))
            streams.append(self.element._make_command_id(element))
        for stream in streams:
            cur_records = self.element.entry_read_since(
                None, stream, start_time, serialization=None)
            for record in cur_records:
                for key, value in record.items():
                    try:
                        if not isinstance(value, str):
                            value = value.decode()
                    except:
                        try:
                            value = ser.deserialize(value, method=self.serialization)
                        except:
                            pass

                    finally:
                        record[key] = value
                record["type"], record["element"] = stream.split(":")
                records.append(record)
        return sorted(records, key=lambda x: (x["id"], x["type"]))

    def cmd_command(self, *args):
        usage = self.usage["cmd_command"]
        if len(args) < 2:
            print(usage)
            print("\nToo few arguments.")
            return
        element_name = args[0]
        command_name = args[1]
        if len(args) >= 3:
            data = str(" ".join(args[2:]))
            if self.serialization:
                try:
                    data = json.loads(data)
                except:
                    print("Received improperly formatted data!")
                    return
        else:
            data = ""
        resp = self.element.command_send(element_name,
                                         command_name,
                                         data,
                                         serialize=(self.serialization is not None),
                                         deserialize=(self.serialization is not None),
                                         serialization=self.serialization)  # shouldn't be used if it's None
        print(self.format_record(resp))

    def cmd_read(self, *args):
        usage = self.usage["cmd_read"]
        if len(args) < 2:
            print(usage)
            print("\nToo few arguments.")
            return
        if len(args) > 3:
            print(usage)
            print("\nToo many arguments.")
            return
        if len(args) == 3:
            try:
                rate = float(args[2])
                if rate < 0:
                    raise ValueError()
            except ValueError:
                print("rate must be an float greater than 0.")
                return
        else:
            rate = None
        element_name, stream_name = args[:2]

        last_timestamp = None
        while True:
            start_time = time.time()
            entries = self.element.entry_read_n(element_name,
                                                stream_name,
                                                1,
                                                deserialize=(self.serialization is not None),
                                                serialization=self.serialization)  # shouldn't be used if it's None
            if not entries:
                print(f"No data from {element_name} {stream_name}.")
                return
            entry = entries[0]
            timestamp = entry["id"]
            # Only print the entry if it is different from the previous one
            if timestamp != last_timestamp:
                last_timestamp = timestamp
                print(self.format_record(entry))
            if rate:
                time.sleep(max(1 / rate - (time.time() - start_time), 0))

    def cmd_serialization(self, *args):
        usage = self.usage["cmd_serialization"]
        if (len(args) != 1):
            print(usage)
            print(f"\nPass one argument: {ser.Serializations.print_values()}.")
            return

        # Otherwise try to get the new setting
        if ser.is_valid_serialization(args[0].lower()):
            self.serialization = args[0].lower() if args[0].lower() != "none" else None
        else:
            print(f"\nArgument must be one of {ser.Serializations.print_values()}.")

        print("Current serialization status is {}".format(self.serialization))

    def cmd_exit(*args):
        print("Exiting.")
        sys.exit()
Example #3
0
def record_fn(name, n_entries, n_sec, perm, element, stream):
    '''
    Mainloop for a recording thread. Creates a new
    element with the proper name and listens on and
    records the stream until we're told to stop
    '''
    global active_recordings

    # Make an element from the name
    record_elem = Element("record_" + name)

    # Open the file for the recording
    filename = os.path.join(PERM_RECORDING_LOC if perm else TEMP_RECORDING_LOC,
                            name + RECORDING_EXTENSION)
    try:
        record_file = open(filename, 'wb')
    except:
        record_elem.log(LogLevel.ERR,
                        "Unable to open file {}".format(filename))
        del active_recordings[name]
        return

    # At the outer loop, we want to loop until we've been cancelled
    last_id = "$"
    intervals = 0
    entries_read = 0
    while name in active_recordings:

        # Read the data
        data = record_elem.entry_read_since(element,
                                            stream,
                                            last_id,
                                            n=n_entries,
                                            block=BLOCK_MS)

        # If we got no data, then we should finish up
        if len(data) == 0:
            record_elem.log(
                LogLevel.ERR,
                "Recording {}: no data after {} entries read!".format(
                    name, entries_read))
            break

        entries_read += len(data)

        # We're going to pack up each entry into a msgpack item and
        #   then write it to the file. If it's already msgpack'd
        #   that's totally fine, this will just pack up the keys and ID
        for entry in data:
            packed_data = msgpack.packb(entry, use_bin_type=True)

            # Write the packed data to file
            record_file.write(packed_data)

        # If n_entries is not none then we want to subtract
        #   off the number of entries left and perhaps break out
        if n_entries is not None:
            n_enties -= len(data)
            if (n_enties <= 0):
                break

        # Otherwise see if we've recorded for longer than our
        #   elapsed time
        else:
            intervals += 1
            if (intervals * POLL_INTERVAL) >= n_sec:
                break

        # If we got here, we should sleep for the interval before
        #   making the next call
        time.sleep(POLL_INTERVAL)

        # And update the last ID
        last_id = data[-1]["id"]

    # Once we're out of here we want to note that we're no longer
    #   active in the global system. It might be that someone else popped
    #   it out through already in the "stop" command
    if name in active_recordings:
        thread = active_recordings.pop(name)

    # And we want to close the file
    record_file.close()

    # And log that we completed the recording
    record_elem.log(
        LogLevel.INFO, "Finished recording {} with {} entries read".format(
            name, entries_read))