Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Doc amendment for uavcan.register.Access: mapping between registers and environment variables #109

Merged
merged 3 commits into from
Mar 16, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 94 additions & 24 deletions uavcan/register/384.Access.1.0.uavcan
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# This service is used to write and read a register. Write is optional, it is performed if the value provided in
# the request is not empty.
# Registers are strongly-typed named values used to store the configuration parameters of a node.
# This service is used to write and read a register.
#
#
# READ/WRITE BEHAVIORS
#
# The write operation is performed first, unless skipped by sending an empty value in the request.
# The server may attempt to convert the type of the value to the proper type if there is a type mismatch
# The server may attempt to convert the type of the supplied value to the correct type if there is a type mismatch
# (e.g. uint8 may be converted to uint16); however, servers are not required to perform implicit type conversion,
# and the rules of such conversion are not explicitly specified, so this behavior should not be relied upon.
#
Expand All @@ -15,33 +18,66 @@
# case properly.
#
# The timestamp provided in the response corresponds to the time when the register was read. The timestamp may
# be empty if the server does not support timestamping or its clock is not yet synchronized with the bus.
# be empty if the server does not support timestamping or its clock is not (yet) synchronized with the network.
#
# If only read is desired, but not write, the caller shall provide a value of type Empty. That will signal the server
# If only read is desired, but not write, the caller shall provide a value of type 'empty'. That will signal the server
# that the write operation shall be skipped, and it will proceed to read the register immediately.
#
# If the requested register does not exist, the write operation will have no effect and the returned value will be
# empty. Existing registers should not return Empty when read since that would make them indistinguishable from
# empty. Existing registers should not return 'empty' when read since that would make them indistinguishable from
# nonexistent registers.
#
#
# REGISTER DEFINITION REQUIREMENTS
#
# Registers shall never change their type or flags as long as the server is running. Meaning that:
# - Mutability and persistence flags cannot change their states.
# - Read operations shall always return values of the same type and same dimensionality.
# The dimensionality requirement does not apply to inherently variable-length values such as strings and
# unstructured chunks.
#
# In order to discover the type of a register, the caller needs to invoke this service with the write request set
# to Empty. The response will contain the current value of the register with the type information (which doesn't
# change).
# Register name should contain only:
# - Lowercase ASCII alphanumeric characters (a-z, 0-9)
# - Full stop (.)
# - Low line (underscore) (_)
# With the following limitations/recommendations:
# - The name shall not begin with a decimal digit (0-9).
# - The name shall neither begin nor end with a full stop.
# - A low line shall not be followed by a non-alphanumeric character.
# - The name should contain at least one full stop character.
# Other patterns and ASCII characters are reserved for special function registers (introduced below).
#
#
# ENVIRONMENT VARIABLES
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really hate encouraging environment variables as a primary mechanism. It's fine as a secondary but I'd rather we define a configuration file standard as the primary mechanism. Can we not just use the same mapping but encourage a file of KEY=VALUE\n as the way to specify each?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Environment variables are not meant to be directly used by the human integrator/developer excepting some unusual circumstances that require low-level access. They are convenient because they are present virtually everywhere and are familiar to virtually everybody, so it is a solid foundation to build upon. What you are describing can be implemented on top of the environment variable conventions that we define here. For instance, the configuration file-like behavior can be obtained using shell scripts or the orchestrator that you are already familiar with.

# my-configuration-file
UAVCAN__NODE__ID=42
UAVCAN__UDP__IFACE=127.9.0.0
UAVCAN__SUB__TEMPERATURE_SETPOINT__ID=2345
UAVCAN__SUB__TEMPERATURE_MEASUREMENT__ID=2346
UAVCAN__PUB__HEATER_VOLTAGE__ID=2347
UAVCAN__SRV__LEAST_SQUARES__ID=123
UAVCAN__DIAGNOSTIC__SEVERITY=2
$ . my-configuration-file
$ ./run_this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

environment variables are a pain in automation. They are only suitable as overrides provided for interactive environments.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate why? Environment variables are trivial to rely on because they provide a reasonably complete solution whereas a configuration parameter file would be a much heavier addition to the standard. Automation solutions built on top of that will also have to concern themselves with creating and managing the configuration files which carries more logic and is more error-prone.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

environment variables infer a shell environment. We are creating a dependency on the presence of a shell.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is by design:

This section applies only to software nodes executed in a high-level operating system that supports environment variables or an equivalent mechanism.

A deeply embedded system is unlikely to source its configuration from a shell or a generic configuration file, so it doesn't seem relevant for that use case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what to say. I think it's a bad design choice. You can override my opinion if you disagree.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if shell dependency is intentional and user-friendliness is not relevant, what makes it bad?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. It's an abuse of environment variables since you are basically treating it as a list. If anything it should be a delineated value within a single environment variable to be consistent with things like $PATH
  2. Not all build automation tools support environment variables but they all support files.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. The objective is to assign registers selectively such that each register is treated as an independent entity. A node process that is unaware of register X would not read the corresponding environment variable, so unless I misunderstood what you mean we don't seem to be following a list-like semantics here.

  2. This is meant to be a method of integration-time/runtime configuration management. What build tools are you referring to and why is this relevant?

