From e2fbe2a6e12cf77662fa4c4fbd07e503a48bc5af Mon Sep 17 00:00:00 2001 From: Matthias Bodenbenner <m.bodenbenner@wzl-mq.rwth-aachen.de> Date: Thu, 21 Mar 2024 07:59:16 +0100 Subject: [PATCH] updated to newest soil version and updated evaluations scripts --- README.md | 8 ++ assets/interfaces/cartCMM.soil | 29 ++++ assets/interfaces/ipp.soil | 23 ++++ assets/interfaces/part.soil | 29 ++++ assets/interfaces/tool.soil | 59 ++++++++ assets/interfaces/toolChanger.soil | 23 ++++ init.bat | 1 + requirements.txt | 8 +- requirements_dev.txt | 2 +- scripts/generate.py | 2 +- src/assessment.py | 2 +- src/evaluation.py | 23 ++-- src/ipp/hwc/__init__.py | 0 src/ipp/hwc/com_tool.py | 17 +++ src/ipp/hwc/device.py | 55 ++++++++ src/ipp/hwc/ippServerConfig.toml | 5 + src/ipp/hwc/ipp_client.py | 126 ++++++++++++++++++ src/ipp/hwc/transaction.py | 92 +++++++++++++ src/ipp/main.py | 12 ++ src/lasertracker/hwc/com_base.py | 6 +- src/lasertracker/hwc/com_target.py | 4 +- src/monitoring/hwc/com_environmentalsensor.py | 24 ++-- src/robot/hwc/com_mobilerobot.py | 10 +- src/robot/hwc/com_robot.py | 6 +- 24 files changed, 527 insertions(+), 39 deletions(-) create mode 100644 assets/interfaces/cartCMM.soil create mode 100644 assets/interfaces/ipp.soil create mode 100644 assets/interfaces/part.soil create mode 100644 assets/interfaces/tool.soil create mode 100644 assets/interfaces/toolChanger.soil create mode 100644 src/ipp/hwc/__init__.py create mode 100644 src/ipp/hwc/com_tool.py create mode 100644 src/ipp/hwc/device.py create mode 100644 src/ipp/hwc/ippServerConfig.toml create mode 100644 src/ipp/hwc/ipp_client.py create mode 100644 src/ipp/hwc/transaction.py create mode 100644 src/ipp/main.py diff --git a/README.md b/README.md index 346d8e5..b2358f3 100644 --- a/README.md +++ b/README.md @@ -49,3 +49,11 @@ Funded by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) Funded by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) under Project-ID 432233186 -- AIMS. +## License of used third-party libraries + +| Library | License | +|----------|-----------------------------| +| numpy | BSD License | +| requests | BSD License | +| toml | MIT License | +| wzl-udi | MIT License | \ No newline at end of file diff --git a/assets/interfaces/cartCMM.soil b/assets/interfaces/cartCMM.soil new file mode 100644 index 0000000..2a4ce6c --- /dev/null +++ b/assets/interfaces/cartCMM.soil @@ -0,0 +1,29 @@ +import tool; +import toolChanger; +import part; + +enum CoordinateSystems { + MachineCsy + MovableMachineCsy + MultipleArmCsy + RotaryTableVarCsy + PartCsy +} + +variable ActiveCsy { + name: "ActiveCsy" + description: "The currently active coordinate system." + datatype: enum + dimension: [] + range: CoordinateSystems +} + +component CartCMM { + name: "CartCMM" + description: "Cartesian Coordinate Measuring Machine" + components: + toolChanger.ToolChanger toolChanger + part.Part part + parameters: + tool.Id activeTool +} \ No newline at end of file diff --git a/assets/interfaces/ipp.soil b/assets/interfaces/ipp.soil new file mode 100644 index 0000000..87ce326 --- /dev/null +++ b/assets/interfaces/ipp.soil @@ -0,0 +1,23 @@ +import cartCMM; + +function AbortE { + name: "AbortE" + description: "Aborts all pending transactions and stops the machine from moving." +} + +function ClearAllErrors { + name: "ClearAllErrors" + description: "Is called to recover from an error." +} + +component Ipp { + name: "I++" + description: "The top-level component of the Server." + components: + cartCMM.CartCMM cmm + functions: + AbortE abortE + ClearAllErrors clearAllErrors +} + +interface Ipp IppServer {} \ No newline at end of file diff --git a/assets/interfaces/part.soil b/assets/interfaces/part.soil new file mode 100644 index 0000000..05ac503 --- /dev/null +++ b/assets/interfaces/part.soil @@ -0,0 +1,29 @@ +@prefix quantitykind: <http://qudt.org/vocab/quantitykind/> ; +@prefix unit: <http://qudt.org/vocab/unit/> ; + +variable Temperature defines <quantitykind:Temperature> { + name: "Temperature" + description: "Current temperature of the part." + datatype: float + dimension: [] + range: (-50, 200) + unit: <unit:DEG_C> +} + +variable Approach defines <quantitykind:Distance>{ + name: "Approach" + description: "Currently specified approach distance of the part." + datatype: float + dimension: [] + range: (0, 500) + unit: <unit:MilliM> +} + +component Part { + name: "Part" + description: "Soil representation of a Part." + measurements: + Temperature temperature + parameters: + Approach approach +} \ No newline at end of file diff --git a/assets/interfaces/tool.soil b/assets/interfaces/tool.soil new file mode 100644 index 0000000..0c24d70 --- /dev/null +++ b/assets/interfaces/tool.soil @@ -0,0 +1,59 @@ +@prefix quantitykind: <http://qudt.org/vocab/quantitykind/> ; +@prefix unit: <http://qudt.org/vocab/unit/> ; + +variable Name { + name: "Name" + description: "Name of a Tool" + datatype: string + dimension: [] +} + +variable Id { + name: "Id" + description: "Id of a Tool" + datatype: string + dimension: [] +} + +# variable PositionList { +# name: "PositionList" +# description: "List of Points used for ScanOnCurve" +# datatype: float +# dimension: [0, 3] +# } + +variable Position defines <quantitykind:CartesianCoordinates> { + name: "Position" + description: "Current position of the tool, consisting of the I++ X, Y and Z values." + datatype: float + dimension: [3] + unit: <unit:MilliM> +} + +function ScanOnCurve { + name: "ScanOnCurve" + description: "Function that issues a ScanOnCurve command on the I++ Server" + arguments: + PositionList posL +} + +function PtMeas { + name: "PtMeas" + description: "Function that issues a PtMeas command on the I++ Server" + arguments: + Position pos +} + +component Tool { + name: "Tool" + description: "The currently active Tool." + measurements: + Position pos + parameters: + constant Name name + constant Id id + PositionList posL + functions: + PtMeas ptMeas + ScanOnCurve scanOnCurve +} \ No newline at end of file diff --git a/assets/interfaces/toolChanger.soil b/assets/interfaces/toolChanger.soil new file mode 100644 index 0000000..f2859ed --- /dev/null +++ b/assets/interfaces/toolChanger.soil @@ -0,0 +1,23 @@ +import tool; + +function EnumTools { + name: "EnumTools" + description: "Function that issues an EnumTools command on the I++ server." +} + +function ChangeTool { + name: "EnumTools" + description: "Function that issues an ChangeTool command on the I++ server." + arguments: + tool.Id tool_id +} + +component ToolChanger { + name: "ToolChanger" + description: "ToolChanger component" + components: + dynamic tool.Tool tools + functions: + EnumTools enumTools + ChangeTool changeTool +} \ No newline at end of file diff --git a/init.bat b/init.bat index b412275..2821a3d 100644 --- a/init.bat +++ b/init.bat @@ -1,4 +1,5 @@ cd scripts +:: python generate.py ipp.soil -g shell -s -hwc python generate.py robot.soil -g remote -s -hwc python generate.py lasertracker.soil -g remote -s -hwc python generate.py monitoring.soil -g remote -s -hwc diff --git a/requirements.txt b/requirements.txt index afee9be..4b8519b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy -requests -toml -wzl-udi==9.3.8 +numpy==1.24.2 +requests==0.10.2 +toml== +wzl-udi==10.0.0 diff --git a/requirements_dev.txt b/requirements_dev.txt index f80a934..2092c7c 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -2,7 +2,7 @@ numpy requests toml tqdm -wzl-udi==9.3.3 +wzl-udi==10.0.0 kaleido plotly pandas \ No newline at end of file diff --git a/scripts/generate.py b/scripts/generate.py index 70aeb9f..7aaf085 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -71,6 +71,6 @@ if __name__ == '__main__': stderr=subprocess.PIPE) stdout, stderr = process.communicate() print(stdout.decode()) - print(stderr.decode()) + print(stderr.decode('latin-1')) else: print(f'Generator type "{args.generator}" is unknown, must be one of "shell", "local", "remote".') diff --git a/src/assessment.py b/src/assessment.py index db1542f..ff7ba1c 100644 --- a/src/assessment.py +++ b/src/assessment.py @@ -9,7 +9,7 @@ from assessment.framework import Framework, AssessmentError, FUJI, FAIRChecker SRC = os.path.join('.') OUT = os.path.join('..', 'out') -PROFILES = True +PROFILES = False METADATA = True diff --git a/src/evaluation.py b/src/evaluation.py index 177a418..241b83f 100644 --- a/src/evaluation.py +++ b/src/evaluation.py @@ -8,14 +8,14 @@ SRC = os.path.join('..', 'src') ASSESSMENTS = os.path.join('..', 'out') OUT = os.path.join('..', 'out') -PROFILES = True +PROFILES = False METADATA = True if __name__ == '__main__': dummies = ['lasertracker', 'monitoring', 'robot'] profile_scores = pandas.DataFrame() - metadata_scores = {'f-uji': [], 'fair-checker': []} + metadata_scores = pandas.DataFrame(columns=["framework", "dummy", "principle", "value"]) for dummy in dummies: @@ -26,15 +26,18 @@ if __name__ == '__main__': for framework in frameworks: if PROFILES: - profile_scores[framework.name] += [ - framework.evaluate(os.path.join(ASSESSMENTS, framework.name, dummy, 'profiles'))] + framework.evaluate(os.path.join(ASSESSMENTS, framework.name, dummy, 'profiles'), dummy) if METADATA: - metadata_scores[framework.name] += [ - framework.evaluate(os.path.join(ASSESSMENTS, framework.name, dummy, 'profiles'))] + score = framework.evaluate(os.path.join(ASSESSMENTS, framework.name, dummy, 'metadata'), dummy) + metadata_scores = pandas.concat([metadata_scores, score], ignore_index=True) - average_profile_scores = [profile_scores[key].mean() for key in profile_scores] - average_metadata_scores = [metadata_scores[key].mean() for key in metadata_scores] + average_scores = metadata_scores.groupby(['framework', 'principle'], as_index=False).agg({'value': 'mean'}) - Framework.visualize(average_profile_scores, list(profile_scores.keys()), 'profiles.png') - Framework.visualize(average_metadata_scores, list(profile_scores.keys()), 'metadata.png') + print(average_scores) + + # average_profile_scores = [profile_scores[key].mean() for key in profile_scores] + # average_metadata_scores = [metadata_scores[key].mean() for key in metadata_scores] + + # Framework.visualize(average_profile_scores, list(profile_scores.keys()), 'profiles.png') + Framework.visualize(metadata_scores, 'metadata.png') diff --git a/src/ipp/hwc/__init__.py b/src/ipp/hwc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/ipp/hwc/com_tool.py b/src/ipp/hwc/com_tool.py new file mode 100644 index 0000000..2004333 --- /dev/null +++ b/src/ipp/hwc/com_tool.py @@ -0,0 +1,17 @@ +from scripts.gen.ipp.tool.com_tool import COMToolTOP +from scripts.gen.ipp.hwc.device import Device + + +class COMTool(COMToolTOP): + def __init__(self, device: Device, name: str = "", id: str = "", posl: list[float] = None ): + COMToolTOP.__init__(self, device, name, id) + self._device = device + + def fun_ptmeas(self, arg_pos): + self._device.pt_meas(arg_pos[0], arg_pos[1], arg_pos[2]) + + def scanoncurve(self, posl): + self._device.scanoncurve(posl) + + def get_mea_pos(self): + return self._device.position diff --git a/src/ipp/hwc/device.py b/src/ipp/hwc/device.py new file mode 100644 index 0000000..604cef5 --- /dev/null +++ b/src/ipp/hwc/device.py @@ -0,0 +1,55 @@ +import asyncio +import time +from scripts.gen.ipp.device import DeviceTOP +from threading import Thread +from . import ipp_client +import re + + +class Tool(object): + def __init__(self, position): + self._position = position + + def update(self, position): + self._position = position + + @property + def position(self): + return self._position + + +class Device(DeviceTOP, Thread): + def __init__(self): + DeviceTOP.__init__(self) + Thread.__init__(self) + self._x = 0 + self._y = 0 + self._z = 0 + self._tool = Tool(self.position) + self._error = False + self._ipp_client = None + + async def goto(self, x, y, z): + await self._ipp_client.transaction("GoTo(X(" + x + "), Y(" + y + "), Z(" + z + "))", False) + + def pt_meas(self, x, y, z): + self._ipp_client.transaction("PtMeas(X(" + str(x) + "), Y(" + str(y) + "), Z(" + str(z) + "))", False) + + @property + def position(self): + return [self._x, self._y, self._z] + + def run(self): + self._ipp_client = ipp_client.IPPClient(self) + self._ipp_client.start() + time.sleep(1) + self._ipp_client.transaction("StartSession()", False) + self._ipp_client.transaction("PtMeas(X(1), Y(2), Z(3))", False) + self._ipp_client.transaction("GoTo(X(1), Y(2), Z(3))", False) + for i in range(1000): + self._ipp_client.transaction("ABC(1,2,3)", False) + self._ipp_client.transaction("OnMoveReport(X(), Y(), Z())", True) + + def stop(self): + loop = asyncio.get_event_loop() + loop.run_until_complete(self._ipp_client.transaction("EndSession()", False)) diff --git a/src/ipp/hwc/ippServerConfig.toml b/src/ipp/hwc/ippServerConfig.toml new file mode 100644 index 0000000..1f90202 --- /dev/null +++ b/src/ipp/hwc/ippServerConfig.toml @@ -0,0 +1,5 @@ +dataformat = 'json' + +[tcp] + address='localhost' + port=1294 \ No newline at end of file diff --git a/src/ipp/hwc/ipp_client.py b/src/ipp/hwc/ipp_client.py new file mode 100644 index 0000000..ccbf668 --- /dev/null +++ b/src/ipp/hwc/ipp_client.py @@ -0,0 +1,126 @@ +from __future__ import annotations +import socket +from threading import Thread +import typing +from . import transaction + +if typing.TYPE_CHECKING: + from .device import Device + + +class UnexpectedTagError(Exception): + def __init__(self): + self.message = "The received message contains an unexpected tag." + + +class MissingACKError(Exception): + def __init__(self): + self.message = "Was expecting an ACK response but received something else." + + +class MissingTransactionCompleteResponseError(Exception): + def __init__(self): + self.message = "Transaction complete response is missing." + + +class DaemonNonDataMessage(Exception): + def __init__(self): + self.message = "Received a daemon message that is not a data message!" + + +class IPPClient(Thread): + def __init__(self, device_i: Device): + Thread.__init__(self) + self._socket = None + self._active = False + self._command_tag = 1 + self._transaction_log = {} + self._data_log = {} + self._daemon_log = {} + self._active_streams = [] + self._device = device_i + + def _handle_event(self, tag, message_type, message_body): + non_event_tag = "0" + tag[1:5] + if non_event_tag in self._active_streams: + daemon = self._transaction_log.get(non_event_tag) + if message_type != "#": + raise DaemonNonDataMessage() + daemon['cb'].update_device(message_body.split(",")) + + + def _parse_message(self, message): + print("<-- " + message) + message = message.split(" ") + tag = message[0] + message_type = message[1] + if tag[0] == "E": + self._handle_event(tag, message_type, "".join(message[2:])) + return + + transaction = self._transaction_log.get(tag) + if not transaction['ACK']: + if message_type == '&': + transaction['ACK'] = True + return + else: + raise MissingACKError() + + if message_type == '#': + self._data_log[tag + '#' + transaction['uuid']].append(message[::2]) + return + + if message_type == '%': + if not transaction['daemon']: + self._transaction_log.pop(tag) + return + + return + + def _send(self, message): + print("--> " + message.decode()) + self._socket.sendall(message) + + def transaction(self, command, streams): + tag = str(self._command_tag).zfill(5) + command_name = command.split('(')[0] + + if streams: + self._active_streams.append(tag) + + # Add transaction to pending transaction log + self._transaction_log[tag] = { + 'ACK': False, + 'uuid': command, + 'daemon': streams, + 'cb': transaction.create(command_name, tag, self._device) + } + + self._send(str.encode(tag + " " + command + "\n")) + self._command_tag = self._command_tag + 1 + + def _listen(self): + buffer = [] + stitch = False + while True: + if (len(buffer) > 0 and not stitch) or (len(buffer) > 1): + self._parse_message(buffer[0]) + buffer.pop(0) + continue + data, _ = self._socket.recvfrom(1024) + data = data.decode() + buffer = buffer + (data.splitlines()) + if stitch: + buffer[0:2] = ["".join(buffer[0:2])] + stitch = False + if len(data) > 0: + if data[-1] != '\n': + stitch = True + + def run(self): + self._active = True + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._socket.connect(('localhost', 1294)) + + listener = Thread(target=self._listen) + listener.start() diff --git a/src/ipp/hwc/transaction.py b/src/ipp/hwc/transaction.py new file mode 100644 index 0000000..82b67f6 --- /dev/null +++ b/src/ipp/hwc/transaction.py @@ -0,0 +1,92 @@ +from __future__ import annotations +from abc import ABC, abstractmethod +import re +import typing +from sys import modules + +if typing.TYPE_CHECKING: + from .device import Device + + +class TransactionNotImplementedException(Exception): + def __init__(self): + self.message = "Tried to use an unimplemented Transaction." + + +class Transaction(ABC): + + def __init__(self, tag: str, device: Device): + self._tag = tag + self._device = device + + @property + def tag(self) -> str: + return self._tag + + @abstractmethod + def update_device(self, data): + ... + + +class OnMoveReportTransaction(Transaction): + def __init__(self, tag: str, device: Device): + Transaction.__init__(self, tag, device) + + def update_device(self, data): + print("OnMoveReport update: " + '[%s]' % ', '.join(map(str, data))) + for data_point in data: + axis = data_point.split("(")[0] + value = re.findall(r"-?\d+\.\d+", data_point) + value = value[0] + match axis: + case "X": + self._device._x = float(value) + case "Y": + self._device._y = float(value) + case "Z": + self._device._z = float(value) + # self._tool.update([self._x, self._y, self._z]) + + +class StartSessionTransaction(Transaction): + def __init__(self, tag: str, device: Device): + Transaction.__init__(self, tag, device) + + def update_device(self, data): + pass + + +class PtMeasTransaction(Transaction): + def __init__(self, tag: str, device: Device): + Transaction.__init__(self, tag, device) + + def update_device(self, data): + pass + + +class ScanOnCurveTransaction(Transaction): + def __init__(self, tag: str, device: Device): + Transaction.__init__(self, tag, device) + + def update_device(self, data): + pass + + +class GoToTransaction(Transaction): + def __init__(self, tag: str, device: Device): + Transaction.__init__(self, tag, device) + + def update_device(self, data): + pass + + +def create(command: str, tag: str, device: Device) -> Transaction | None: + module = modules[__name__] + try: + transaction = getattr(module, '{}Transaction'.format(command))(tag, device) + except Exception: + print("Unknown command: " + '"' + command + '"') + print("No callback registered.") + return None + return transaction + diff --git a/src/ipp/main.py b/src/ipp/main.py new file mode 100644 index 0000000..25e0a06 --- /dev/null +++ b/src/ipp/main.py @@ -0,0 +1,12 @@ +import toml + +import start +from hwc.device import Device +from com_ipp import COMIpp + +if __name__ == '__main__': + device = Device() + soilClient = COMIpp(device) + device.start() + + start.start(soilClient, toml.load('config.toml'), 'model.json') \ No newline at end of file diff --git a/src/lasertracker/hwc/com_base.py b/src/lasertracker/hwc/com_base.py index bd8dbfc..478847c 100644 --- a/src/lasertracker/hwc/com_base.py +++ b/src/lasertracker/hwc/com_base.py @@ -24,13 +24,13 @@ class COMBase(COMBaseTOP): self._device.point_to(*par_position) def get_mea_azimuth(self): - return self._device.azimuth + return self._device.azimuth, random.random() * 10e-10 def get_mea_elevation(self): - return self._device.elevation + return self._device.elevation, random.random() * 10e-10 def get_mea_distance(self): - return self._device.distance + return self._device.distance, random.random() * 10e-9 def get_par_state(self): return str(self._device.state) diff --git a/src/lasertracker/hwc/com_target.py b/src/lasertracker/hwc/com_target.py index 0c0d817..cb0710f 100644 --- a/src/lasertracker/hwc/com_target.py +++ b/src/lasertracker/hwc/com_target.py @@ -1,3 +1,5 @@ +import random + import time from typing import List @@ -21,7 +23,7 @@ class COMTarget(COMTargetTOP): yield self.get_mea_position(), arg_label def get_mea_position(self): - return self._device.target.position + return self._device.target.position, [[random.random()*10e-9 for j in range(3)] for i in range(3)] def get_par_state(self): return str(self._device.target.state) diff --git a/src/monitoring/hwc/com_environmentalsensor.py b/src/monitoring/hwc/com_environmentalsensor.py index dc1bd5a..ab7f19a 100644 --- a/src/monitoring/hwc/com_environmentalsensor.py +++ b/src/monitoring/hwc/com_environmentalsensor.py @@ -1,3 +1,7 @@ +from typing import Tuple, Any + +import random + from com_environmentalsensor import COMEnvironmentalSensorTOP from hwc.device import Device @@ -8,20 +12,20 @@ class COMEnvironmentalSensor(COMEnvironmentalSensorTOP): COMEnvironmentalSensorTOP.__init__(self, device, location) self.uuid = id - def get_mea_temperature(self) -> float: - return self._device.sensors[self.uuid].temperature + def get_mea_temperature(self) -> Tuple[float, Any]: + return self._device.sensors[self.uuid].temperature, random.random() * 10e-2 - def get_mea_pressure(self) -> float: - return self._device.sensors[self.uuid].pressure + def get_mea_pressure(self) -> Tuple[float, Any]: + return self._device.sensors[self.uuid].pressure, random.random() * 10e-1 - def get_mea_humidity(self) -> float: - return self._device.sensors[self.uuid].humidity + def get_mea_humidity(self) -> Tuple[float, Any]: + return self._device.sensors[self.uuid].humidity, random.random() * 10e-3 - def get_mea_signalstrength(self) -> int: - return self._device.sensors[self.uuid].signal_strength + def get_mea_signalstrength(self) -> Tuple[int, Any]: + return self._device.sensors[self.uuid].signal_strength, None - def get_mea_batterylevel(self) -> int: - return self._device.sensors[self.uuid].battery_level + def get_mea_batterylevel(self) -> Tuple[int, Any]: + return self._device.sensors[self.uuid].battery_level, None def get_par_location(self) -> str: return self._par_location diff --git a/src/robot/hwc/com_mobilerobot.py b/src/robot/hwc/com_mobilerobot.py index d428f15..3665c28 100644 --- a/src/robot/hwc/com_mobilerobot.py +++ b/src/robot/hwc/com_mobilerobot.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Any, Tuple import numpy @@ -13,11 +13,11 @@ class COMMobileRobot(COMMobileRobotTOP): self._mea_position = numpy.array([0, 0, 0], dtype=float) self._device.start() - def get_mea_position(self) -> List[float]: - return self._device.position.tolist() + def get_mea_position(self) -> Tuple[List[float], Any]: + return self._device.position.tolist(), None - def get_mea_batterylevel(self) -> int: - return self._device.batterylevel + def get_mea_batterylevel(self) -> Tuple[int, Any]: + return self._device.batterylevel, None def get_par_auto(self) -> bool: return self._device.mode == Mode.AUTO diff --git a/src/robot/hwc/com_robot.py b/src/robot/hwc/com_robot.py index 62cfdd3..db212f0 100644 --- a/src/robot/hwc/com_robot.py +++ b/src/robot/hwc/com_robot.py @@ -1,5 +1,5 @@ import time -from typing import List +from typing import List, Tuple, Any import numpy @@ -16,8 +16,8 @@ class COMRobot(COMRobotTOP): self._mea_position = numpy.array([0, 0, 0], dtype=float) self._com_gripper = COMGripper(device, open=True) - def get_mea_position(self) -> List[float]: - return self._device.position.tolist() + def get_mea_position(self) -> Tuple[List[float], Any]: + return self._device.position.tolist(), None def fun_goto(self, arg_position: List[float] = None): self._device.goto(numpy.array(arg_position)) -- GitLab