Implemented main basically
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Pünkösd Marcell 2021-04-14 17:41:48 +02:00
parent f8bd18c307
commit 97986d78e5
8 changed files with 152 additions and 44 deletions

View File

@ -1,3 +1,5 @@
ur-rtde~=1.4.1 ur-rtde~=1.4.1
redis~=3.5.3 redis~=3.5.3
pyprocsync>=0.1.0 pyprocsync>=0.1.0
requests
marshmallow~=3.11.1

View File

@ -0,0 +1,17 @@
from plugins import AbstractCommand
from typing import List
from plugin_repository import PluginRepository
import logging
def compile_program(plugin_repository: PluginRepository, program_source: List[dict]) -> List[AbstractCommand]:
logger = logging.getLogger('compiler')
compiled_program = []
for command_source in program_source:
logger.debug(f"Compiling: [{command_source['name']}],{command_source['args']}")
compiled_program.append(
plugin_repository.get_compiler(command_source['name']).compile(**command_source['args'])
)
return compiled_program

View File

@ -1,3 +1,4 @@
import sys
import os import os
@ -7,4 +8,7 @@ class Config:
SYNC_DELAY = float(os.environ.get("SYNC_DELAY", 1.0)) SYNC_DELAY = float(os.environ.get("SYNC_DELAY", 1.0))
REDIS_URL = os.environ["REDIS_URL"] REDIS_URL = os.environ["REDIS_URL"]
SYNC_TIMEOUT = os.environ.get("SYNC_TIMEOUT", None) # Wait infinity by default SYNC_TIMEOUT = os.environ.get("SYNC_TIMEOUT", None) # Wait infinity by default
ROBOT_ADDRESS = os.environ.get("ROBOT_ADDRESS") ROBOT_ADDRESS = os.environ.get("ROBOT_ADDRESS")
PROGRAM_URL = os.environ["PROGRAM_URL"]
DRY_RUN = ('--dry-run' in sys.argv) or bool(os.environ.get("DRY_RUN", False))
DEBUG = ('--debug' in sys.argv) or bool(os.environ.get("DEBUG", False))

View File

@ -1,14 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os
import sys import sys
from config import Config from config import Config
from plugins import SleepPlugin, SyncPlugin, WaitPlugin, URRTDEPlugin from plugins import SleepPlugin, SyncPlugin, WaitPlugin, URRTDEPlugin
from plugin_repository import PluginRepository from plugin_repository import PluginRepository
from program_executor import ProgramExecutor from program_executor import ProgramExecutor, ProgramExecutorStates
from http_server import ControllerHTTPServer from http_server import ControllerHTTPServer
import logging import logging
import signal import signal
from compiler import compile_program
from program_loader import load_program
class HTTPControl: class HTTPControl:
@ -22,43 +24,54 @@ class HTTPControl:
return 201, "Will do sir!" return 201, "Will do sir!"
def main(): def main() -> int:
# init # init instance
logging.basicConfig( logging.basicConfig(
stream=sys.stdout, stream=sys.stdout,
format="%(asctime)s [%(levelname)s]: %(name)s: %(message)s", format="%(asctime)s [%(levelname)s]: %(name)s: %(message)s",
level=logging.DEBUG level=logging.DEBUG if Config.DEBUG else logging.INFO
) )
http_server = ControllerHTTPServer(HTTPControl())
http_server.start()
compiler_repo = PluginRepository() logging.info("Registering available plugins...")
compiler_repo.register_plugin(SleepPlugin) # Register all available plugins
compiler_repo.register_plugin(SyncPlugin) plugin_repo = PluginRepository()
compiler_repo.register_plugin(WaitPlugin) plugin_repo.register_plugin(SleepPlugin)
compiler_repo.register_plugin(URRTDEPlugin) plugin_repo.register_plugin(SyncPlugin)
plugin_repo.register_plugin(WaitPlugin)
plugin_repo.register_plugin(URRTDEPlugin)
# Example code: # Download the program
compiler_repo.load_plugin("sleep") logging.info("Downloading program...")
compiler_repo.load_plugin("sync") try:
compiler_repo.load_plugin("wait") program_source = load_program(Config.PROGRAM_URL)
compiler_repo.load_plugin("ur_rtde") except Exception as e:
program = [] logging.error(f"Failed to download program: {e}! Exiting...")
program.append(compiler_repo.get_compiler("sleep").compile(secs=2)) logging.exception(e)
program.append(compiler_repo.get_compiler("moveJ").compile([5.7386425805573555, -0.536165146212658, 1.6278685933351111, -2.661452576366153, -1.5683528658421044, 1.0096729722787197], 1.0, 4.0)) return 1
program.append(compiler_repo.get_compiler("moveL").compile([-0.4, 0.1, -0.31, 3.142, 0, 0], 0.05, 0.75))
program.append(compiler_repo.get_compiler("moveL").compile([-0.4, 0.1, -0.24, 3.142, 0, 0], 0.05, 0.75))
program.append(compiler_repo.get_compiler("moveJ").compile([5.923472948343555, 0.032637657012293965, 0.2590068609959585, -0.2935643801854462, -2.7157323161031766, 4.71238898038469], 1.0, 4.0))
program.append(compiler_repo.get_compiler("moveJ").compile([4.982042349817814, -0.5256931707006921, 1.620887276327134, -1.0993828958312282, -3.660653573132907, 5.271592472723674], 1.0, 4.0))
program.append(compiler_repo.get_compiler("wait").compile())
program.append(compiler_repo.get_compiler("sleep").compile(secs=3))
program.append(compiler_repo.get_compiler("sync").compile(nodes=2, name="test"))
program.append(compiler_repo.get_compiler("sleep").compile(secs=10))
program.append(compiler_repo.get_compiler("sleep").compile(secs=10))
# prepare execution # Load required plugins
executor = ProgramExecutor(program, loop=True) logging.info("Loading required plugins...")
try:
plugin_repo.load_plugins(program_source['load_plugins'])
except Exception as e:
logging.error(f"Error during plugin loading: {e}! Exiting...")
logging.exception(e)
return 2
# Compile the program
logging.info("Compiling program...")
try:
program = compile_program(plugin_repo, program_source['program'])
except Exception as e:
logging.error(f"Error during compilation: {e}! Exiting...")
logging.exception(e)
return 3
# prepare the executor
logging.info("Preparing for execution...")
executor = ProgramExecutor(program, loop=False)
# Setup signal handler
def handle_stop_signal(signum, frame): def handle_stop_signal(signum, frame):
logging.warning(f"Signal {signum} received. Aborting execution!") logging.warning(f"Signal {signum} received. Aborting execution!")
executor.abort() executor.abort()
@ -70,14 +83,28 @@ def main():
signal.signal(signal.SIGINT, handle_stop_signal) signal.signal(signal.SIGINT, handle_stop_signal)
signal.signal(signal.SIGTERM, handle_stop_signal) signal.signal(signal.SIGTERM, handle_stop_signal)
# start execution # Actually execute
executor.start() execution_success = True
if Config.DRY_RUN:
logging.info("DRY_RUN enabled. Dumping command descriptions and exiting!")
for i, command in enumerate(program):
logging.info(f"{i:04d}: {command.describe()}")
else:
logging.info("Starting execution...")
executor.start()
executor.join()
# End of execution if executor.state == ProgramExecutorStates.DONE:
executor.join() logging.info("Program executed successfully!")
compiler_repo.close() else:
http_server.shutdown() logging.error(f"Could not finish execution! Executor state: {executor.state.name}")
execution_success = False
# Close all resources
logging.info("Cleaning up...")
plugin_repo.close()
return 0 if execution_success else 5
if __name__ == '__main__': if __name__ == '__main__':
main() sys.exit(main())

