From eed68719572b9103c3641ac47d80c3b3a5d09aab Mon Sep 17 00:00:00 2001 From: Martin Weishart <41727377+maweit@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:55:48 +0100 Subject: [PATCH 01/14] Implement audio sender control class and descriptors Added audio sender control class and related data types to manage audio sender properties and descriptors. --- .../nmos-cpp-node/node_implementation.cpp | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 9661a109..b71171c8 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -203,6 +203,130 @@ namespace impl nmos::nc::counter late_packet_counter; }; std::vector nic_packet_counters; + + // Example of an audio sender control class + static nmos::control_protocol_resource + make_sender_control(nmos::experimental::control_protocol_state & control_protocol_state, + const web::json::value & sender_data, + const nmos::nc_oid & oid, + const nmos::nc_oid & parent_oid, + const web::json::value touchpoint, + slog::base_gate & gate) { + using web::json::value; + using web::json::value_of; + + // Datatype description for an object within an object + auto make_object_object_datatype = [](const std::vector> & obj_desc, + const utility::string_t & descriptor, const utility::string_t & type_name) { + using web::json::value; + auto fields = value::array(); + for ( auto obj : obj_desc ) { + auto data = nmos::nc::details::make_field_descriptor(std::get<0>(obj), std::get<1>(obj), std::get<2>(obj), true, false, value::null()); + web::json::push_back(fields, std::move(data)); + } + auto ret = nmos::nc::details::make_datatype_descriptor_struct(descriptor, type_name, fields, value::null()); + return ret; + }; + + // Datatype description for an array within an object + auto make_object_array_datatype = [](const utility::string_t & obj_descriptor, const utility::string_t & obj_type_name, + const utility::string_t & descriptor, const utility::string_t & type_name) { + using web::json::value; + auto fields = value::array(); + auto data = nmos::nc::details::make_field_descriptor(obj_descriptor, obj_type_name, U("NcString"), false, true, value::null()); + web::json::push_back(fields, std::move(data)); + auto ret = nmos::nc::details::make_datatype_descriptor_struct(descriptor, type_name, fields, value::null()); + return ret; + }; + + std::vector> subs_array{ + {U("active"), U("active"), U("NcBoolean")}, + {U("receiver_id"), U("receiver_id"), U("NcUuid")}}; + auto subs_data_type = make_object_object_datatype(subs_array, U("NcSenderObjectType"), U("NcSenderObjectType")); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ subs_data_type }); + + auto caps_data_type = make_object_array_datatype(U("Media Type Array"), U("media_types"), U("NcSenderCapsType"), U("NcSenderCapsType")); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ caps_data_type }); + + auto tags_data_type = make_object_array_datatype(U("Hints Type Array"), U("urn:x-nmos:tag:grouphint/v1.0"), U("NcSenderTagsType"), U("NcSenderTagsType")); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ tags_data_type }); + + auto array_data_type = nmos::nc::details::make_datatype_typedef(U("Array Type"), U("NcArray"), true, U("NcString"), value::null()); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ array_data_type }); + + const web::json::field_as_value caps_property{ U("caps") }; + const web::json::field_as_string description_property{ U("description") }; + const web::json::field_as_string device_id_property{ U("device_id") }; + const web::json::field_as_string flow_id_property{ U("flow_id") }; + const web::json::field_as_string uuid_property{ U("resource_id") }; + const web::json::field_as_array interface_bindings_property{ U("interface_bindings") }; + const web::json::field_as_string label_property{ U("label") }; + const web::json::field_as_string manifest_href_property{ U("manifest_href") }; + const web::json::field_as_array subscription_property{ U("subscription") }; + const web::json::field_as_value tags_property{ U("tags") }; + const web::json::field_as_string transport_property{ U("transport") }; + const web::json::field version_property{ U("version") }; + + // Define property descriptors: required fields + std::vector snd_control_property_descriptors = { + nmos::experimental::make_control_class_property_descriptor(description_property, { 3, 1 }, description_property, U("NcString")), + nmos::experimental::make_control_class_property_descriptor(uuid_property, { 3, 2 }, uuid_property, U("NcUuid")), + nmos::experimental::make_control_class_property_descriptor(label_property, { 3, 3 }, label_property, U("NcString")), + nmos::experimental::make_control_class_property_descriptor(tags_property, { 3, 4 }, tags_property, U("NcSenderTagsType")), + nmos::experimental::make_control_class_property_descriptor(version_property, { 3, 5 }, version_property, U("NcVersionCode")), + }; + // Property descriptor for optional fields + if (sender_data.has_field(caps_property)) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(caps_property,{ 3, 6 }, caps_property, U("NcSenderCapsType"))); + if (sender_data.has_field(device_id_property)) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(device_id_property, { 3, 7 }, device_id_property, U("NcUuid"))); + if (sender_data.has_field(flow_id_property)) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(flow_id_property, { 3, 8 }, flow_id_property, U("NcUuid"))); + if (sender_data.has_field(interface_bindings_property)) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(interface_bindings_property, { 3, 9 }, interface_bindings_property, U("NcArray"))); + if (sender_data.has_field(manifest_href_property)) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(manifest_href_property, { 3, 10 }, manifest_href_property, U("NcUri"))); + if (sender_data.has_field(subscription_property)) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(subscription_property, { 3, 11 }, subscription_property, U("NcSenderObjectType"))); + if (sender_data.has_field(transport_property)) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(transport_property, { 3, 12 }, transport_property, U("NcString"))); + + auto snd_class_id = nmos::nc::make_class_id(nmos::nc_worker_class_id, 0, { 4 }); + + // method and event descriptors are defined by defaults in the function prototype, so only need to pass the property descriptors + utility::string_t descriptor_description = sender_data.at(label_property).as_string()+ U(" control class descriptor"); + auto sender_control_class_descriptor = + nmos::experimental::make_control_class_descriptor(descriptor_description, //U("Sender control class descriptor"), + snd_class_id, U("SenderControl"), + snd_control_property_descriptors /*,snd_control_method_descriptors*/); + + // Insert class descriptor into the nmos-cpp framework + control_protocol_state.insert(std::move(sender_control_class_descriptor)); + + auto snd_name = U("audio-sender-control"); + // define a function for instantiating object instances of the class + auto data = nmos::nc::details::make_worker(snd_class_id, oid, + true, + parent_oid, + snd_name, + web::json::value(snd_name), + U("Sender resource data"), + touchpoint, + web::json::value::null(), + true); + + // Required + data[description_property] = sender_data.at(description_property); + data[uuid_property] = sender_data.at(U("id")); + data[label_property] = sender_data.at(label_property); + data[tags_property] = sender_data.at(tags_property); + data[version_property] = sender_data.at(version_property); + // Optional + if (sender_data.has_field(caps_property)) data[caps_property] = sender_data.at(caps_property); + if (sender_data.has_field(device_id_property)) data[device_id_property] = sender_data.at(device_id_property); + if (sender_data.has_field(flow_id_property)) data[flow_id_property] = sender_data.at(flow_id_property); + if (sender_data.has_field(interface_bindings_property)) data[interface_bindings_property] = sender_data.at(interface_bindings_property); + if (sender_data.has_field(manifest_href_property)) data[manifest_href_property] = sender_data.at(manifest_href_property); + if (sender_data.has_field(subscription_property)) data[subscription_property] = sender_data.at(subscription_property); + if (sender_data.has_field(transport_property)) data[transport_property] = sender_data.at(transport_property); + + auto snd_control_resource = nmos::control_protocol_resource{ nmos::is12_versions::v1_0, nmos::types::nc_worker, std::move(data), true }; + return snd_control_resource; + } + } // forward declarations for node_implementation_thread @@ -397,6 +521,7 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr if (!insert_resource_after(delay_millis, model.node_resources, std::move(device), gate)) throw node_implementation_init_exception(); } + web::json::value audio_sender_for_control_protocol; // example sources, flows and senders for (int index = 0; index < how_many; ++index) { @@ -506,6 +631,9 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr sender.data[nmos::fields::bit_rate] = value(transport_bit_rate); sender.data[nmos::fields::st2110_21_sender_type] = value(nmos::st2110_21_sender_types::type_N.name); } + else if (impl::ports::audio == port) { + audio_sender_for_control_protocol = sender.data; + } impl::set_label_description(sender, port, index); impl::insert_group_hint(sender, port, index); @@ -1326,6 +1454,13 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr // example temperature-sensor const auto temperature_sensor = make_temperature_sensor(++oid, nmos::root_block_oid, U("temperature-sensor"), U("Temperature Sensor"), U("Temperature Sensor block"), value::null(), value::null(), 0.0, U("Celsius")); + // exmaple sender + nmos::nc::push_back(root_block, impl::make_sender_control(control_protocol_state, + audio_sender_for_control_protocol, + ++oid, nmos::root_block_oid, + value_of({ { nmos::nc::details::make_touchpoint_nmos({nmos::ncp_touchpoint_resource_types::sender, audio_sender_for_control_protocol.at(U("id")).as_string()}) } }), + gate)); + // add receivers-block to root-block nmos::nc::push_back(root_block, receivers_block); // add temperature-sensor to root-block From 30473e55e7239a30a3b3bd86f0d88c47e07f5ca3 Mon Sep 17 00:00:00 2001 From: Martin Weishart <41727377+maweit@users.noreply.github.com> Date: Thu, 6 Nov 2025 15:50:27 +0100 Subject: [PATCH 02/14] Refactor audio sender control class and descriptors --- .../nmos-cpp-node/node_implementation.cpp | 122 +++++++++++------- 1 file changed, 78 insertions(+), 44 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index b71171c8..cb547993 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -204,14 +204,11 @@ namespace impl }; std::vector nic_packet_counters; - // Example of an audio sender control class - static nmos::control_protocol_resource - make_sender_control(nmos::experimental::control_protocol_state & control_protocol_state, - const web::json::value & sender_data, - const nmos::nc_oid & oid, - const nmos::nc_oid & parent_oid, - const web::json::value touchpoint, - slog::base_gate & gate) { + + // Example of audio sender control class descriptors. Used below for setting up a sender control class + nmos::nc_class_id + make_audio_sender_descriptors(nmos::experimental::control_protocol_state & control_protocol_state, + const web::json::value & sender_data) { using web::json::value; using web::json::value_of; @@ -238,21 +235,21 @@ namespace impl auto ret = nmos::nc::details::make_datatype_descriptor_struct(descriptor, type_name, fields, value::null()); return ret; }; - - std::vector> subs_array{ + std::vector> subs_array { {U("active"), U("active"), U("NcBoolean")}, - {U("receiver_id"), U("receiver_id"), U("NcUuid")}}; + {U("receiver_id"), U("receiver_id"), U("NcUuid")} + }; auto subs_data_type = make_object_object_datatype(subs_array, U("NcSenderObjectType"), U("NcSenderObjectType")); - control_protocol_state.insert(nmos::experimental::datatype_descriptor{ subs_data_type }); + control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ subs_data_type })); auto caps_data_type = make_object_array_datatype(U("Media Type Array"), U("media_types"), U("NcSenderCapsType"), U("NcSenderCapsType")); - control_protocol_state.insert(nmos::experimental::datatype_descriptor{ caps_data_type }); + control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ caps_data_type })); auto tags_data_type = make_object_array_datatype(U("Hints Type Array"), U("urn:x-nmos:tag:grouphint/v1.0"), U("NcSenderTagsType"), U("NcSenderTagsType")); - control_protocol_state.insert(nmos::experimental::datatype_descriptor{ tags_data_type }); + control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ tags_data_type })); auto array_data_type = nmos::nc::details::make_datatype_typedef(U("Array Type"), U("NcArray"), true, U("NcString"), value::null()); - control_protocol_state.insert(nmos::experimental::datatype_descriptor{ array_data_type }); + control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ array_data_type })); const web::json::field_as_value caps_property{ U("caps") }; const web::json::field_as_string description_property{ U("description") }; @@ -269,34 +266,61 @@ namespace impl // Define property descriptors: required fields std::vector snd_control_property_descriptors = { - nmos::experimental::make_control_class_property_descriptor(description_property, { 3, 1 }, description_property, U("NcString")), - nmos::experimental::make_control_class_property_descriptor(uuid_property, { 3, 2 }, uuid_property, U("NcUuid")), - nmos::experimental::make_control_class_property_descriptor(label_property, { 3, 3 }, label_property, U("NcString")), - nmos::experimental::make_control_class_property_descriptor(tags_property, { 3, 4 }, tags_property, U("NcSenderTagsType")), - nmos::experimental::make_control_class_property_descriptor(version_property, { 3, 5 }, version_property, U("NcVersionCode")), + nmos::experimental::make_control_class_property_descriptor(U("description"), { 3, 1 }, description_property, U("NcString")), + nmos::experimental::make_control_class_property_descriptor(U("resource_id"), { 3, 2 }, uuid_property, U("NcUuid")), + nmos::experimental::make_control_class_property_descriptor(U("label"), { 3, 3 }, label_property, U("NcString")), + nmos::experimental::make_control_class_property_descriptor(U("tags"), { 3, 4 }, tags_property, U("NcSenderTagsType")), + nmos::experimental::make_control_class_property_descriptor(U("version"), { 3, 5 }, version_property, U("NcVersionCode")), }; // Property descriptor for optional fields - if (sender_data.has_field(caps_property)) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(caps_property,{ 3, 6 }, caps_property, U("NcSenderCapsType"))); - if (sender_data.has_field(device_id_property)) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(device_id_property, { 3, 7 }, device_id_property, U("NcUuid"))); - if (sender_data.has_field(flow_id_property)) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(flow_id_property, { 3, 8 }, flow_id_property, U("NcUuid"))); - if (sender_data.has_field(interface_bindings_property)) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(interface_bindings_property, { 3, 9 }, interface_bindings_property, U("NcArray"))); - if (sender_data.has_field(manifest_href_property)) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(manifest_href_property, { 3, 10 }, manifest_href_property, U("NcUri"))); - if (sender_data.has_field(subscription_property)) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(subscription_property, { 3, 11 }, subscription_property, U("NcSenderObjectType"))); - if (sender_data.has_field(transport_property)) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(transport_property, { 3, 12 }, transport_property, U("NcString"))); + if (sender_data.has_field(U("caps"))) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("caps"),{ 3, 6 }, caps_property, U("NcSenderCapsType"))); + if (sender_data.has_field(U("device_id"))) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("description"), { 3, 7 }, device_id_property, U("NcUuid"))); + if (sender_data.has_field(U("flow_id"))) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("flow_id"), { 3, 8 }, flow_id_property, U("NcUuid"))); + if (sender_data.has_field(U("interface_bindings"))) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("interface_bindings"), { 3, 9 }, interface_bindings_property, U("NcArray"))); + if (sender_data.has_field(U("manifest_href"))) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("manifest_href"), { 3, 10 }, manifest_href_property, U("NcUri"))); + if (sender_data.has_field(U("subscription"))) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("subscription"), { 3, 11 }, subscription_property, U("NcSenderObjectType"))); + if (sender_data.has_field(U("transport"))) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("transport"), { 3, 12 }, transport_property, U("NcString"))); - auto snd_class_id = nmos::nc::make_class_id(nmos::nc_worker_class_id, 0, { 4 }); + auto snd_class_id = nmos::nc::make_class_id(nmos::nc_worker_class_id, 0, { 5 }); // method and event descriptors are defined by defaults in the function prototype, so only need to pass the property descriptors - utility::string_t descriptor_description = sender_data.at(label_property).as_string()+ U(" control class descriptor"); + utility::string_t descriptor_description = utility::string_t(U("AudioSenderControl")) + utility::string_t(U(" control class descriptor")); auto sender_control_class_descriptor = - nmos::experimental::make_control_class_descriptor(descriptor_description, //U("Sender control class descriptor"), - snd_class_id, U("SenderControl"), - snd_control_property_descriptors /*,snd_control_method_descriptors*/); + nmos::experimental::make_control_class_descriptor(descriptor_description, + snd_class_id, U("AudioSenderControl"), + snd_control_property_descriptors); // Insert class descriptor into the nmos-cpp framework control_protocol_state.insert(std::move(sender_control_class_descriptor)); - auto snd_name = U("audio-sender-control"); + return snd_class_id; + } + + // Example of an audio sender control class + static nmos::control_protocol_resource + make_audio_sender_control(const nmos::nc_class_id & snd_class_id, + const web::json::value & sender_data, + const nmos::nc_oid & oid, + const nmos::nc_oid & parent_oid, + const web::json::value touchpoint, + slog::base_gate & gate) { + using web::json::value; + using web::json::value_of; + + const web::json::field_as_value caps_property{ U("caps") }; + const web::json::field_as_string description_property{ U("description") }; + const web::json::field_as_string device_id_property{ U("device_id") }; + const web::json::field_as_string flow_id_property{ U("flow_id") }; + const web::json::field_as_string uuid_property{ U("resource_id") }; + const web::json::field_as_array interface_bindings_property{ U("interface_bindings") }; + const web::json::field_as_string label_property{ U("label") }; + const web::json::field_as_string manifest_href_property{ U("manifest_href") }; + const web::json::field_as_array subscription_property{ U("subscription") }; + const web::json::field_as_value tags_property{ U("tags") }; + const web::json::field_as_string transport_property{ U("transport") }; + const web::json::field version_property{ U("version") }; + + auto snd_name = U("AudioSenderControl"); // define a function for instantiating object instances of the class auto data = nmos::nc::details::make_worker(snd_class_id, oid, true, @@ -309,19 +333,19 @@ namespace impl true); // Required - data[description_property] = sender_data.at(description_property); + data[description_property] = sender_data.at(U("description")); data[uuid_property] = sender_data.at(U("id")); - data[label_property] = sender_data.at(label_property); - data[tags_property] = sender_data.at(tags_property); - data[version_property] = sender_data.at(version_property); + data[label_property] = sender_data.at(U("label")); + data[tags_property] = sender_data.at(U("tags")); + data[version_property] = sender_data.at(U("version")); // Optional - if (sender_data.has_field(caps_property)) data[caps_property] = sender_data.at(caps_property); - if (sender_data.has_field(device_id_property)) data[device_id_property] = sender_data.at(device_id_property); - if (sender_data.has_field(flow_id_property)) data[flow_id_property] = sender_data.at(flow_id_property); - if (sender_data.has_field(interface_bindings_property)) data[interface_bindings_property] = sender_data.at(interface_bindings_property); - if (sender_data.has_field(manifest_href_property)) data[manifest_href_property] = sender_data.at(manifest_href_property); - if (sender_data.has_field(subscription_property)) data[subscription_property] = sender_data.at(subscription_property); - if (sender_data.has_field(transport_property)) data[transport_property] = sender_data.at(transport_property); + if (sender_data.has_field(U("caps"))) data[caps_property] = sender_data.at(U("caps")); + if (sender_data.has_field(U("device_id"))) data[device_id_property] = sender_data.at(U("device_id")); + if (sender_data.has_field(U("flow_id"))) data[flow_id_property] = sender_data.at(U("flow_id")); + if (sender_data.has_field(U("interface_bindings"))) data[interface_bindings_property] = sender_data.at(U("interface_bindings")); + if (sender_data.has_field(U("manifest_href"))) data[manifest_href_property] = sender_data.at(U("manifest_href")); + if (sender_data.has_field(U("subscription"))) data[subscription_property] = sender_data.at(U("subscription")); + if (sender_data.has_field(U("transport"))) data[transport_property] = sender_data.at(U("transport")); auto snd_control_resource = nmos::control_protocol_resource{ nmos::is12_versions::v1_0, nmos::types::nc_worker, std::move(data), true }; return snd_control_resource; @@ -1340,6 +1364,9 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr return nmos::control_protocol_resource{ nmos::is12_versions::v1_0, nmos::types::nc_worker, std::move(data), true }; }; + // audio sender control descriptors + auto audio_sender_class_id = impl::make_audio_sender_descriptors(control_protocol_state, audio_sender_for_control_protocol); + // example root block auto root_block = nmos::make_root_block(); @@ -1354,6 +1381,13 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr // example bulk properties manager auto bulk_properties_manager = nmos::make_bulk_properties_manager(++oid); + // example sender control + auto sender_control_block = impl::make_audio_sender_control(audio_sender_class_id, + audio_sender_for_control_protocol, + ++oid, nmos::root_block_oid, + value_of({ { nmos::nc::details::make_touchpoint_nmos({nmos::ncp_touchpoint_resource_types::sender, audio_sender_for_control_protocol.at(U("id")).as_string()}) } }), + gate); + // example stereo gain const auto stereo_gain_oid = ++oid; auto stereo_gain = nmos::make_block(stereo_gain_oid, nmos::root_block_oid, U("stereo-gain"), U("Stereo gain"), U("Stereo gain block")); From c02ff61d935cc6094e5d4d969d0a44ed7def4b0c Mon Sep 17 00:00:00 2001 From: Martin Weishart <41727377+maweit@users.noreply.github.com> Date: Thu, 6 Nov 2025 15:54:35 +0100 Subject: [PATCH 03/14] Remove example sender from root block Removed example sender control from root block setup. --- Development/nmos-cpp-node/node_implementation.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index cb547993..98b676bc 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1488,13 +1488,6 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr // example temperature-sensor const auto temperature_sensor = make_temperature_sensor(++oid, nmos::root_block_oid, U("temperature-sensor"), U("Temperature Sensor"), U("Temperature Sensor block"), value::null(), value::null(), 0.0, U("Celsius")); - // exmaple sender - nmos::nc::push_back(root_block, impl::make_sender_control(control_protocol_state, - audio_sender_for_control_protocol, - ++oid, nmos::root_block_oid, - value_of({ { nmos::nc::details::make_touchpoint_nmos({nmos::ncp_touchpoint_resource_types::sender, audio_sender_for_control_protocol.at(U("id")).as_string()}) } }), - gate)); - // add receivers-block to root-block nmos::nc::push_back(root_block, receivers_block); // add temperature-sensor to root-block From 1f56a446b08b4ee4bb743ff6715d685bb7d55bb5 Mon Sep 17 00:00:00 2001 From: Martin Weishart <41727377+maweit@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:56:12 +0100 Subject: [PATCH 04/14] Update Development/nmos-cpp-node/node_implementation.cpp Co-authored-by: jonathan-r-thorpe <64410119+jonathan-r-thorpe@users.noreply.github.com> --- Development/nmos-cpp-node/node_implementation.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 98b676bc..559a380a 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -273,13 +273,13 @@ namespace impl nmos::experimental::make_control_class_property_descriptor(U("version"), { 3, 5 }, version_property, U("NcVersionCode")), }; // Property descriptor for optional fields - if (sender_data.has_field(U("caps"))) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("caps"),{ 3, 6 }, caps_property, U("NcSenderCapsType"))); - if (sender_data.has_field(U("device_id"))) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("description"), { 3, 7 }, device_id_property, U("NcUuid"))); - if (sender_data.has_field(U("flow_id"))) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("flow_id"), { 3, 8 }, flow_id_property, U("NcUuid"))); - if (sender_data.has_field(U("interface_bindings"))) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("interface_bindings"), { 3, 9 }, interface_bindings_property, U("NcArray"))); - if (sender_data.has_field(U("manifest_href"))) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("manifest_href"), { 3, 10 }, manifest_href_property, U("NcUri"))); - if (sender_data.has_field(U("subscription"))) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("subscription"), { 3, 11 }, subscription_property, U("NcSenderObjectType"))); - if (sender_data.has_field(U("transport"))) snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("transport"), { 3, 12 }, transport_property, U("NcString"))); + snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("caps"), { 3, 6 }, caps_property, U("SenderCapsType"), true, true, false, false, web::json::value::null())); + snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("description"), { 3, 7 }, device_id_property, U("NcUuid"), true, true, false, false, web::json::value::null())); + snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("flow_id"), { 3, 8 }, flow_id_property, U("NcUuid"), true, true, false, false, web::json::value::null())); + snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("interface_bindings"), { 3, 9 }, interface_bindings_property, U("Array"))); + snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("manifest_href"), { 3, 10 }, manifest_href_property, U("NcUri"), true, true, false, false, web::json::value::null())); + snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("subscription"), { 3, 11 }, subscription_property, U("SenderObjectType"), true, true, false, false, web::json::value::null())); + snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("transport"), { 3, 12 }, transport_property, U("NcString"), true, true, false, false, web::json::value::null())); auto snd_class_id = nmos::nc::make_class_id(nmos::nc_worker_class_id, 0, { 5 }); From 81fcd09c57f23e19917aa09961007d718f8e22f3 Mon Sep 17 00:00:00 2001 From: Martin Weishart <41727377+maweit@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:58:28 +0100 Subject: [PATCH 05/14] Update Development/nmos-cpp-node/node_implementation.cpp Co-authored-by: jonathan-r-thorpe <64410119+jonathan-r-thorpe@users.noreply.github.com> --- Development/nmos-cpp-node/node_implementation.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 559a380a..43a32b74 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -339,13 +339,13 @@ namespace impl data[tags_property] = sender_data.at(U("tags")); data[version_property] = sender_data.at(U("version")); // Optional - if (sender_data.has_field(U("caps"))) data[caps_property] = sender_data.at(U("caps")); - if (sender_data.has_field(U("device_id"))) data[device_id_property] = sender_data.at(U("device_id")); - if (sender_data.has_field(U("flow_id"))) data[flow_id_property] = sender_data.at(U("flow_id")); - if (sender_data.has_field(U("interface_bindings"))) data[interface_bindings_property] = sender_data.at(U("interface_bindings")); - if (sender_data.has_field(U("manifest_href"))) data[manifest_href_property] = sender_data.at(U("manifest_href")); - if (sender_data.has_field(U("subscription"))) data[subscription_property] = sender_data.at(U("subscription")); - if (sender_data.has_field(U("transport"))) data[transport_property] = sender_data.at(U("transport")); + data[caps_property] = sender_data.has_field(U("caps")) ? sender_data.at(U("caps")) : web::json::value::null(); + data[device_id_property] = sender_data.has_field(U("device_id")) ? sender_data.at(U("device_id")) : web::json::value::null(); + data[flow_id_property] = sender_data.has_field(U("flow_id")) ? sender_data.at(U("flow_id")) : web::json::value::null(); + data[interface_bindings_property] = sender_data.has_field(U("interface_bindings")) ? sender_data.at(U("interface_bindings")) : web::json::value::null(); + data[manifest_href_property] = sender_data.has_field(U("manifest_href")) ? sender_data.at(U("manifest_href")) : web::json::value::null(); + data[subscription_property] = sender_data.has_field(U("subscription")) ? sender_data.at(U("subscription")) : web::json::value::null(); + data[transport_property] = sender_data.has_field(U("transport")) ? sender_data.at(U("transport")) : web::json::value::null(); auto snd_control_resource = nmos::control_protocol_resource{ nmos::is12_versions::v1_0, nmos::types::nc_worker, std::move(data), true }; return snd_control_resource; From 1bf05045bd0c5ea85a75b718b75d79543bee3b41 Mon Sep 17 00:00:00 2001 From: Martin Weishart <41727377+maweit@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:02:39 +0100 Subject: [PATCH 06/14] Update Development/nmos-cpp-node/node_implementation.cpp Co-authored-by: jonathan-r-thorpe <64410119+jonathan-r-thorpe@users.noreply.github.com> --- Development/nmos-cpp-node/node_implementation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 43a32b74..4c3ee48f 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -239,7 +239,7 @@ namespace impl {U("active"), U("active"), U("NcBoolean")}, {U("receiver_id"), U("receiver_id"), U("NcUuid")} }; - auto subs_data_type = make_object_object_datatype(subs_array, U("NcSenderObjectType"), U("NcSenderObjectType")); + auto subs_data_type = make_object_object_datatype(subs_array, U("SenderObjectType"), U("SenderObjectType")); control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ subs_data_type })); auto caps_data_type = make_object_array_datatype(U("Media Type Array"), U("media_types"), U("NcSenderCapsType"), U("NcSenderCapsType")); From 27013ef1b03b88efda12b877b13efe1a68c91d8d Mon Sep 17 00:00:00 2001 From: Martin Weishart <41727377+maweit@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:02:51 +0100 Subject: [PATCH 07/14] Update Development/nmos-cpp-node/node_implementation.cpp Co-authored-by: jonathan-r-thorpe <64410119+jonathan-r-thorpe@users.noreply.github.com> --- Development/nmos-cpp-node/node_implementation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 4c3ee48f..f1aabd78 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -242,7 +242,7 @@ namespace impl auto subs_data_type = make_object_object_datatype(subs_array, U("SenderObjectType"), U("SenderObjectType")); control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ subs_data_type })); - auto caps_data_type = make_object_array_datatype(U("Media Type Array"), U("media_types"), U("NcSenderCapsType"), U("NcSenderCapsType")); + auto caps_data_type = make_object_array_datatype(U("Media Type Array"), U("media_types"), U("SenderCapsType"), U("SenderCapsType")); control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ caps_data_type })); auto tags_data_type = make_object_array_datatype(U("Hints Type Array"), U("urn:x-nmos:tag:grouphint/v1.0"), U("NcSenderTagsType"), U("NcSenderTagsType")); From 6d3a0085db375a2d00168942281d9014c764f631 Mon Sep 17 00:00:00 2001 From: Martin Weishart <41727377+maweit@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:03:03 +0100 Subject: [PATCH 08/14] Update Development/nmos-cpp-node/node_implementation.cpp Co-authored-by: jonathan-r-thorpe <64410119+jonathan-r-thorpe@users.noreply.github.com> --- Development/nmos-cpp-node/node_implementation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index f1aabd78..09a992cf 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -245,7 +245,7 @@ namespace impl auto caps_data_type = make_object_array_datatype(U("Media Type Array"), U("media_types"), U("SenderCapsType"), U("SenderCapsType")); control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ caps_data_type })); - auto tags_data_type = make_object_array_datatype(U("Hints Type Array"), U("urn:x-nmos:tag:grouphint/v1.0"), U("NcSenderTagsType"), U("NcSenderTagsType")); + auto tags_data_type = make_object_array_datatype(U("Hints Type Array"), U("urn:x-nmos:tag:grouphint/v1.0"), U("SenderTagsType"), U("SenderTagsType")); control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ tags_data_type })); auto array_data_type = nmos::nc::details::make_datatype_typedef(U("Array Type"), U("NcArray"), true, U("NcString"), value::null()); From c6ce7184556aa476d61d8a10347be58d0c0f0bb0 Mon Sep 17 00:00:00 2001 From: Martin Weishart <41727377+maweit@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:03:12 +0100 Subject: [PATCH 09/14] Update Development/nmos-cpp-node/node_implementation.cpp Co-authored-by: jonathan-r-thorpe <64410119+jonathan-r-thorpe@users.noreply.github.com> --- Development/nmos-cpp-node/node_implementation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 09a992cf..5a2af2ba 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -269,7 +269,7 @@ namespace impl nmos::experimental::make_control_class_property_descriptor(U("description"), { 3, 1 }, description_property, U("NcString")), nmos::experimental::make_control_class_property_descriptor(U("resource_id"), { 3, 2 }, uuid_property, U("NcUuid")), nmos::experimental::make_control_class_property_descriptor(U("label"), { 3, 3 }, label_property, U("NcString")), - nmos::experimental::make_control_class_property_descriptor(U("tags"), { 3, 4 }, tags_property, U("NcSenderTagsType")), + nmos::experimental::make_control_class_property_descriptor(U("tags"), { 3, 4 }, tags_property, U("SenderTagsType")), nmos::experimental::make_control_class_property_descriptor(U("version"), { 3, 5 }, version_property, U("NcVersionCode")), }; // Property descriptor for optional fields From 9d9c4aad7fb698d76cc30aaa50b3a1b42ee80361 Mon Sep 17 00:00:00 2001 From: Martin Weishart <41727377+maweit@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:39:44 +0100 Subject: [PATCH 10/14] Refactor audio sender descriptors to include class ID --- Development/nmos-cpp-node/node_implementation.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 5a2af2ba..2069459d 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -206,9 +206,9 @@ namespace impl // Example of audio sender control class descriptors. Used below for setting up a sender control class - nmos::nc_class_id + void make_audio_sender_descriptors(nmos::experimental::control_protocol_state & control_protocol_state, - const web::json::value & sender_data) { + const web::json::value & sender_data, nmos::nc_class_id snd_class_id) { using web::json::value; using web::json::value_of; @@ -281,8 +281,6 @@ namespace impl snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("subscription"), { 3, 11 }, subscription_property, U("SenderObjectType"), true, true, false, false, web::json::value::null())); snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("transport"), { 3, 12 }, transport_property, U("NcString"), true, true, false, false, web::json::value::null())); - auto snd_class_id = nmos::nc::make_class_id(nmos::nc_worker_class_id, 0, { 5 }); - // method and event descriptors are defined by defaults in the function prototype, so only need to pass the property descriptors utility::string_t descriptor_description = utility::string_t(U("AudioSenderControl")) + utility::string_t(U(" control class descriptor")); auto sender_control_class_descriptor = @@ -292,8 +290,6 @@ namespace impl // Insert class descriptor into the nmos-cpp framework control_protocol_state.insert(std::move(sender_control_class_descriptor)); - - return snd_class_id; } // Example of an audio sender control class @@ -1365,7 +1361,8 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr }; // audio sender control descriptors - auto audio_sender_class_id = impl::make_audio_sender_descriptors(control_protocol_state, audio_sender_for_control_protocol); + auto audio_sender_class_id = nmos::nc::make_class_id(nmos::nc_worker_class_id, 0, { 5 }); + impl::make_audio_sender_descriptors(control_protocol_state, audio_sender_for_control_protocol); // example root block auto root_block = nmos::make_root_block(); From 3696370dd8475877785d628ce06b13e9887156c3 Mon Sep 17 00:00:00 2001 From: Martin Weishart <41727377+maweit@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:41:24 +0100 Subject: [PATCH 11/14] Add audio sender class ID to descriptors function --- Development/nmos-cpp-node/node_implementation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 2069459d..ce80e452 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1362,7 +1362,7 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr // audio sender control descriptors auto audio_sender_class_id = nmos::nc::make_class_id(nmos::nc_worker_class_id, 0, { 5 }); - impl::make_audio_sender_descriptors(control_protocol_state, audio_sender_for_control_protocol); + impl::make_audio_sender_descriptors(control_protocol_state, audio_sender_for_control_protocol, audio_sender_class_id); // example root block auto root_block = nmos::make_root_block(); From 9d3e08b60d08f6fc3342f43258451f4f431f5063 Mon Sep 17 00:00:00 2001 From: jonathan-r-thorpe Date: Wed, 26 Nov 2025 15:15:38 +0000 Subject: [PATCH 12/14] Generalize Sender Control object. Add blocks for both control and monitor objects. --- .../nmos-cpp-node/node_implementation.cpp | 206 ++++++++++-------- 1 file changed, 117 insertions(+), 89 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index ce80e452..4a51baca 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -204,17 +204,15 @@ namespace impl }; std::vector nic_packet_counters; - - // Example of audio sender control class descriptors. Used below for setting up a sender control class - void - make_audio_sender_descriptors(nmos::experimental::control_protocol_state & control_protocol_state, - const web::json::value & sender_data, nmos::nc_class_id snd_class_id) { + // Example of sender control class descriptors. Used below for setting up a sender control class + void make_sender_control_descriptors(nmos::experimental::control_protocol_state & control_protocol_state, nmos::nc_class_id snd_class_id) + { using web::json::value; using web::json::value_of; // Datatype description for an object within an object - auto make_object_object_datatype = [](const std::vector> & obj_desc, - const utility::string_t & descriptor, const utility::string_t & type_name) { + auto make_struct_datatype = [](const std::vector>& obj_desc, + const utility::string_t& descriptor, const utility::string_t& type_name) { using web::json::value; auto fields = value::array(); for ( auto obj : obj_desc ) { @@ -225,31 +223,21 @@ namespace impl return ret; }; - // Datatype description for an array within an object - auto make_object_array_datatype = [](const utility::string_t & obj_descriptor, const utility::string_t & obj_type_name, - const utility::string_t & descriptor, const utility::string_t & type_name) { - using web::json::value; - auto fields = value::array(); - auto data = nmos::nc::details::make_field_descriptor(obj_descriptor, obj_type_name, U("NcString"), false, true, value::null()); - web::json::push_back(fields, std::move(data)); - auto ret = nmos::nc::details::make_datatype_descriptor_struct(descriptor, type_name, fields, value::null()); - return ret; + std::vector> subscription_struct_fields { + // description, name, type_name, is_sequence + {U("active"), U("active"), U("NcBoolean"), false}, + {U("receiver_id"), U("receiver_id"), U("NcUuid"), false} }; - std::vector> subs_array { - {U("active"), U("active"), U("NcBoolean")}, - {U("receiver_id"), U("receiver_id"), U("NcUuid")} - }; - auto subs_data_type = make_object_object_datatype(subs_array, U("SenderObjectType"), U("SenderObjectType")); - control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ subs_data_type })); - - auto caps_data_type = make_object_array_datatype(U("Media Type Array"), U("media_types"), U("SenderCapsType"), U("SenderCapsType")); - control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ caps_data_type })); - - auto tags_data_type = make_object_array_datatype(U("Hints Type Array"), U("urn:x-nmos:tag:grouphint/v1.0"), U("SenderTagsType"), U("SenderTagsType")); - control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ tags_data_type })); + auto subscription_data_type = make_struct_datatype(subscription_struct_fields, U("SubscriptionType"), U("SubscriptionType")); + control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ subscription_data_type })); - auto array_data_type = nmos::nc::details::make_datatype_typedef(U("Array Type"), U("NcArray"), true, U("NcString"), value::null()); - control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ array_data_type })); + std::vector> key_value_struct_fields{ + // description, name, type_name, is_sequence + {U("key"), U("key"), U("NcString"), false}, + {U("value"), U("value"), U("NcString"), true} + }; + auto key_value_data_type = make_struct_datatype(key_value_struct_fields, U("KeyValueStruct"), U("KeyValueStruct")); + control_protocol_state.insert(std::move(nmos::experimental::datatype_descriptor{ key_value_data_type })); const web::json::field_as_value caps_property{ U("caps") }; const web::json::field_as_string description_property{ U("description") }; @@ -264,46 +252,58 @@ namespace impl const web::json::field_as_string transport_property{ U("transport") }; const web::json::field version_property{ U("version") }; - // Define property descriptors: required fields + // Define property descriptors std::vector snd_control_property_descriptors = { - nmos::experimental::make_control_class_property_descriptor(U("description"), { 3, 1 }, description_property, U("NcString")), - nmos::experimental::make_control_class_property_descriptor(U("resource_id"), { 3, 2 }, uuid_property, U("NcUuid")), - nmos::experimental::make_control_class_property_descriptor(U("label"), { 3, 3 }, label_property, U("NcString")), - nmos::experimental::make_control_class_property_descriptor(U("tags"), { 3, 4 }, tags_property, U("SenderTagsType")), - nmos::experimental::make_control_class_property_descriptor(U("version"), { 3, 5 }, version_property, U("NcVersionCode")), + // Property descriptor for required fields + nmos::experimental::make_control_class_property_descriptor(U("description"), { 3, 1 }, description_property, U("NcString"), true, false, false, false, web::json::value::null()), + nmos::experimental::make_control_class_property_descriptor(U("resource_id"), { 3, 2 }, uuid_property, U("NcUuid"), true, false, false, false, web::json::value::null()), + nmos::experimental::make_control_class_property_descriptor(U("label"), { 3, 3 }, label_property, U("NcString"), true, false, false, false, web::json::value::null()), + nmos::experimental::make_control_class_property_descriptor(U("tags"), { 3, 4 }, tags_property, U("KeyValueStruct"), true, false, true, false, web::json::value::null()), + nmos::experimental::make_control_class_property_descriptor(U("version"), { 3, 5 }, version_property, U("NcVersionCode"), true, false, false, false, web::json::value::null()), + + // Property descriptor for optional fields + nmos::experimental::make_control_class_property_descriptor(U("caps"), { 3, 6 }, caps_property, U("KeyValueStruct"), true, true, true, false, web::json::value::null()), + nmos::experimental::make_control_class_property_descriptor(U("description"), { 3, 7 }, device_id_property, U("NcUuid"), true, true, false, false, web::json::value::null()), + nmos::experimental::make_control_class_property_descriptor(U("flow_id"), { 3, 8 }, flow_id_property, U("NcUuid"), true, true, false, false, web::json::value::null()), + nmos::experimental::make_control_class_property_descriptor(U("interface_bindings"), { 3, 9 }, interface_bindings_property, U("NcString"), true, false, true, false, web::json::value::null()), + nmos::experimental::make_control_class_property_descriptor(U("manifest_href"), { 3, 10 }, manifest_href_property, U("NcUri"), true, true, false, false, web::json::value::null()), + nmos::experimental::make_control_class_property_descriptor(U("subscription"), { 3, 11 }, subscription_property, U("SubscriptionType"), true, true, false, false, web::json::value::null()), + nmos::experimental::make_control_class_property_descriptor(U("transport"), { 3, 12 }, transport_property, U("NcString"), true, true, false, false, web::json::value::null()) }; - // Property descriptor for optional fields - snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("caps"), { 3, 6 }, caps_property, U("SenderCapsType"), true, true, false, false, web::json::value::null())); - snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("description"), { 3, 7 }, device_id_property, U("NcUuid"), true, true, false, false, web::json::value::null())); - snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("flow_id"), { 3, 8 }, flow_id_property, U("NcUuid"), true, true, false, false, web::json::value::null())); - snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("interface_bindings"), { 3, 9 }, interface_bindings_property, U("Array"))); - snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("manifest_href"), { 3, 10 }, manifest_href_property, U("NcUri"), true, true, false, false, web::json::value::null())); - snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("subscription"), { 3, 11 }, subscription_property, U("SenderObjectType"), true, true, false, false, web::json::value::null())); - snd_control_property_descriptors.push_back(nmos::experimental::make_control_class_property_descriptor(U("transport"), { 3, 12 }, transport_property, U("NcString"), true, true, false, false, web::json::value::null())); - // method and event descriptors are defined by defaults in the function prototype, so only need to pass the property descriptors - utility::string_t descriptor_description = utility::string_t(U("AudioSenderControl")) + utility::string_t(U(" control class descriptor")); + utility::string_t descriptor_description = utility::string_t(U("SenderControl")) + utility::string_t(U("Sender control class descriptor")); auto sender_control_class_descriptor = nmos::experimental::make_control_class_descriptor(descriptor_description, - snd_class_id, U("AudioSenderControl"), + snd_class_id, U("SenderControl"), snd_control_property_descriptors); // Insert class descriptor into the nmos-cpp framework control_protocol_state.insert(std::move(sender_control_class_descriptor)); } - // Example of an audio sender control class - static nmos::control_protocol_resource - make_audio_sender_control(const nmos::nc_class_id & snd_class_id, - const web::json::value & sender_data, - const nmos::nc_oid & oid, - const nmos::nc_oid & parent_oid, - const web::json::value touchpoint, - slog::base_gate & gate) { + // Example of an sender control class + static nmos::control_protocol_resource make_sender_control(const nmos::nc_class_id & snd_class_id, + const web::json::value & sender_data, + const nmos::nc_oid & oid, + const nmos::nc_oid & parent_oid, + const web::json::value touchpoint, + slog::base_gate & gate) + { using web::json::value; using web::json::value_of; - const web::json::field_as_value caps_property{ U("caps") }; + auto make_key_value_array = [](const value& object) + { + auto key_value_array = value::array(); + + for (auto iter = object.as_object().begin(); iter != object.as_object().end(); iter++) + { + web::json::push_back(key_value_array, web::json::value_of({ { U("key"), (*iter).first}, {U("value"), (*iter).second } })); + } + return key_value_array; + }; + + const web::json::field_as_array caps_property{ U("caps") }; const web::json::field_as_string description_property{ U("description") }; const web::json::field_as_string device_id_property{ U("device_id") }; const web::json::field_as_string flow_id_property{ U("flow_id") }; @@ -316,7 +316,7 @@ namespace impl const web::json::field_as_string transport_property{ U("transport") }; const web::json::field version_property{ U("version") }; - auto snd_name = U("AudioSenderControl"); + auto snd_name = U("SenderControl"); // define a function for instantiating object instances of the class auto data = nmos::nc::details::make_worker(snd_class_id, oid, true, @@ -332,21 +332,19 @@ namespace impl data[description_property] = sender_data.at(U("description")); data[uuid_property] = sender_data.at(U("id")); data[label_property] = sender_data.at(U("label")); - data[tags_property] = sender_data.at(U("tags")); + data[tags_property] = make_key_value_array(sender_data.at(U("tags"))); data[version_property] = sender_data.at(U("version")); // Optional - data[caps_property] = sender_data.has_field(U("caps")) ? sender_data.at(U("caps")) : web::json::value::null(); + data[caps_property] = sender_data.has_field(U("caps")) ? make_key_value_array(sender_data.at(U("caps"))) : web::json::value::array(); data[device_id_property] = sender_data.has_field(U("device_id")) ? sender_data.at(U("device_id")) : web::json::value::null(); data[flow_id_property] = sender_data.has_field(U("flow_id")) ? sender_data.at(U("flow_id")) : web::json::value::null(); - data[interface_bindings_property] = sender_data.has_field(U("interface_bindings")) ? sender_data.at(U("interface_bindings")) : web::json::value::null(); + data[interface_bindings_property] = sender_data.has_field(U("interface_bindings")) ? sender_data.at(U("interface_bindings")) : web::json::value::array(); data[manifest_href_property] = sender_data.has_field(U("manifest_href")) ? sender_data.at(U("manifest_href")) : web::json::value::null(); data[subscription_property] = sender_data.has_field(U("subscription")) ? sender_data.at(U("subscription")) : web::json::value::null(); data[transport_property] = sender_data.has_field(U("transport")) ? sender_data.at(U("transport")) : web::json::value::null(); - auto snd_control_resource = nmos::control_protocol_resource{ nmos::is12_versions::v1_0, nmos::types::nc_worker, std::move(data), true }; - return snd_control_resource; + return nmos::control_protocol_resource{ nmos::is12_versions::v1_0, nmos::types::nc_worker, std::move(data), true }; } - } // forward declarations for node_implementation_thread @@ -541,7 +539,6 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr if (!insert_resource_after(delay_millis, model.node_resources, std::move(device), gate)) throw node_implementation_init_exception(); } - web::json::value audio_sender_for_control_protocol; // example sources, flows and senders for (int index = 0; index < how_many; ++index) { @@ -651,9 +648,6 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr sender.data[nmos::fields::bit_rate] = value(transport_bit_rate); sender.data[nmos::fields::st2110_21_sender_type] = value(nmos::st2110_21_sender_types::type_N.name); } - else if (impl::ports::audio == port) { - audio_sender_for_control_protocol = sender.data; - } impl::set_label_description(sender, port, index); impl::insert_group_hint(sender, port, index); @@ -1360,9 +1354,9 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr return nmos::control_protocol_resource{ nmos::is12_versions::v1_0, nmos::types::nc_worker, std::move(data), true }; }; - // audio sender control descriptors - auto audio_sender_class_id = nmos::nc::make_class_id(nmos::nc_worker_class_id, 0, { 5 }); - impl::make_audio_sender_descriptors(control_protocol_state, audio_sender_for_control_protocol, audio_sender_class_id); + // example audio sender control descriptors + auto sender_control_class_id = nmos::nc::make_class_id(nmos::nc_worker_class_id, 0, { 5 }); + impl::make_sender_control_descriptors(control_protocol_state, sender_control_class_id); // example root block auto root_block = nmos::make_root_block(); @@ -1378,13 +1372,6 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr // example bulk properties manager auto bulk_properties_manager = nmos::make_bulk_properties_manager(++oid); - // example sender control - auto sender_control_block = impl::make_audio_sender_control(audio_sender_class_id, - audio_sender_for_control_protocol, - ++oid, nmos::root_block_oid, - value_of({ { nmos::nc::details::make_touchpoint_nmos({nmos::ncp_touchpoint_resource_types::sender, audio_sender_for_control_protocol.at(U("id")).as_string()}) } }), - gate); - // example stereo gain const auto stereo_gain_oid = ++oid; auto stereo_gain = nmos::make_block(stereo_gain_oid, nmos::root_block_oid, U("stereo-gain"), U("Stereo gain"), U("Stereo gain block")); @@ -1434,12 +1421,12 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr // making an object rebuildable allows read only properties to be modified by the Configuration API in Rebuild mode nmos::make_rebuildable(example_control); - const auto receivers_block_oid = ++oid; - auto receivers_block = nmos::make_block(receivers_block_oid, nmos::root_block_oid, U("receivers"), U("Receiver Monitors"), U("Receiver Monitors")); + const auto receiver_monitors_block_oid = ++oid; + auto receiver_monitors_block = nmos::make_block(receiver_monitors_block_oid, nmos::root_block_oid, U("receiver-monitors"), U("Receiver Monitors"), U("Receiver Monitors")); // making a block rebuildable allows block members to be added or removed by the Configuration API in Rebuild mode - nmos::make_rebuildable(receivers_block); + nmos::make_rebuildable(receiver_monitors_block); // restrict the allowed classes for members of this block - nmos::set_block_allowed_member_classes(receivers_block, {nmos::nc_receiver_monitor_class_id}); + nmos::set_block_allowed_member_classes(receiver_monitors_block, {nmos::nc_receiver_monitor_class_id}); // example receiver-monitor(s) { @@ -1453,15 +1440,22 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr utility::ostringstream_t role; role << U("receiver-monitor-") << ++count; const auto& receiver = nmos::find_resource(model.node_resources, receiver_id); - auto receiver_monitor = nmos::make_receiver_monitor(++oid, true, receivers_block_oid, role.str(), nmos::fields::label(receiver->data), nmos::fields::description(receiver->data), value_of({ { nmos::nc::details::make_touchpoint_nmos({nmos::ncp_touchpoint_resource_types::receiver, receiver_id}) } })); + auto receiver_monitor = nmos::make_receiver_monitor(++oid, true, receiver_monitors_block_oid, role.str(), nmos::fields::label(receiver->data), nmos::fields::description(receiver->data), value_of({ { nmos::nc::details::make_touchpoint_nmos({nmos::ncp_touchpoint_resource_types::receiver, receiver_id}) } })); // optionally indicate dependencies within the device model - nmos::set_object_dependency_paths(receiver_monitor, {{U("root"), U("receivers")}}); + nmos::set_object_dependency_paths(receiver_monitor, {{U("root"), U("receiver-monitors")}}); // add receiver-monitor to receivers-block - nmos::nc::push_back(receivers_block, receiver_monitor); + nmos::nc::push_back(receiver_monitors_block, receiver_monitor); } } } + const auto sender_monitors_block_oid = ++oid; + auto sender_monitors_block = nmos::make_block(sender_monitors_block_oid, nmos::root_block_oid, U("sender-monitors"), U("Sender Monitors"), U("Sender Monitors")); + // making a block rebuildable allows block members to be added or removed by the Configuration API in Rebuild mode + nmos::make_rebuildable(sender_monitors_block); + // restrict the allowed classes for members of this block + nmos::set_block_allowed_member_classes(sender_monitors_block, { nmos::nc_sender_monitor_class_id }); + // example sender-monitor(s) { int count = 0; @@ -1474,10 +1468,40 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr utility::ostringstream_t role; role << U("sender-monitor-") << ++count; const auto& sender = nmos::find_resource(model.node_resources, sender_id); - const auto sender_monitor = nmos::make_sender_monitor(++oid, true, nmos::root_block_oid, role.str(), nmos::fields::label(sender->data), nmos::fields::description(sender->data), value_of({ { nmos::nc::details::make_touchpoint_nmos({nmos::ncp_touchpoint_resource_types::sender, sender_id}) } })); - + auto sender_monitor = nmos::make_sender_monitor(++oid, true, nmos::root_block_oid, role.str(), nmos::fields::label(sender->data), nmos::fields::description(sender->data), value_of({ { nmos::nc::details::make_touchpoint_nmos({nmos::ncp_touchpoint_resource_types::sender, sender_id}) } })); + // optionally indicate dependencies within the device model + nmos::set_object_dependency_paths(sender_monitor, { {U("root"), U("sender-monitors")} }); // add sender-monitor to root-block - nmos::nc::push_back(root_block, sender_monitor); + nmos::nc::push_back(sender_monitors_block, sender_monitor); + } + } + } + + const auto sender_controls_block_oid = ++oid; + auto sender_controls_block = nmos::make_block(sender_controls_block_oid, nmos::root_block_oid, U("sender-controls"), U("Sender Controls"), U("Sender Controls")); + + // example audio sender control(s) + { + int count = 0; + for (int index = 0; index < how_many; ++index) + { + for (const auto& port : rtp_receiver_ports) + { + // only create audio sender controls for audio senders + //if (impl::ports::audio != port) continue; + + const auto sender_id = impl::make_id(seed_id, nmos::types::sender, port, index); + + utility::ostringstream_t role; + role << U("sender-control-") << ++count; + const auto& sender = nmos::find_resource(model.node_resources, sender_id); + auto sender_control = impl::make_sender_control(sender_control_class_id, + sender->data, + ++oid, nmos::root_block_oid, + value_of({ { nmos::nc::details::make_touchpoint_nmos({nmos::ncp_touchpoint_resource_types::sender, sender_id}) } }), + gate); + // add receiver-monitor to receivers-block + nmos::nc::push_back(sender_controls_block, sender_control); } } } @@ -1485,8 +1509,12 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr // example temperature-sensor const auto temperature_sensor = make_temperature_sensor(++oid, nmos::root_block_oid, U("temperature-sensor"), U("Temperature Sensor"), U("Temperature Sensor block"), value::null(), value::null(), 0.0, U("Celsius")); - // add receivers-block to root-block - nmos::nc::push_back(root_block, receivers_block); + // add receiver-monitors-block to root-block + nmos::nc::push_back(root_block, receiver_monitors_block); + // add sender-monitors-block to root-block + nmos::nc::push_back(root_block, sender_monitors_block); + // add sender-controls-block to root-block + nmos::nc::push_back(root_block, sender_controls_block); // add temperature-sensor to root-block nmos::nc::push_back(root_block, temperature_sensor); // add example-control to root-block From f98a5bcc3fe6b24b2688802e8e07853800f94709 Mon Sep 17 00:00:00 2001 From: jonathan-r-thorpe Date: Thu, 27 Nov 2025 15:43:01 +0000 Subject: [PATCH 13/14] Fix array property --- .../nmos-cpp-node/node_implementation.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 4a51baca..2c0ce676 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -216,7 +216,7 @@ namespace impl using web::json::value; auto fields = value::array(); for ( auto obj : obj_desc ) { - auto data = nmos::nc::details::make_field_descriptor(std::get<0>(obj), std::get<1>(obj), std::get<2>(obj), true, false, value::null()); + auto data = nmos::nc::details::make_field_descriptor(std::get<0>(obj), std::get<1>(obj), std::get<2>(obj), true, std::get<3>(obj), value::null()); web::json::push_back(fields, std::move(data)); } auto ret = nmos::nc::details::make_datatype_descriptor_struct(descriptor, type_name, fields, value::null()); @@ -282,10 +282,11 @@ namespace impl } // Example of an sender control class - static nmos::control_protocol_resource make_sender_control(const nmos::nc_class_id & snd_class_id, - const web::json::value & sender_data, - const nmos::nc_oid & oid, - const nmos::nc_oid & parent_oid, + static nmos::control_protocol_resource make_sender_control(const nmos::nc_class_id& snd_class_id, + const web::json::value& sender_data, + const nmos::nc_oid& oid, + const utility::string_t& role, + const nmos::nc_oid& parent_oid, const web::json::value touchpoint, slog::base_gate & gate) { @@ -321,8 +322,8 @@ namespace impl auto data = nmos::nc::details::make_worker(snd_class_id, oid, true, parent_oid, - snd_name, - web::json::value(snd_name), + role, + web::json::value(role), U("Sender resource data"), touchpoint, web::json::value::null(), @@ -1497,7 +1498,8 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr const auto& sender = nmos::find_resource(model.node_resources, sender_id); auto sender_control = impl::make_sender_control(sender_control_class_id, sender->data, - ++oid, nmos::root_block_oid, + ++oid, role.str(), + nmos::root_block_oid, value_of({ { nmos::nc::details::make_touchpoint_nmos({nmos::ncp_touchpoint_resource_types::sender, sender_id}) } }), gate); // add receiver-monitor to receivers-block From 19d3db60bece2a4a9e2accfb9d0d3788704a918e Mon Sep 17 00:00:00 2001 From: jonathan-r-thorpe Date: Fri, 28 Nov 2025 11:29:46 +0000 Subject: [PATCH 14/14] Fix parent oids --- Development/nmos-cpp-node/node_implementation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 2c0ce676..85ea5900 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1469,7 +1469,7 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr utility::ostringstream_t role; role << U("sender-monitor-") << ++count; const auto& sender = nmos::find_resource(model.node_resources, sender_id); - auto sender_monitor = nmos::make_sender_monitor(++oid, true, nmos::root_block_oid, role.str(), nmos::fields::label(sender->data), nmos::fields::description(sender->data), value_of({ { nmos::nc::details::make_touchpoint_nmos({nmos::ncp_touchpoint_resource_types::sender, sender_id}) } })); + auto sender_monitor = nmos::make_sender_monitor(++oid, true, sender_monitors_block_oid, role.str(), nmos::fields::label(sender->data), nmos::fields::description(sender->data), value_of({ { nmos::nc::details::make_touchpoint_nmos({nmos::ncp_touchpoint_resource_types::sender, sender_id}) } })); // optionally indicate dependencies within the device model nmos::set_object_dependency_paths(sender_monitor, { {U("root"), U("sender-monitors")} }); // add sender-monitor to root-block @@ -1499,7 +1499,7 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr auto sender_control = impl::make_sender_control(sender_control_class_id, sender->data, ++oid, role.str(), - nmos::root_block_oid, + sender_controls_block_oid, value_of({ { nmos::nc::details::make_touchpoint_nmos({nmos::ncp_touchpoint_resource_types::sender, sender_id}) } }), gate); // add receiver-monitor to receivers-block