Implemented main basically
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
f8bd18c307
commit
97986d78e5
@ -1,3 +1,5 @@
|
||||
ur-rtde~=1.4.1
|
||||
redis~=3.5.3
|
||||
pyprocsync>=0.1.0
|
||||
pyprocsync>=0.1.0
|
||||
requests
|
||||
marshmallow~=3.11.1
|
17
single_ursim_control/compiler.py
Normal file
17
single_ursim_control/compiler.py
Normal 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
|
@ -1,3 +1,4 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
@ -7,4 +8,7 @@ class Config:
|
||||
SYNC_DELAY = float(os.environ.get("SYNC_DELAY", 1.0))
|
||||
REDIS_URL = os.environ["REDIS_URL"]
|
||||
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))
|
||||
|
@ -1,14 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
from config import Config
|
||||
from plugins import SleepPlugin, SyncPlugin, WaitPlugin, URRTDEPlugin
|
||||
from plugin_repository import PluginRepository
|
||||
from program_executor import ProgramExecutor
|
||||
from program_executor import ProgramExecutor, ProgramExecutorStates
|
||||
from http_server import ControllerHTTPServer
|
||||
import logging
|
||||
import signal
|
||||
|
||||
from compiler import compile_program
|
||||
from program_loader import load_program
|
||||
|
||||
|
||||
class HTTPControl:
|
||||
|
||||
@ -22,43 +24,54 @@ class HTTPControl:
|
||||
return 201, "Will do sir!"
|
||||
|
||||
|
||||
def main():
|
||||
# init
|
||||
def main() -> int:
|
||||
# init instance
|
||||
logging.basicConfig(
|
||||
stream=sys.stdout,
|
||||
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()
|
||||
compiler_repo.register_plugin(SleepPlugin)
|
||||
compiler_repo.register_plugin(SyncPlugin)
|
||||
compiler_repo.register_plugin(WaitPlugin)
|
||||
compiler_repo.register_plugin(URRTDEPlugin)
|
||||
logging.info("Registering available plugins...")
|
||||
# Register all available plugins
|
||||
plugin_repo = PluginRepository()
|
||||
plugin_repo.register_plugin(SleepPlugin)
|
||||
plugin_repo.register_plugin(SyncPlugin)
|
||||
plugin_repo.register_plugin(WaitPlugin)
|
||||
plugin_repo.register_plugin(URRTDEPlugin)
|
||||
|
||||
# Example code:
|
||||
compiler_repo.load_plugin("sleep")
|
||||
compiler_repo.load_plugin("sync")
|
||||
compiler_repo.load_plugin("wait")
|
||||
compiler_repo.load_plugin("ur_rtde")
|
||||
program = []
|
||||
program.append(compiler_repo.get_compiler("sleep").compile(secs=2))
|
||||
program.append(compiler_repo.get_compiler("moveJ").compile([5.7386425805573555, -0.536165146212658, 1.6278685933351111, -2.661452576366153, -1.5683528658421044, 1.0096729722787197], 1.0, 4.0))
|
||||
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))
|
||||
# Download the program
|
||||
logging.info("Downloading program...")
|
||||
try:
|
||||
program_source = load_program(Config.PROGRAM_URL)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to download program: {e}! Exiting...")
|
||||
logging.exception(e)
|
||||
return 1
|
||||
|
||||
# prepare execution
|
||||
executor = ProgramExecutor(program, loop=True)
|
||||
# Load required plugins
|
||||
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):
|
||||
logging.warning(f"Signal {signum} received. Aborting execution!")
|
||||
executor.abort()
|
||||
@ -70,14 +83,28 @@ def main():
|
||||
signal.signal(signal.SIGINT, handle_stop_signal)
|
||||
signal.signal(signal.SIGTERM, handle_stop_signal)
|
||||
|
||||
# start execution
|
||||
executor.start()
|
||||
# Actually execute
|
||||
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
|
||||
executor.join()
|
||||
compiler_repo.close()
|
||||
http_server.shutdown()
|
||||
if executor.state == ProgramExecutorStates.DONE:
|
||||
logging.info("Program executed successfully!")
|
||||
else:
|
||||
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__':
|
||||
main()
|
||||
sys.exit(main())
|
||||
|
@ -3,7 +3,11 @@ from plugins import AbstractPlugin, AbstractCommandCompiler
|
||||
import logging
|
||||
|
||||
|
||||
class ConflictingPlugins(BaseException):
|
||||
class ConflictingPlugins(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownPlugin(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@ -19,14 +23,25 @@ class PluginRepository:
|
||||
self._registered_plugins[plugin_class.plugin_name] = plugin_class
|
||||
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):
|
||||
self._logger.debug(f"Loading plugin: {plugin}")
|
||||
|
||||
if plugin in self._loaded_plugins.keys():
|
||||
self._logger.warning(f"Plugin {plugin} already loaded!")
|
||||
return
|
||||
|
||||
# create instance
|
||||
plugin_instance = self._registered_plugins[plugin]() # config is statically loaded
|
||||
# lookup plugin class
|
||||
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
|
||||
compilers = plugin_instance.load_compilers()
|
||||
|
@ -50,8 +50,14 @@ class ProgramExecutor(Thread):
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
# Used to check the successfulness of the run as well
|
||||
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:
|
||||
self._state = ProgramExecutorStates.RUNNING
|
||||
self._logger.info("Start running program")
|
||||
@ -61,7 +67,7 @@ class ProgramExecutor(Thread):
|
||||
for i, step in enumerate(self._program):
|
||||
self._pc = i
|
||||
self._current_step_desc = step.describe()
|
||||
self._logger.debug(f"Executing step {self._pc}")
|
||||
self._logger.debug(f"Executing step {self._pc:04d}")
|
||||
|
||||
try:
|
||||
step.execute()
|
||||
|
14
single_ursim_control/program_loader.py
Normal file
14
single_ursim_control/program_loader.py
Normal 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
|
23
single_ursim_control/program_schema.py
Normal file
23
single_ursim_control/program_schema.py
Normal 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
|
Loading…
Reference in New Issue
Block a user