diff --git a/examples/ubuntu/main.cpp b/examples/ubuntu/main.cpp index 701d9a6..324de6a 100644 --- a/examples/ubuntu/main.cpp +++ b/examples/ubuntu/main.cpp @@ -40,6 +40,7 @@ IntegerDesc_t __attribute__((weak)) integer_desc_pool[] = { IntegerParamValue_t integer_values_pool[sizeof(integer_desc_pool) / sizeof(IntegerDesc_t)]; StringDesc_t __attribute__((weak)) string_desc_pool[] = { + {"uavcan.node.description", "", true}, {"system.name", "dronecan_application", true}, }; StringParamValue_t string_values_pool[sizeof(string_desc_pool) / sizeof(StringDesc_t)]; @@ -140,7 +141,7 @@ void lights_callback(const uavcan_equipment_indication_LightsCommand &msg) int main() { - paramsInit(1, 1, -1, 1); + paramsInit(1, 2, -1, 1); paramsResetToDefault(); ParamsApi params_api = { .getName = paramsGetName, diff --git a/include/libdcnode/legacy/uavcan/protocol/param/getset.h b/include/libdcnode/legacy/uavcan/protocol/param/getset.h index 2a2c0d6..17e3d55 100644 --- a/include/libdcnode/legacy/uavcan/protocol/param/getset.h +++ b/include/libdcnode/legacy/uavcan/protocol/param/getset.h @@ -88,7 +88,7 @@ uint16_t uavcanParamGetSetMakeStringResponse( // Value value (uint8[<=128] string_value) canardEncodeScalar(buffer, 5, 3, &tag_string); - uint8_t string_size = strlenSafely(str_val, 128); + uint8_t string_size = strlenSafely(str_val, STRING_MAX_SIZE); uint16_t string_bit_offset = string_size * 8; canardEncodeScalar(buffer, 8, 8, &string_size); memcpy(&buffer[2], str_val, string_size); diff --git a/tests/smoke_socketcan.py b/tests/smoke_socketcan.py index 37ae6d7..0012976 100755 --- a/tests/smoke_socketcan.py +++ b/tests/smoke_socketcan.py @@ -1,20 +1,55 @@ #!/usr/bin/env python3 +import time from pathlib import Path import dronecan import pytest +pytestmark = pytest.mark.filterwarnings( + "ignore:The 'bustype' argument is deprecated.*:DeprecationWarning" +) + APP_NODE_ID = 50 APP_IFACE = "slcan0" NODE_ID_TESTER = 100 MESSAGE_TIMEOUT = 1.2 -SERVICE_TIMEOUT = 0.2 +SERVICE_TIMEOUT = 1.0 +MAX_STRING_LENGTH = 56 +PARAM_DESCRIPTION_INDEX = 1 +PARAM_SYSTEM_NAME_INDEX = 2 +PARAM_DESCRIPTION_NAME = "uavcan.node.description" +PARAM_SYSTEM_NAME = "system.name" def make_dronecan_node() -> dronecan.node.Node: - node = dronecan.make_node(APP_IFACE, node_id=NODE_ID_TESTER, bitrate=1_000_000, baudrate=1_000_000) + node = dronecan.make_node( + APP_IFACE, + node_id=NODE_ID_TESTER, + bitrate=1_000_000, + baudrate=1_000_000, + node_status_interval=3600, + ) node.mode = dronecan.uavcan.protocol.NodeStatus().MODE_OPERATIONAL node.health = dronecan.uavcan.protocol.NodeStatus().HEALTH_OK return node +def request(node: dronecan.node.Node, payload): + response_transfer_event = None + + def _callback(transfer_event: dronecan.node.TransferEvent): + nonlocal response_transfer_event + if transfer_event is None or transfer_event.transfer.source_node_id != APP_NODE_ID: + return + response_transfer_event = transfer_event + + node.request(payload, APP_NODE_ID, _callback, timeout=SERVICE_TIMEOUT) + deadline = time.monotonic() + SERVICE_TIMEOUT + 0.1 + while response_transfer_event is None and time.monotonic() < deadline: + node.spin(0.01) + assert response_transfer_event is not None, f"{payload} response is missing" + return response_transfer_event.response + +def as_string(value) -> str: + return str(value).rstrip("\x00") + @pytest.mark.dependency() def test_node_status(): node = make_dronecan_node() @@ -59,5 +94,34 @@ def _get_info_callback(transfer_event: dronecan.node.TransferEvent): assert get_node_info_response_transfer_event.transfer.dest_node_id == NODE_ID_TESTER assert get_node_info_response_transfer_event.response.name.decode("utf-8").rstrip("\x00") == "com.example.libdcnode.sitl" +@pytest.mark.dependency(depends=["test_node_status"]) +def test_max_length_string_param(): + node = make_dronecan_node() + max_string = "S" * MAX_STRING_LENGTH + neighbor_marker = "AFTER_MAX_STRING" + + req = dronecan.uavcan.protocol.param.GetSet.Request(name=PARAM_SYSTEM_NAME) + req.value = dronecan.uavcan.protocol.param.Value(string_value=neighbor_marker) + response = request(node, req) + assert as_string(response.name) == PARAM_SYSTEM_NAME + assert as_string(response.value.string_value) == neighbor_marker + + req = dronecan.uavcan.protocol.param.GetSet.Request(name=PARAM_DESCRIPTION_NAME) + req.value = dronecan.uavcan.protocol.param.Value(string_value=max_string) + response = request(node, req) + assert as_string(response.name) == PARAM_DESCRIPTION_NAME + assert as_string(response.value.string_value) == max_string + + req = dronecan.uavcan.protocol.param.GetSet.Request(index=PARAM_DESCRIPTION_INDEX) + response = request(node, req) + assert as_string(response.name) == PARAM_DESCRIPTION_NAME + assert as_string(response.value.string_value) == max_string + assert neighbor_marker not in as_string(response.value.string_value) + + req = dronecan.uavcan.protocol.param.GetSet.Request(index=PARAM_SYSTEM_NAME_INDEX) + response = request(node, req) + assert as_string(response.name) == PARAM_SYSTEM_NAME + assert as_string(response.value.string_value) == neighbor_marker + if __name__ == "__main__": raise SystemExit(pytest.main([str(Path(__file__).resolve())]))