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
|
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
|
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
|
import os
|
||||||
|
|
||||||
|
|
||||||
@ -8,3 +9,6 @@ class Config:
|
|||||||
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))
|
||||||
|
@ -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())
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
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