diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 9661a109..85ea5900 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -203,6 +203,149 @@ namespace impl nmos::nc::counter late_packet_counter; }; std::vector nic_packet_counters; + + // 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_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 ) { + 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()); + 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} + }; + 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 })); + + 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") }; + 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 + std::vector snd_control_property_descriptors = { + // 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()) + }; + // 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("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("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 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 utility::string_t& role, + const nmos::nc_oid& parent_oid, + const web::json::value touchpoint, + slog::base_gate & gate) + { + using web::json::value; + using web::json::value_of; + + 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") }; + 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("SenderControl"); + // define a function for instantiating object instances of the class + auto data = nmos::nc::details::make_worker(snd_class_id, oid, + true, + parent_oid, + role, + web::json::value(role), + U("Sender resource data"), + touchpoint, + web::json::value::null(), + true); + + // Required + 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] = 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")) ? 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::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(); + + return nmos::control_protocol_resource{ nmos::is12_versions::v1_0, nmos::types::nc_worker, std::move(data), true }; + } } // forward declarations for node_implementation_thread @@ -1212,6 +1355,10 @@ 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 }; }; + // 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(); @@ -1275,12 +1422,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) { @@ -1294,15 +1441,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; @@ -1315,10 +1469,41 @@ 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, 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 - 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, role.str(), + 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 + nmos::nc::push_back(sender_controls_block, sender_control); } } } @@ -1326,8 +1511,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