This commit is contained in:
parent
43f80a79c1
commit
769ee0aeca
@ -0,0 +1,6 @@
|
||||
import os
|
||||
|
||||
|
||||
# Config is loaded statically at import time
|
||||
class Config:
|
||||
pass
|
@ -1,5 +1,34 @@
|
||||
import os
|
||||
import sys
|
||||
from config import Config
|
||||
from plugins import WaitPlugin
|
||||
from plugin_repository import PluginRepository
|
||||
from program_executor import ProgramExecutor
|
||||
import logging
|
||||
|
||||
|
||||
def main():
|
||||
print("Hello world!")
|
||||
# init
|
||||
logging.basicConfig(
|
||||
stream=sys.stdout,
|
||||
format="%(asctime)s [%(levelname)s]: %(name)s: %(message)s",
|
||||
level=logging.DEBUG
|
||||
)
|
||||
compiler_repo = PluginRepository()
|
||||
compiler_repo.register_plugin(WaitPlugin)
|
||||
|
||||
# Example code:
|
||||
compiler_repo.load_plugin("wait")
|
||||
program = []
|
||||
program.append(compiler_repo.get_compiler("wait").compile(secs=2))
|
||||
|
||||
# execute:
|
||||
executor = ProgramExecutor(program)
|
||||
executor.start()
|
||||
|
||||
# End of execution
|
||||
executor.join()
|
||||
compiler_repo.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
35
single_ursim_control/plugin_repository.py
Normal file
35
single_ursim_control/plugin_repository.py
Normal file
@ -0,0 +1,35 @@
|
||||
from typing import Dict, List
|
||||
from plugins import AbstractPlugin, AbstractCommandCompiler
|
||||
import logging
|
||||
|
||||
|
||||
class PluginRepository:
|
||||
|
||||
def __init__(self):
|
||||
self._registered_plugins: Dict[str, type(AbstractPlugin)] = {}
|
||||
self._loaded_plugins: List[AbstractPlugin] = []
|
||||
self._command_compilers: Dict[str, AbstractCommandCompiler] = {}
|
||||
self._logger = logging.getLogger("plugin_repository")
|
||||
|
||||
def register_plugin(self, plugin_class: type(AbstractPlugin)):
|
||||
self._registered_plugins[plugin_class.plugin_name] = plugin_class
|
||||
self._logger.debug(f"Registered plugin: {plugin_class.plugin_name}")
|
||||
|
||||
def load_plugin(self, plugin: str):
|
||||
plugin_instance = self._registered_plugins[plugin]() # config is statically loaded
|
||||
|
||||
self._loaded_plugins.append(plugin_instance)
|
||||
|
||||
compilers = plugin_instance.load_compilers()
|
||||
self._command_compilers.update(compilers)
|
||||
self._logger.info(f"Loaded plugin: {plugin}")
|
||||
self._logger.debug(f"Plugin {plugin} loaded the following commands: {', '.join(compilers.keys())}")
|
||||
|
||||
def get_compiler(self, command: str) -> AbstractCommandCompiler:
|
||||
return self._command_compilers[command]
|
||||
|
||||
def close(self):
|
||||
self._command_compilers = []
|
||||
for plugin in self._loaded_plugins:
|
||||
plugin.close()
|
||||
self._logger.info(f"Unloaded plugin: {plugin.plugin_name}")
|
2
single_ursim_control/plugins/__init__.py
Normal file
2
single_ursim_control/plugins/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .abstract_plugin import AbstractCommand, AbstractCommandCompiler, AbstractPlugin
|
||||
from .wait_plugin import WaitPlugin
|
32
single_ursim_control/plugins/abstract_plugin.py
Normal file
32
single_ursim_control/plugins/abstract_plugin.py
Normal file
@ -0,0 +1,32 @@
|
||||
from typing import Dict
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class AbstractCommand(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def execute(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def describe(self) -> dict:
|
||||
pass
|
||||
|
||||
|
||||
class AbstractCommandCompiler(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def compile(self, *args, **kwargs) -> AbstractCommand:
|
||||
pass
|
||||
|
||||
|
||||
class AbstractPlugin(ABC):
|
||||
plugin_name = ""
|
||||
|
||||
@abstractmethod
|
||||
def load_compilers(self) -> Dict[str, AbstractCommandCompiler]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def close(self):
|
||||
pass
|
53
single_ursim_control/plugins/wait_plugin.py
Normal file
53
single_ursim_control/plugins/wait_plugin.py
Normal file
@ -0,0 +1,53 @@
|
||||
from typing import Dict
|
||||
|
||||
from .abstract_plugin import AbstractCommand, AbstractPlugin, AbstractCommandCompiler
|
||||
import logging
|
||||
import time
|
||||
|
||||
|
||||
class WaitCommand(AbstractCommand):
|
||||
|
||||
def __init__(self, logger: logging.Logger, secs: float):
|
||||
|
||||
if type(secs) not in [float, int]:
|
||||
raise ValueError("Secs must be float or int")
|
||||
|
||||
if secs <= 0:
|
||||
raise ValueError("Secs must be a positive integer")
|
||||
|
||||
self._secs = secs
|
||||
self._logger = logger
|
||||
|
||||
def execute(self):
|
||||
self._logger.debug(f"Sleeping for {self._secs} seconds")
|
||||
time.sleep(self._secs)
|
||||
self._logger.debug(f"Slept for {self._secs} seconds")
|
||||
|
||||
def describe(self) -> dict:
|
||||
return {
|
||||
"secs": self._secs
|
||||
}
|
||||
|
||||
|
||||
class WaitCompiler(AbstractCommandCompiler):
|
||||
|
||||
def __init__(self, logger: logging.Logger):
|
||||
self._logger = logger
|
||||
|
||||
def compile(self, secs: float) -> AbstractCommand:
|
||||
return WaitCommand(self._logger, secs)
|
||||
|
||||
|
||||
class WaitPlugin(AbstractPlugin):
|
||||
plugin_name = "wait"
|
||||
|
||||
def __init__(self):
|
||||
self._logger = logging.getLogger("plugin").getChild("wait")
|
||||
|
||||
def load_compilers(self) -> Dict[str, AbstractCommandCompiler]:
|
||||
return {
|
||||
"wait": WaitCompiler(self._logger)
|
||||
}
|
||||
|
||||
def close(self):
|
||||
pass
|
86
single_ursim_control/program_executor.py
Normal file
86
single_ursim_control/program_executor.py
Normal file
@ -0,0 +1,86 @@
|
||||
from typing import List
|
||||
from threading import Thread
|
||||
from plugins import AbstractCommand
|
||||
from enum import Enum
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
class ProgramExecutorStates(Enum):
|
||||
PREPARING = 0
|
||||
RUNNING = 1
|
||||
DONE = 2
|
||||
ABORTED = 3
|
||||
CRASHED = 4
|
||||
|
||||
|
||||
class ProgramExecutor(Thread):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
program: List[AbstractCommand],
|
||||
loop: bool = False
|
||||
):
|
||||
super().__init__()
|
||||
self._program = program
|
||||
self._loop = loop
|
||||
self._current_step_desc = {}
|
||||
self._pc = 0
|
||||
self._loop_counter = 0
|
||||
# TODO: jogging wait
|
||||
|
||||
self._state = ProgramExecutorStates.PREPARING
|
||||
|
||||
self._logger = logging.getLogger("program_executor")
|
||||
|
||||
def abort(self):
|
||||
self._state = ProgramExecutorStates.ABORTED
|
||||
# TODO: implement abort
|
||||
|
||||
def cont(self):
|
||||
# TODO: jogging wait
|
||||
pass
|
||||
|
||||
def get_status(self) -> dict:
|
||||
return {
|
||||
"current_step": self._pc,
|
||||
"program_length": len(self._program),
|
||||
"step_description": self._current_step_desc,
|
||||
"state": self._state.name,
|
||||
"loop_count": self._loop_counter,
|
||||
"config": {
|
||||
"loop": self._loop
|
||||
}
|
||||
}
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self._state
|
||||
|
||||
def run(self) -> None:
|
||||
self._state = ProgramExecutorStates.RUNNING
|
||||
self._logger.info("Start running program")
|
||||
|
||||
while True: # needed for loop
|
||||
self._loop_counter += 1
|
||||
for i, step in enumerate(self._program):
|
||||
self._pc = i
|
||||
self._current_step_desc = step.describe()
|
||||
self._logger.debug(f"Executing step {self._pc}")
|
||||
|
||||
try:
|
||||
step.execute()
|
||||
except Exception as e:
|
||||
self._logger.exception(e)
|
||||
self._state = ProgramExecutorStates.CRASHED
|
||||
return
|
||||
|
||||
# TODO: jogging wait
|
||||
|
||||
if self._loop:
|
||||
self._logger.debug("Looping program")
|
||||
else:
|
||||
self._logger.debug("Program ended")
|
||||
break
|
||||
|
||||
self._state = ProgramExecutorStates.DONE
|
Loading…
Reference in New Issue
Block a user