Okay, here is my attempt to push this effort forward a little bit. I am not yet ready to offer a solution, but it is time to create a checkpoint with my progress to date in the hopes that it will attract constructive criticism and fresh ideas. To understand the context, one needs to read the origin of this thread and also the conversation in Choosing Message and Service IDs.
Requirements
First, I need to restate the objectives here. The high-level business requirement is to accelerate the uptake of UAVCAN v1 in its early days by simplifying deployment and integration of v1-enabled systems. This is to be done by automating the integration and the initial configuration of some of the commonly occurring hardware setups. UX-wise this is intended to resemble, say, standard USB classes, where some of the popular types of devices are picked up by the host system automatically without the need to install device drivers or configuring anything. Extending this analogy, some of the more sophisticated use cases will require manual intervention like uncommon USB devices require the installation of custom drivers.
The set of such supported configurations will be intentionally kept small. There is an expectation that in the sense of their functionality and quantity, UAVCAN devices generally follow the Pareto principle, such that supporting a carefully chosen small subset of popular configurations will allow us to cover a large number of practical use cases, relegating the rest to manual configuration.
It is vital to understand that avoiding manual configuration entirely is an anti-goal. To solve the problem of configuration in general, one necessarily has to involve understanding of the system, its architecture, data flows, and the business logic in general (not to mention vital configuration parameters that have nothing to do with data distribution, like ESC indices). These are necessarily managed by the human architect, and therefore, there may be no possibility to solve the problem in general (unless one is willing to rely on advanced artificial intelligence).
Complexity aside, it is also relevant that UAVCAN is primarily designed for high-integrity applications, where it may be preferred to have full control over the configuration of the system, which also may not be compatible with the auto-configuration feature. This side of the argument is similar to the plug-and-play node-ID allocation procedure, which is commented on by the Specification as follows (section 5.13.2 “Plug-and-play nodes”):
Remember that the plug-and-play feature is entirely optional and it is expected that applications where a high degree of determinism and robustness is expected are unlikely to benefit from it.
Implementing automatic configuration in a truly distributed system (which UAVCAN is primarily designed for) is harder compared to a centralized system. Since the business requirement is to simplify the uptake of the technology in its early days and seeing that a large subset of today’s practical uses of UAVCAN implement centralized (to some extent) networks, the auto-configuration capability will be focusing on centralized networks in the first place.
That is, auto-configuration is intended to be an optional minor helper feature rather than a core capability. It may somewhat overlap with the UAVCAN as a ROS middleware project because it also involves automatic port-ID configuration, although the context is rather different.
Approach
Considering the optionality of this feature, it has to be kept non-intrusive, such that supporting it would not put extra constraints on the implementation (the ROS design guide communicates a similar preference saying "we don’t wrap your main()
").
Upon some pondering, I ended up with the following idea. Suppose that in network service specifications we define a recommended name for each subject/service. For instance, for the battery service, we would have this:
# SUBJECT NAME TYPE TYP. RATE [Hz]
# source reg.drone.phy.electricity.SourceTs 1...100
# status reg.drone.srv.battery.Status ~1
# parameters reg.drone.srv.battery.Parameters ~0.2
So there are three subjects: source
, status
, and parameters
.
Naturally, enforcing an exact port name is not possible because in that case, different service specifications may conflict with each other (e.g., a servo service also has a status
subject). Hence, implementations are to be allowed to add arbitrary prefixes before the port name.
Imagine a made-up node that implements two smart battery services (primary and secondary) and a servo service (suppose we call it the main drive); then it might have the following registers (among others):
uavcan.pub.battery.primary.source.id
uavcan.pub.battery.primary.status.id
uavcan.pub.battery.primary.parameters.id
uavcan.pub.battery.secondary.source.id
uavcan.pub.battery.secondary.status.id
uavcan.pub.battery.secondary.parameters.id
uavcan.sub.main_drive.setpoint.id
uavcan.sub.main_drive.readiness.id
uavcan.pub.main_drive.feedback.id
uavcan.pub.main_drive.status.id
uavcan.pub.main_drive.power.id
uavcan.pub.main_drive.dynamics.id
By virtue of sharing common prefixes, the registers clearly define three services:
battery.primary
battery.secondary
main_drive
So far this seems like a sensible design rather than an arbitrary convention — a standalone developer is likely to adopt this approach of grouping registers independently even if it is not explicitly documented anywhere because it just makes sense. It should be kept this simple. Attempting to enforce too much structure upon the implementation might defeat the flexibility of UAVCAN, we don’t want that.
With semantic port grouping in place, our job is basically done here. What is left is to announce to the external auto-configuration authority (like the flight management unit) that a given group of ports conforms to a published network service definition. It may seem trivial but one should observe that a given set of ports or a subset thereof may conform to more than one service definition. For instance, the servo and the ESC services are, in fact, extremely similar (the latter is nearly a subset of the former), so it is natural to expect that a COTS node that implements one will also implement the other for the sake of greater application flexibility. In UML this is expressed as follows:
The objective then is to communicate to the auto-configuration authority that a given port is part of a given (set of) network service specifications.
Service specifications are currently described in plain prose in DSDL comments, which is the accepted practice in many applications, from ROS (see any package documentation) to HTTP REST API (see any web API specs). Various automation solutions exist and are practiced whenever sensible; in the web API case, Swagger is an interesting example. For UAVCAN, the prospect of providing rigorous machine-readable network service definitions has been explored in the past, and it is, in fact, something that is likely to be worked on in the long term, but it is not manifested on our roadmap in any concrete terms yet. Such automation is a highly complex feature that we will not be able to commence any work on until we have is a solid empirical base, which means that it is at least years away from now.
But I digress. Having semantically grouped registers in place, we need to announce that a given semantic group conforms to a network service specification. Service specifications are defined in plain prose in DSDL comments. DSDL comments, naturally, reside in DSDL files, which reside in DSDL namespaces. Ergo, the proposal is to use the DSDL namespace name as the network service name. For example, if the smart battery network service is defined in the namespace reg.drone.srv.battery
, then it would be the name of the service.
Putting the above together, we define a persistent immutable string-typed register named after the network service it implements (reg.drone.srv.battery
). The value would be the semantic prefix of the group of ports that implement said service.
The example node introduced above would then have the following additional registers:
Register name |
Register type |
Value |
reg.drone.srv.battery |
string |
battery. |
reg.drone.srv.actuator.servo |
string |
main_drive. |
reg.drone.srv.actuator.esc |
string |
main_drive. |
Observe how the main drive group implements two distinct (but similar) services.
The rest should be obvious:
-
For any unconfigured node, the auto-configuration authority checks if there are any registers that match the names of the services it is able to auto-configure.
-
For each matching service, it reads the port-ID configuration registers whose names begin with the specified prefix. Using reg.drone.srv.battery
as an example, the read would return:
battery.primary.source
battery.primary.status
battery.primary.parameters
battery.secondary.source
battery.secondary.status
battery.secondary.parameters
- Next, it splits out the last component, by virtue of which it detects that there are two distinct service instances:
battery.primary
battery.secondary
- The job of the auto-configuration service is done here because the remaining activities are implementation-specific. For example, a centralized autopilot may be programmed to assign the first occurring smart battery subjects some pre-defined subject-IDs.
The proposal is rough around the edges but I think it illustrates the idea well. It may be implemented in an experimental fashion but it is far from being production-ready. Once validated extensively, it may be weakly endorsed in a domain-specific UAVCAN-based standard like DS-015, with some possibility that it may end up in the core UAVCAN standard one day.