View File

@ -3,7 +3,11 @@ from plugins import AbstractPlugin, AbstractCommandCompiler
import logging import logging
class ConflictingPlugins(BaseException): class ConflictingPlugins(Exception):
pass
class UnknownPlugin(Exception):
pass pass
@ -19,14 +23,25 @@ class PluginRepository:
self._registered_plugins[plugin_class.plugin_name] = plugin_class self._registered_plugins[plugin_class.plugin_name] = plugin_class
self._logger.debug(f"Registered plugin: {plugin_class.plugin_name}") self._logger.debug(f"Registered plugin: {plugin_class.plugin_name}")
def load_plugins(self, plugins: List[str]):
for plugin in plugins:
self.load_plugin(plugin)
def load_plugin(self, plugin: str): def load_plugin(self, plugin: str):
self._logger.debug(f"Loading plugin: {plugin}")
if plugin in self._loaded_plugins.keys(): if plugin in self._loaded_plugins.keys():
self._logger.warning(f"Plugin {plugin} already loaded!") self._logger.warning(f"Plugin {plugin} already loaded!")
return return
# create instance # lookup plugin class
plugin_instance = self._registered_plugins[plugin]() # config is statically loaded try:
plugin_cls = self._registered_plugins[plugin]
except KeyError:
raise UnknownPlugin(f"Tried to load unknown plugin: {plugin}")
# Create instance
plugin_instance = plugin_cls() # config is statically loaded
# load compilers # load compilers
compilers = plugin_instance.load_compilers() compilers = plugin_instance.load_compilers()

View File

@ -50,8 +50,14 @@ class ProgramExecutor(Thread):
@property @property
def state(self): def state(self):
# Used to check the successfulness of the run as well
return self._state return self._state
def stop_looping(self):
if self._loop:
self._logger.info("Looping disabled! Finishing current loop then exiting...")
self._loop = False
def run(self) -> None: def run(self) -> None:
self._state = ProgramExecutorStates.RUNNING self._state = ProgramExecutorStates.RUNNING
self._logger.info("Start running program") self._logger.info("Start running program")
@ -61,7 +67,7 @@ class ProgramExecutor(Thread):
for i, step in enumerate(self._program): for i, step in enumerate(self._program):
self._pc = i self._pc = i
self._current_step_desc = step.describe() self._current_step_desc = step.describe()
self._logger.debug(f"Executing step {self._pc}") self._logger.debug(f"Executing step {self._pc:04d}")
try: try:
step.execute() step.execute()

View File

@ -0,0 +1,14 @@
from program_schema import ProgramSchema, SUPPORTED_PROGRAM_STRUCTURE_VERSION
import requests
def load_program(url: str) -> dict:
headers = {
'Accept': 'application/json'
}
r = requests.get(url, headers=headers)
r.raise_for_status()
program_schema = ProgramSchema(many=False)
return program_schema.load(r.json()) # Might raise marshmallow exceptions

View File

@ -0,0 +1,23 @@
from marshmallow import Schema, fields
from marshmallow.validate import Length, Equal
from marshmallow import RAISE
SUPPORTED_PROGRAM_STRUCTURE_VERSION = 1
class CommandSchema(Schema):
name = fields.Str(required=True)
args = fields.Dict(required=True)
class Meta:
unknown = RAISE
class ProgramSchema(Schema):
version = fields.Int(required=True, validate=Equal(SUPPORTED_PROGRAM_STRUCTURE_VERSION))
name = fields.Str()
load_plugins = fields.List(fields.Str(), required=True)
program = fields.Nested(CommandSchema, many=True, required=True, validate=Length(min=1))
class Meta:
unknown = RAISE