From fb45d7291ca877d9d72825f90f804c0e33614f17 Mon Sep 17 00:00:00 2001 From: Mikhail Zelenyy Date: Fri, 13 Oct 2023 22:33:07 +0300 Subject: [PATCH 1/2] For homework 3 --- controls/__init__.py | 0 controls/device.py | 82 +++++++++++++++++++++++++++ equipment/__init__.py | 0 equipment/turtle_device.py | 8 +++ tests/controls/test_device.py | 12 ++++ tests/equipment/test_turtle_device.py | 13 +++++ 6 files changed, 115 insertions(+) create mode 100644 controls/__init__.py create mode 100644 controls/device.py create mode 100644 equipment/__init__.py create mode 100644 equipment/turtle_device.py create mode 100644 tests/controls/test_device.py create mode 100644 tests/equipment/test_turtle_device.py diff --git a/controls/__init__.py b/controls/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/controls/device.py b/controls/device.py new file mode 100644 index 0000000..d126692 --- /dev/null +++ b/controls/device.py @@ -0,0 +1,82 @@ +from dataclasses import dataclass +from typing import Optional, Collection, Any +from abc import abstractmethod + +class DeviceLifecycleState: + pass # TODO(Homework #3) + + +class DevaceError(Exception): + pass + + +class NonReadableTrait(Exception): + pass + + +class NonWritableTrait(Exception): + pass + + +@dataclass +class TraitDescriptor: + name: str + info: Optional[str] = None + readable: bool = True + writable: bool = False + + +@dataclass +class ActionDescriptor: + name: str + arguments: dict[str, type] + info: Optional[str] = None + + +class Device: + # TODO(Homework #3) + _state = DeviceLifecycleState.INIT + + @property + def state(self) -> DeviceLifecycleState: + return self._state + + def close(self): + self._state = DeviceLifecycleState.CLOSE + + def trait_descriptors(self) -> Collection[TraitDescriptor]: + pass + + def action_descriptors(self) -> Collection[ActionDescriptor]: + pass + + @abstractmethod + def __getitem__(self, trait_name: str) -> Optional[Any]: + """Return logical state of trait `trait_name`.""" + pass + + +class SynchronyDevice(Device): + + def open(self): + self._state = DeviceLifecycleState.OPEN + + @abstractmethod + def execute(self, action_name: str, *args, **kwargs): + """Execute action `action_name`, using `args` and `kwargs` as action argument.""" + pass + + @abstractmethod + def read(self, trait_name: str) -> Any: + """Read physical state of trait `trait_name` from device.""" + raise NonReadableTrait + + @abstractmethod + def write(self, trait_name: str, value: Any) -> bool: + """Pass `value` to trait `trait_name` of device.""" + raise NonWritableTrait + + @abstractmethod + def invalidate(self, trait_name: str): + """Invalidate logical state of trait `trait_name`""" + pass \ No newline at end of file diff --git a/equipment/__init__.py b/equipment/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/equipment/turtle_device.py b/equipment/turtle_device.py new file mode 100644 index 0000000..3ba251f --- /dev/null +++ b/equipment/turtle_device.py @@ -0,0 +1,8 @@ +from turtle import Turtle +from controls.device import SynchronyDevice + +class TurtleDevice(SynchronyDevice): + pass # TODO(Homework #3) + + + diff --git a/tests/controls/test_device.py b/tests/controls/test_device.py new file mode 100644 index 0000000..51249ad --- /dev/null +++ b/tests/controls/test_device.py @@ -0,0 +1,12 @@ +from unittest import TestCase + +from controls.device import DeviceLifecycleState + + +class DeviceLifecycleStateTest(TestCase): + + def setUp(self) -> None: + pass + + def test_enum(self): + self.assertEqual(DeviceLifecycleStateTest["INIT"], DeviceLifecycleStateTest.INIT) diff --git a/tests/equipment/test_turtle_device.py b/tests/equipment/test_turtle_device.py new file mode 100644 index 0000000..a12912b --- /dev/null +++ b/tests/equipment/test_turtle_device.py @@ -0,0 +1,13 @@ +from unittest import TestCase + +from equipment.turtle_device import TurtleDevice + + +class TurtleDeviceTest(TestCase): + + def setUp(self) -> None: + self.device = TurtleDevice() + + def test_open(self): + self.device.open() + self.device.close() \ No newline at end of file From e26b8e541bb44ede923f8903945f1f9eed83faab Mon Sep 17 00:00:00 2001 From: Mikhail Zelenyy Date: Sat, 4 Nov 2023 19:05:45 +0300 Subject: [PATCH 2/2] For homework 4 --- noblocking_turtle_shell.py | 35 ++++++++++++++++++ turtle_shell.py | 74 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 noblocking_turtle_shell.py create mode 100644 turtle_shell.py diff --git a/noblocking_turtle_shell.py b/noblocking_turtle_shell.py new file mode 100644 index 0000000..394fe37 --- /dev/null +++ b/noblocking_turtle_shell.py @@ -0,0 +1,35 @@ +import cmd +import threading + +from queue import Queue + +from equipment.turtle_device import TurtleDevice + + +class TurtleDeviceThread(threading.Thread): + # TODO(Homework 4) + def __init__(self): + super().__init__() + self.device = TurtleDevice() + self.queue = Queue() + + +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): + pass # TODO(Homework 4) + + def do_execute(self, arg): + pass # TODO(Homework 4) + + def do_exit(self, arg): + pass # TODO(Homework 4) + + +if __name__ == '__main__': + turtle_thread = TurtleDeviceThread() + # TODO(Homework 4: Correct start thread) + NoBlockingTurtleShell(turtle_thread).cmdloop() \ No newline at end of file diff --git a/turtle_shell.py b/turtle_shell.py new file mode 100644 index 0000000..ac22aab --- /dev/null +++ b/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