From 35d850a1b4205177106b892df598a97e20797066 Mon Sep 17 00:00:00 2001 From: "wilbers.lucas" Date: Mon, 1 Sep 2025 11:52:15 +0200 Subject: [PATCH 01/11] Add SerialConfiguration Cloud Operation --- config/modbus.toml | 7 +++ operations/c8y_SerialConfiguration | 4 ++ tedge_modbus/operations/__main__.py | 3 + .../operations/c8y_serial_configuration.py | 62 +++++++++++++++++++ tedge_modbus/reader/smartresttemplates.py | 1 + 5 files changed, 77 insertions(+) create mode 100644 operations/c8y_SerialConfiguration create mode 100644 tedge_modbus/operations/c8y_serial_configuration.py diff --git a/config/modbus.toml b/config/modbus.toml index ce7cffd..6a67d3d 100644 --- a/config/modbus.toml +++ b/config/modbus.toml @@ -2,6 +2,13 @@ pollinterval=2 loglevel="INFO" +[serial] +interface="/dev/ttyRS485" +baudrate=9600 +stopbits=2 +parity="N" +databits=8 + [thinedge] mqtthost="127.0.0.1" mqttport=1883 \ No newline at end of file diff --git a/operations/c8y_SerialConfiguration b/operations/c8y_SerialConfiguration new file mode 100644 index 0000000..ae2e717 --- /dev/null +++ b/operations/c8y_SerialConfiguration @@ -0,0 +1,4 @@ +[exec] + topic = "c8y/s/dc/modbus" + on_message = "3" + command = "python3 -m tedge_modbus.operations c8y_SerialConfiguration" \ No newline at end of file diff --git a/tedge_modbus/operations/__main__.py b/tedge_modbus/operations/__main__.py index 832cc7f..2847690 100644 --- a/tedge_modbus/operations/__main__.py +++ b/tedge_modbus/operations/__main__.py @@ -6,6 +6,7 @@ from . import c8y_modbus_configuration from . import c8y_modbus_device from . import c8y_registers +from . import c8y_serial_configuration from .context import Context @@ -20,6 +21,8 @@ def main(): run = c8y_modbus_device.run elif command == "c8y_Registers": run = c8y_registers.run + elif command == "c8y_SerialConfiguration": + run = c8y_serial_configuration.run arguments = sys.argv[2].split(",") if len(sys.argv) > 2 else [] context = Context() diff --git a/tedge_modbus/operations/c8y_serial_configuration.py b/tedge_modbus/operations/c8y_serial_configuration.py new file mode 100644 index 0000000..148be44 --- /dev/null +++ b/tedge_modbus/operations/c8y_serial_configuration.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Cumulocity IoT SerialConfiguration operation handler""" +import json +import logging +import toml +from paho.mqtt.publish import single as mqtt_publish + +from .context import Context + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) + + +def run(arguments, context: Context): + """Run c8y_SerialConfiguration operation handler""" + if len(arguments) != 6: + raise ValueError( + f"Expected 6 arguments in smart rest template. Got {len(arguments)}" + ) + # Get device configuration + modbus_config = context.base_config + loglevel = modbus_config["modbus"]["loglevel"] or "INFO" + logger.setLevel(getattr(logging, loglevel.upper(), logging.INFO)) + logger.info("New c8y_SerialConfiguration operation") + logger.debug("Current configuration: %s", modbus_config) + baud_rate = int(arguments[2]) + stop_bits = int(arguments[3]) + parity = int(arguments[4]) + data_bits = int(arguments[5]) + logger.debug("baudRate: %d, stopBits: %d, parity: %s, dataBits: %d", baud_rate, stop_bits, parity, data_bits) + + # Update configuration + modbus_config["serial"]["baudrate"] = baud_rate + modbus_config["serial"]["stopbits"] = stop_bits + modbus_config["serial"]["parity"] = parity + modbus_config["serial"]["databits"] = data_bits + + # Save to file + logger.info("Saving new configuration to %s", context.base_config_path) + with open(context.base_config_path, "w", encoding="utf8") as f: + toml.dump(modbus_config, f) + + # Update managedObject + logger.debug("Updating managedObject with new configuration") + + config = { + "baudRate":baud_rate, + "stopBits":stop_bits, + "parity":parity, + "dataBits":data_bits + } + mqtt_publish( + topic="te/device/main///twin/c8y_SerialConfiguration", + payload=json.dumps(config), + qos=1, + retain=True, + hostname=context.broker, + port=context.port, + client_id="c8y_SerialConfiguration-operation-client", + ) diff --git a/tedge_modbus/reader/smartresttemplates.py b/tedge_modbus/reader/smartresttemplates.py index 3692856..89e9ee0 100644 --- a/tedge_modbus/reader/smartresttemplates.py +++ b/tedge_modbus/reader/smartresttemplates.py @@ -4,4 +4,5 @@ SMARTREST_TEMPLATES = [ "11,1,,c8y_ModbusConfiguration,c8y_ModbusConfiguration.transmitRate,c8y_ModbusConfiguration.pollingRate", "11,2,,c8y_ModbusDevice,c8y_ModbusDevice.protocol,c8y_ModbusDevice.address,c8y_ModbusDevice.name,c8y_ModbusDevice.ipAddress,c8y_ModbusDevice.id,c8y_ModbusDevice.type", + "11,3,,c8y_SerialConfiguration,c8y_SerialConfiguration.baudRate,c8y_SerialConfiguration.stopBits,c8y_SerialConfiguration.parity,c8y_SerialConfiguration.dataBits", ] From f92357655042ef738ca3380bc8ed415416e877e4 Mon Sep 17 00:00:00 2001 From: "wilbers.lucas" Date: Mon, 1 Sep 2025 13:23:04 +0200 Subject: [PATCH 02/11] Add modbus RTU device registration via Cloud Fieldbus --- tedge_modbus/operations/c8y_modbus_device.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tedge_modbus/operations/c8y_modbus_device.py b/tedge_modbus/operations/c8y_modbus_device.py index d439221..c7d9fd4 100644 --- a/tedge_modbus/operations/c8y_modbus_device.py +++ b/tedge_modbus/operations/c8y_modbus_device.py @@ -42,8 +42,9 @@ def get_device_from_mapping(target: ModebusDevice, mapping): device = {} device["name"] = target.child_name device["address"] = int(target.modbus_address) - device["ip"] = target.modbus_server - device["port"] = 502 + if target.modbus_type == "TCP": + device["ip"] = target.modbus_server + device["port"] = 502 device["protocol"] = target.modbus_type device["littlewordendian"] = True @@ -100,12 +101,6 @@ def run(arguments, context: Context): config_path = context.config_dir / "devices.toml" target = parse_arguments(arguments) - # Fail if modbus_type is not TCP - if target.modbus_type != "TCP": - raise ValueError( - "Expected modbus_type to be TCP. Got " + target.modbus_type + "." - ) - # Update external id of child device logger.debug("Create external id for child device %s", target.device_id) url = f"{context.c8y_proxy}/identity/globalIds/{target.device_id}/externalIds" From 7c1f341864ce0e873f72a5f2bd336c4e31ae4f89 Mon Sep 17 00:00:00 2001 From: "wilbers.lucas" Date: Mon, 1 Sep 2025 15:27:23 +0200 Subject: [PATCH 03/11] Add modbus RTU reader --- config/modbus.toml | 2 +- tedge_modbus/reader/reader.py | 47 ++++++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/config/modbus.toml b/config/modbus.toml index 6a67d3d..05ec58a 100644 --- a/config/modbus.toml +++ b/config/modbus.toml @@ -3,7 +3,7 @@ pollinterval=2 loglevel="INFO" [serial] -interface="/dev/ttyRS485" +port="/dev/ttyRS485" baudrate=9600 stopbits=2 parity="N" diff --git a/tedge_modbus/reader/reader.py b/tedge_modbus/reader/reader.py index d420419..78e9aa7 100644 --- a/tedge_modbus/reader/reader.py +++ b/tedge_modbus/reader/reader.py @@ -11,7 +11,7 @@ import tomli from paho.mqtt import client as mqtt_client -from pymodbus.client.tcp import ModbusTcpClient +from pymodbus.client import ModbusTcpClient, ModbusSerialClient from pymodbus.exceptions import ConnectionException from watchdog.events import FileSystemEventHandler, DirModifiedEvent, FileModifiedEvent from watchdog.observers import Observer @@ -81,6 +81,19 @@ def reread_config(self): new_devices = self.read_device_definition( f"{self.config_dir}/{DEVICES_CONFIG_NAME}" ) + # Add Serial Config into Device Config + for device in new_devices["device"]: + if device["protocol"] == "RTU": + if device.get("port", None) is None: + device["port"] = self.base_config["serial"]["port"] + if device.get("baudrate", None) is None: + device["baudrate"] = self.base_config["serial"]["baudrate"] + if device.get("stopbits", None) is None: + device["stopbits"] = self.base_config["serial"]["stopbits"] + if device.get("parity", None) is None: + device["parity"] = self.base_config["serial"]["parity"] + if device.get("databits", None) is None: + device["databits"] = self.base_config["serial"]["databits"] if ( len(new_devices) >= 1 and new_devices.get("device") @@ -271,13 +284,28 @@ def poll_device(self, device, poll_model, mapper): def get_data_from_device(self, device, poll_model): """Get Modbus information from the device""" # pylint: disable=too-many-locals - client = ModbusTcpClient( - host=device["ip"], - port=device["port"], - auto_open=True, - auto_close=True, - debug=True, - ) + client = None + if device["protocol"] == "RTU": + client = ModbusSerialClient( + port=device["port"], + baudrate=device["baudrate"], + stopbits=device["stopbits"], + parity=device["parity"], + bytesize=device["databits"], + ) + elif device["protocol"] == "TCP": + client = ModbusTcpClient( + host=device["ip"], + port=device["port"], + # TODO: Check if these parameters really supported by ModbusTcpClient? + auto_open=True, + auto_close=True, + debug=True, + ) + else: + raise ValueError( + "Expected protocol to be RTU or TCP. Got " + device["protocol"] + "." + ) holding_register, input_registers, coils, discrete_input = poll_model hr_results = {} ir_result = {} @@ -413,11 +441,12 @@ def update_modbus_info_on_child_devices(self, devices): self.logger.debug("Update modbus info on child device") topic = f"te/device/{device['name']}///twin/c8y_ModbusDevice" config = { - "ipAddress": device["ip"], "port": device["port"], "address": device["address"], "protocol": device["protocol"], } + if device["protocol"] == "TCP": + config["ipAddress"] = device["ip"] self.send_tedge_message( MappedMessage(json.dumps(config), topic), retain=True, qos=1 ) From e072b86cd5e3e7715b958753483d763dd4db62b2 Mon Sep 17 00:00:00 2001 From: "wilbers.lucas" Date: Mon, 1 Sep 2025 15:52:54 +0200 Subject: [PATCH 04/11] formatting and linting --- .../operations/c8y_modbus_configuration.py | 3 ++- .../operations/c8y_serial_configuration.py | 19 +++++++++----- tedge_modbus/reader/reader.py | 26 ++++++++++--------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/tedge_modbus/operations/c8y_modbus_configuration.py b/tedge_modbus/operations/c8y_modbus_configuration.py index a5f4c0f..42beca6 100644 --- a/tedge_modbus/operations/c8y_modbus_configuration.py +++ b/tedge_modbus/operations/c8y_modbus_configuration.py @@ -34,7 +34,7 @@ def run(arguments, context: Context): modbus_config["modbus"]["pollinterval"] = polling_rate # Save to file - logger.info("Saving new configuration to %s", context.base_config_path) + logger.info("Saving new modbus configuration to %s", context.base_config_path) with open(context.base_config_path, "w", encoding="utf8") as f: toml.dump(modbus_config, f) @@ -45,6 +45,7 @@ def run(arguments, context: Context): "transmitRate": transmit_rate, "pollingRate": polling_rate, } + # pylint: disable=duplicate-code mqtt_publish( topic="te/device/main///twin/c8y_ModbusConfiguration", payload=json.dumps(config), diff --git a/tedge_modbus/operations/c8y_serial_configuration.py b/tedge_modbus/operations/c8y_serial_configuration.py index 148be44..f2d758f 100644 --- a/tedge_modbus/operations/c8y_serial_configuration.py +++ b/tedge_modbus/operations/c8y_serial_configuration.py @@ -29,7 +29,13 @@ def run(arguments, context: Context): stop_bits = int(arguments[3]) parity = int(arguments[4]) data_bits = int(arguments[5]) - logger.debug("baudRate: %d, stopBits: %d, parity: %s, dataBits: %d", baud_rate, stop_bits, parity, data_bits) + logger.debug( + "baudRate: %d, stopBits: %d, parity: %s, dataBits: %d", + baud_rate, + stop_bits, + parity, + data_bits, + ) # Update configuration modbus_config["serial"]["baudrate"] = baud_rate @@ -38,7 +44,7 @@ def run(arguments, context: Context): modbus_config["serial"]["databits"] = data_bits # Save to file - logger.info("Saving new configuration to %s", context.base_config_path) + logger.info("Saving new serial configuration to %s", context.base_config_path) with open(context.base_config_path, "w", encoding="utf8") as f: toml.dump(modbus_config, f) @@ -46,11 +52,12 @@ def run(arguments, context: Context): logger.debug("Updating managedObject with new configuration") config = { - "baudRate":baud_rate, - "stopBits":stop_bits, - "parity":parity, - "dataBits":data_bits + "baudRate": baud_rate, + "stopBits": stop_bits, + "parity": parity, + "dataBits": data_bits, } + # pylint: disable=duplicate-code mqtt_publish( topic="te/device/main///twin/c8y_SerialConfiguration", payload=json.dumps(config), diff --git a/tedge_modbus/reader/reader.py b/tedge_modbus/reader/reader.py index 78e9aa7..facbcaf 100644 --- a/tedge_modbus/reader/reader.py +++ b/tedge_modbus/reader/reader.py @@ -281,31 +281,33 @@ def poll_device(self, device, poll_model, mapper): (device, poll_model, mapper), ) - def get_data_from_device(self, device, poll_model): - """Get Modbus information from the device""" - # pylint: disable=too-many-locals - client = None + def get_modbus_client(self, device): + """Get Modbus client""" if device["protocol"] == "RTU": - client = ModbusSerialClient( + return ModbusSerialClient( port=device["port"], baudrate=device["baudrate"], stopbits=device["stopbits"], parity=device["parity"], bytesize=device["databits"], ) - elif device["protocol"] == "TCP": - client = ModbusTcpClient( + if device["protocol"] == "TCP": + return ModbusTcpClient( host=device["ip"], port=device["port"], - # TODO: Check if these parameters really supported by ModbusTcpClient? + # TODO: Check if these parameters really supported by ModbusTcpClient? auto_open=True, auto_close=True, debug=True, ) - else: - raise ValueError( - "Expected protocol to be RTU or TCP. Got " + device["protocol"] + "." - ) + raise ValueError( + "Expected protocol to be RTU or TCP. Got " + device["protocol"] + "." + ) + + def get_data_from_device(self, device, poll_model): + """Get Modbus information from the device""" + # pylint: disable=too-many-locals + client = self.get_modbus_client(device) holding_register, input_registers, coils, discrete_input = poll_model hr_results = {} ir_result = {} From 3fad27487e08594d5f2ed31e14eacd79b346f724 Mon Sep 17 00:00:00 2001 From: "wilbers.lucas" Date: Mon, 1 Sep 2025 16:07:58 +0200 Subject: [PATCH 05/11] Updating the readme for modbus RTU support --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 42b4f89..a67b0cf 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Plugin for polling Modbus devices and publishing the data to thin-edge.io. If us ## Overview -The plugin regularly polls Modbus devices and publishes the data to the thin-edge.io broker. The plugin is based on the [pymodbus](https://pymodbus.readthedocs.io/en/latest/) library. After installing, the plugin can be configured by changing the `modbus.toml` and `devices.toml` files. The plugin comes with an example config [4] with comments to get you started. Adding multiple servers should also be as simple as adding additional `[[device]]` sections for each IP address you want to poll. +The plugin regularly polls Modbus devices and publishes the data to the thin-edge.io broker. The plugin is based on the [pymodbus](https://pymodbus.readthedocs.io/en/latest/) library. After installing, the plugin can be configured by changing the `modbus.toml` and `devices.toml` files. The plugin comes with an example config [4] with comments to get you started. Adding multiple servers should also be as simple as adding additional `[[device]]` sections for each IP address or serial address you want to poll. ## Requirements @@ -83,9 +83,10 @@ If used with Cumulocity IoT, the plugins can be managed via the Device Managemen ### modbus.toml -This includes the basic configuration for the plugin such as poll rate and the connection to thin-edge.io (the MQTT broker needs to match the one of tedge and is probably the default `localhost:1883`). +This includes the basic configuration for the plugin such as poll rate and the connection to thin-edge.io (the MQTT broker needs to match the one of tedge and is probably the default `localhost:1883`). It also includes the configuration of the main serial port used by modbus RTU devices. Make sure the serial port is properly configured to for the hardware in use. - poll rate +- serial configuration - connection to thin-edge.io (MQTT broker needs to match the one of tedge) - log level (e.g. INFO, WARN, ERROR) @@ -154,6 +155,7 @@ As of now, the plugin only supports the following operations: - c8y_ModbusDevice - Mapping of registers to Measurements with c8y_ModbusConfiguration +- c8ySerialConfiguration To create a Cloud Fieldbus Device in Cumulocity IoT, you need first to create a Modbus protocol. Open the Device protocols page in your Device Management and add a new Modbus protocol. The configuration of your protocol depends on your Modbus Server. If you are using the Modbus Demo simulator, the you can use the following configuration: @@ -164,6 +166,8 @@ After creating the protocol, you can add a new Cloud Fieldbus Device. Select the ![Image](./doc/tcp_device.png) +For adding a modbus RTU device you need to use unit-ID of the slave device in the configuration. + ## Testing To run the tests locally, you need to provide your Cumulocity credentials as environment variables in a .env file: From 3afdf842769ee5ca370b5e4104425784bf3b67cc Mon Sep 17 00:00:00 2001 From: "wilbers.lucas" Date: Mon, 1 Sep 2025 16:15:51 +0200 Subject: [PATCH 06/11] optimize reread code --- tedge_modbus/reader/reader.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/tedge_modbus/reader/reader.py b/tedge_modbus/reader/reader.py index facbcaf..34896cc 100644 --- a/tedge_modbus/reader/reader.py +++ b/tedge_modbus/reader/reader.py @@ -84,16 +84,9 @@ def reread_config(self): # Add Serial Config into Device Config for device in new_devices["device"]: if device["protocol"] == "RTU": - if device.get("port", None) is None: - device["port"] = self.base_config["serial"]["port"] - if device.get("baudrate", None) is None: - device["baudrate"] = self.base_config["serial"]["baudrate"] - if device.get("stopbits", None) is None: - device["stopbits"] = self.base_config["serial"]["stopbits"] - if device.get("parity", None) is None: - device["parity"] = self.base_config["serial"]["parity"] - if device.get("databits", None) is None: - device["databits"] = self.base_config["serial"]["databits"] + for key in self.base_config["serial"]: + if device.get(key, None) is None: + device[key] = self.base_config["serial"][key] if ( len(new_devices) >= 1 and new_devices.get("device") From 1458ee685a3857554f876a449e0c7cb8696e3c83 Mon Sep 17 00:00:00 2001 From: Lucas Wilbers Date: Wed, 3 Sep 2025 09:18:35 +0200 Subject: [PATCH 07/11] Add serial configuration handling in update_base_config_on_device --- tedge_modbus/reader/reader.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tedge_modbus/reader/reader.py b/tedge_modbus/reader/reader.py index 34896cc..cd4b10a 100644 --- a/tedge_modbus/reader/reader.py +++ b/tedge_modbus/reader/reader.py @@ -429,6 +429,22 @@ def update_base_config_on_device(self, base_config): self.send_tedge_message( MappedMessage(json.dumps(config), topic), retain=True, qos=1 ) + if base_config.get("serial") is None: + return + topic = "te/device/main///twin/c8y_SerialConfiguration" + baud_rate = base_config["serial"].get("baudrate") + stop_bits = base_config["serial"].get("stopbits") + parity = base_config["serial"].get("parity") + data_bits = base_config["serial"].get("databits") + config = { + "baudRate": baud_rate, + "stopBits": stop_bits, + "parity": parity, + "dataBits": data_bits, + } + self.send_tedge_message( + MappedMessage(json.dumps(config), topic), retain=True, qos=1 + ) def update_modbus_info_on_child_devices(self, devices): """Update the modbus information for the child devices""" From 0d7da03bbfd5326fef18380e148e7543d4ded7f4 Mon Sep 17 00:00:00 2001 From: Lucas Wilbers Date: Wed, 3 Sep 2025 09:31:34 +0200 Subject: [PATCH 08/11] Fix parity variable type conversion in c8y_SerialConfiguration operation --- tedge_modbus/operations/c8y_serial_configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tedge_modbus/operations/c8y_serial_configuration.py b/tedge_modbus/operations/c8y_serial_configuration.py index f2d758f..89e964d 100644 --- a/tedge_modbus/operations/c8y_serial_configuration.py +++ b/tedge_modbus/operations/c8y_serial_configuration.py @@ -27,7 +27,7 @@ def run(arguments, context: Context): logger.debug("Current configuration: %s", modbus_config) baud_rate = int(arguments[2]) stop_bits = int(arguments[3]) - parity = int(arguments[4]) + parity = arguments[4] data_bits = int(arguments[5]) logger.debug( "baudRate: %d, stopBits: %d, parity: %s, dataBits: %d", From 8629f8cce2afa5052ca974393d3050fe10a107fc Mon Sep 17 00:00:00 2001 From: wilbersl <142505107+wilbersl@users.noreply.github.com> Date: Tue, 9 Sep 2025 08:40:27 +0200 Subject: [PATCH 09/11] Update README.md Co-authored-by: Rina Fujino --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a67b0cf..ff8edea 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ As of now, the plugin only supports the following operations: - c8y_ModbusDevice - Mapping of registers to Measurements with c8y_ModbusConfiguration -- c8ySerialConfiguration +- c8y_SerialConfiguration To create a Cloud Fieldbus Device in Cumulocity IoT, you need first to create a Modbus protocol. Open the Device protocols page in your Device Management and add a new Modbus protocol. The configuration of your protocol depends on your Modbus Server. If you are using the Modbus Demo simulator, the you can use the following configuration: From a2b23c46ca918fd7233aa1a96e21eb9e96d671ef Mon Sep 17 00:00:00 2001 From: Lucas Wilbers Date: Tue, 9 Sep 2025 09:57:49 +0200 Subject: [PATCH 10/11] disable linting error duplicate-code, config allocation has a duplicate in reader.py that's not relevant in my opinion --- tedge_modbus/operations/c8y_serial_configuration.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tedge_modbus/operations/c8y_serial_configuration.py b/tedge_modbus/operations/c8y_serial_configuration.py index 89e964d..d71ed84 100644 --- a/tedge_modbus/operations/c8y_serial_configuration.py +++ b/tedge_modbus/operations/c8y_serial_configuration.py @@ -50,14 +50,13 @@ def run(arguments, context: Context): # Update managedObject logger.debug("Updating managedObject with new configuration") - + # pylint: disable=duplicate-code config = { "baudRate": baud_rate, "stopBits": stop_bits, "parity": parity, "dataBits": data_bits, } - # pylint: disable=duplicate-code mqtt_publish( topic="te/device/main///twin/c8y_SerialConfiguration", payload=json.dumps(config), From c28bdcfea3b9769fc33eefdceac28347129cd1be Mon Sep 17 00:00:00 2001 From: reubenmiller Date: Tue, 9 Sep 2025 13:10:58 +0200 Subject: [PATCH 11/11] add system test to check supported operation --- tests/c8y_SerialConfiguration/device.robot | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/c8y_SerialConfiguration/device.robot diff --git a/tests/c8y_SerialConfiguration/device.robot b/tests/c8y_SerialConfiguration/device.robot new file mode 100644 index 0000000..a2e05ff --- /dev/null +++ b/tests/c8y_SerialConfiguration/device.robot @@ -0,0 +1,10 @@ +*** Settings *** +Resource ../resources/common.robot +Library Cumulocity + +Suite Setup Set Main Device + + +*** Test Cases *** +Device should support the operation c8y_SerialConfiguration + Cumulocity.Should Contain Supported Operations c8y_SerialConfiguration