#
# This section applies only to software nodes executed in a high-level operating system that supports environment
# variables or an equivalent mechanism.
#
# When a software node is launched, it is usually necessary to provide some of its configuration information early,
# particularly that which is related to UAVCAN networking, before the node is started. Environment variables offer
# a convenient way of addressing this. Software nodes that support the register interface should evaluate the
# available environment variables during initialization and update their registers (whether they are stored in
# a persistent storage or in memory) accoringly. This should be completed before the first register read access.
#
# A register name is mapped to an environment variable name as follows:
# - the name is upper-cased;
# - full stop characters are replaced with double low line characters.
# For example: 'motor.inductance_dq' is mapped to 'MOTOR__INDUCTANCE_DQ'.
#
# Register name may contain:
# - All ASCII alphanumeric characters (a-z, A-Z, 0-9)
# - Dot (.)
# - Underscore (_)
# - Minus (-)
# All other printable non-whitespace ASCII characters are reserved for standard functions;
# they may appear in register names to support such standard functions defined by the register protocol,
# but they cannot be freely used by applications outside of such standard functions.
# Register values are represented in environment variables as follows:
# - string: utf-8 or platform-specific
# - unstructured: as-is
# - bit, integer*, natural*, real*: space-separated decimals
#
# If an environment variable matches the name of an existing register but its value cannot be converted to the
# register's type, an error should be raised.
#
# If an environment variable does not match the name of any register, it may be ignored. However, if the implementation
# can reliably deduce the type and purpose of the register, it may create one automatically. This provision is to
# support applications where the register schema may be altered by configuration.
#
#
# SPECIAL FUNCTION REGISTERS
#
# The following optional special function register names are defined:
# - suffix '<' is used to define an immutable persistent value that contains the maximum value
Expand All @@ -56,21 +92,24 @@
# - default value is contained in the register named "system.parameter=" (optional)
#
# The type and dimensionality of the special function registers containing the minimum, maximum, and the default
# value of a register shall be the same as those of the register.
# value of a register shall be the same as those of the register they relate to.
#
# If a written value exceeds the minimum/maximum specified by the respective special function registers,
# the server may either adjust the value automatically, or to retain the old value, depending on which behavior
# suits the objectives of the application better.
# The values of registers containing non-scalar numerical entities should be compared elementwise.
#
#
# STANDARD REGISTERS
#
# The following table specifies the register name patterns that are reserved by the specification for
# common functions. These conventions are not mandatory to follow, but implementers are recommended to adhere because
# they enable enhanced introspection capabilities and simplify device configuration and diagnostics.
#
# REGISTER NAME PATTERN TYPE FLAGS RECOMMENDED DEFAULT
# =====================================================================================================================
#
# uavcan.node.id natural16 mutable, persistent 65535 (unset/PnP)
# uavcan.node.id natural16[1] mutable, persistent 65535 (unset/PnP)
thirtytwobits marked this conversation as resolved.
Show resolved Hide resolved
#
# Contains the node-ID of the local node. Values above the maximum valid node-ID for the current transport
# indicate that the node-ID is not set; if plug-and-play is supported, it will be used by the node to obtain an
Expand All @@ -86,7 +125,7 @@
#
# ---------------------------------------------------------------------------------------------------------------------
#
# uavcan.pub.PORT_NAME.id natural16 mutable, persistent 65535 (unset, invalid)
# uavcan.pub.PORT_NAME.id natural16[1] mutable, persistent 65535 (unset, invalid)
# uavcan.sub.PORT_NAME.id ditto ditto ditto
# uavcan.cln.PORT_NAME.id ditto ditto ditto
# uavcan.srv.PORT_NAME.id ditto ditto ditto
Expand Down Expand Up @@ -123,18 +162,49 @@
# For example, a register named "uavcan.pub.measurement.type" may contain "uavcan.si.sample.angle.Quaternion.1.0".
#
# ---------------------------------------------------------------------------------------------------------------------
#
# uavcan.diagnostic.*
#
# Prefix reserved for future use.
#
# ---------------------------------------------------------------------------------------------------------------------
#
# uavcan.can.bitrate natural32[2] implementation-defined implementation-defined
# uavcan.can.iface string mutable, persistent implementation-defined
#
# These registers are only relevant for nodes that support UAVCAN/CAN.
#
# uavcan.can.bitrate defines the CAN bus bit rate: the first value is the arbitration bit rate, the second is the
pavel-kirienko marked this conversation as resolved.
Show resolved Hide resolved
# data phase bit rate. Nodes that support only Classic CAN should ignore the second value. Nodes that support CAN FD
# should initialize in the Classic CAN mode (MTU 8 bytes, BRS flag not set) if the values are equal. If CAN bitrate
# is not configurable or is always auto-detected, this register may be omitted or made immutable; otherwise it should
# be mutable and persistent.
#
# uavcan.can.iface is only relevant for software nodes or nodes that are capable of using different CAN interfaces.
# The value is a space-separated list of CAN interface names to use. The name format is implementation-defined
# (for example, "can0").
#
# ---------------------------------------------------------------------------------------------------------------------
#
# uavcan.udp.*
#
# Prefix reserved for future use.
#
# ---------------------------------------------------------------------------------------------------------------------#
#
# uavcan.serial.*
#
# Prefix reserved for future use.
#
# ---------------------------------------------------------------------------------------------------------------------

Name.1.0 name
# The name of the accessed register. Shall not be empty.
# Use the List service to obtain the list of registers on the node.

@assert _offset_ % 8 == {0}

Value.1.0 value
# Value to be written. Empty if no write is required.

@assert _offset_.min % 8 == 0
@assert _offset_.max % 8 == 0
@sealed

---
Expand Down