diff --git a/scada_system/noblocking_turtle_shell.py b/scada_system/noblocking_turtle_shell.py new file mode 100644 index 0000000..0d6cc6b --- /dev/null +++ b/scada_system/noblocking_turtle_shell.py @@ -0,0 +1,66 @@ +import cmd +import threading + +from queue import Queue + +from equipment.turtle_device import TurtleDevice + + +class TurtleDeviceThread(threading.Thread): + def __init__(self): + super().__init__() + self.device = TurtleDevice() + self.queue = Queue() + + self.device.open() + + def add_task(self, action: str, *args, **kwargs): + self.queue.put((action, args, kwargs)) + + def run(self): + while True: + action, args, kwargs = self.queue.get() + if action == "stop": + self.queue.task_done() + break + self.device.execute(action, *args, **kwargs) + self.queue.task_done() + + +class NoBlockingTurtleShell(cmd.Cmd): + intro = 'Welcome to the turtle shell. Type help or ? to list commands.\n' + prompt = '(turtle) ' + file = None + + def __init__(self, turtle_thread: TurtleDeviceThread): + super().__init__() + self.turtle_thread = turtle_thread + + def do_execute(self, arg): + 'Executes a command with the given arguments: EXECUTE forward 100' + command_and_args = parse(arg) + if len(command_and_args) == 1: + self.turtle_thread.add_task(command_and_args[0]) + return None + + self.turtle_thread.add_task(command_and_args[0], *command_and_args[1:]) + + def do_exit(self, arg): + 'Stops the message processing thread: EXIT' + print('The message processing thread has stopped.') + self.turtle_thread.add_task('stop') + return True + +def parse(arg): + 'Convert a series of zero or more numbers to an argument tuple' + def perform_elem(elem: str): + return int(elem) if elem.isdecimal() else elem + return tuple(map(perform_elem, arg.split())) + +if __name__ == '__main__': + turtle_thread = TurtleDeviceThread() + turtle_thread.daemon = True + thread_shell = threading.Thread(target=NoBlockingTurtleShell(turtle_thread).cmdloop) + thread_shell.start() + turtle_thread.run() + thread_shell.join() \ No newline at end of file diff --git a/scada_system/turtle_shell.py b/scada_system/turtle_shell.py new file mode 100644 index 0000000..ac22aab --- /dev/null +++ b/scada_system/turtle_shell.py @@ -0,0 +1,74 @@ +import cmd, sys +from turtle import * + + +class TurtleShell(cmd.Cmd): + intro = 'Welcome to the turtle shell. Type help or ? to list commands.\n' + prompt = '(turtle) ' + file = None + + # ----- basic turtle commands ----- + def do_forward(self, arg): + 'Move the turtle forward by the specified distance: FORWARD 10' + forward(*parse(arg)) + def do_right(self, arg): + 'Turn turtle right by given number of degrees: RIGHT 20' + right(*parse(arg)) + def do_left(self, arg): + 'Turn turtle left by given number of degrees: LEFT 90' + left(*parse(arg)) + def do_goto(self, arg): + 'Move turtle to an absolute position with changing orientation. GOTO 100 200' + goto(*parse(arg)) + def do_home(self, arg): + 'Return turtle to the home position: HOME' + home() + def do_circle(self, arg): + 'Draw circle with given radius an options extent and steps: CIRCLE 50' + circle(*parse(arg)) + def do_position(self, arg): + 'Print the current turtle position: POSITION' + print('Current position is %d %d\n' % position()) + def do_heading(self, arg): + 'Print the current turtle heading in degrees: HEADING' + print('Current heading is %d\n' % (heading(),)) + def do_color(self, arg): + 'Set the color: COLOR BLUE' + color(arg.lower()) + def do_undo(self, arg): + 'Undo (repeatedly) the last turtle action(s): UNDO' + def do_reset(self, arg): + 'Clear the screen and return turtle to center: RESET' + reset() + def do_bye(self, arg): + 'Stop recording, close the turtle window, and exit: BYE' + print('Thank you for using Turtle') + self.close() + bye() + return True + + # ----- record and playback ----- + def do_record(self, arg): + 'Save future commands to filename: RECORD rose.cmd' + self.file = open(arg, 'w') + def do_playback(self, arg): + 'Playback commands from a file: PLAYBACK rose.cmd' + self.close() + with open(arg) as f: + self.cmdqueue.extend(f.read().splitlines()) + def precmd(self, line): + line = line.lower() + if self.file and 'playback' not in line: + print(line, file=self.file) + return line + def close(self): + if self.file: + self.file.close() + self.file = None + +def parse(arg): + 'Convert a series of zero or more numbers to an argument tuple' + return tuple(map(int, arg.split())) + +if __name__ == '__main__': + TurtleShell().cmdloop() \ No newline at end of file