Working with custom tires BusSignal in the equipment blocks
| It is recommended that you familiarize yourself with the blocks before using custom tire types. Bus Creator and Bus Selector. |
Engee provides support for custom structured signals through the type BusSignal (for more information, see here). This type allows you to combine logically related signals into a single bus structure, where each element has a name, type, and dimension (for example, temperature::Int, position::Vector{Float64}, status::Bool).
Such structures are convenient when interacting with external hardware that returns or accepts entire sets of parameters. So, instead of transmitting data as an unstructured set:
(10, 3.2, [1, 2, 3])
You can use a bus with explicitly specified names.:
(temperature = 10, pressure = 3.2, position = [1, 2, 3])
Using the type BusSignal Such a structure can be accurately described and typed at the model level, which is important for formalizing the hardware data interface.
Type BusSignal it is written as follows:
BusSignal{Names, Types, Dimensions, :BusName}
where:
-
Names— a tuple of signal names ((:a, :b, :c)); -
Types— relevant types (Tuple{Int, Float64, Vector{Float64}}); -
Dimensions— the dimensions of each element), (), (3,for scalars and a vector of length 3); -
BusName— the name of the bus (set by the user).
Type BusSignal it is parameterized by the names of the signals (Names), basic types (BaseTypes), dimensions (Dims), and the name of the bus (BusName). For example, a bus with three signals can be described as:
bus_type = BusSignal{(:s1, :s2, :s3), Tuple{Int, Float64, Float64}, ((), (), (3,)), :MyBus}
In BaseTypes only basic types are specified (for example, Int, Float64), not containers (for example, Vector{Int}).
The dimensions of arrays are set separately in Dims.
|
Here:
-
:s1,:s2,:s3— signal names; -
Tuple{Int, Float64, Float64}— types of signal data; -
(), (), (3,)— the dimensions of the signals (for example,s3— array of length3). -
MyBus— the name of the tire.Any characters other than quotation marks are allowed in the name of the signal and bus ( ").
An example of simple usage:
# Setting the value of the bus type (a structure without data, so parentheses)
bus_type = BusSignal((:a, :b), (Int, Float64), ((), ()), :MyBus)
# Creating objects of this bus, the NamedTuple type parameters have curly brackets.
bus1 = BusSignal{(:a, :b), Tuple{Int, Float64}, ((), ()), :MyBus}((a = 5, b = 6.4))
# Using key arguments
bus2 = BusSignal{(:a, :b), Tuple{Int, Float64}, ((), ()), :MyBus}(a = 5, b = 6.4)
# Using positional arguments (varargs)
bus3 = BusSignal{(:a, :b), Tuple{Int, Float64}, ((), ()), :MyBus}(5, 6.4)
Tires can be nested, for example:
Inner = BusSignal{(:x, :y), Tuple{Int, Int}, ((), ()), :InnerBus}
Outer = BusSignal{(:a, :b), Tuple{Float64, Inner}, ((), ()), :OuterBus}
- The following functions are available for type analysis
get_names_types_dims(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName} # all parameters at once
get_bus_names(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName} # tuple of signal names
get_bus_types(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName} # a tuple of basic types
get_bus_dimensions(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName} # tuple of dimensions
get_bus_name(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName} # bus name
get_bus_signal_type(x::NamedTuple)
Function get_bus_signal_type it is especially convenient: it allows you to automatically determine the type of tire by its value, for example:
bus = (s1 = 5, s2 = [4.3, 5.4], s3 = (a = 4, b = 5.5))
bus_type = get_bus_signal_type(bus)
# bus_type is equal to: BusSignal{(:s1, :s2, :s3), Tuple{Int64, Float64, BusSignal{(:a, :b), Tuple{Int64, Float64}, ((), ())}}, ((), (2,), ())}
This functionality is actively used in hardware units. In order not to set the types of tires manually, such blocks are configured masks is an interface in which it is enough to specify only the names and the number of signals.
Next, let’s look at how this scheme works: from mask parameters to automatic generation. BusSignal inside the equipment block.
Custom buses in hardware units
Let’s take an example — the dynamic formation of tire types. BusSignal using the example of the UM Cosimulation hardware unit. This block has the following parameters:

In order to automatically generate bus types, it is enough to specify only the number and names of the input and output signals through the mask, as well as the names of the tires themselves.
To see how it works, open the block mask (PCM by block/Mask/To view the mask), go to the "Code Editor" → Global tab and in the callback blockChangedCallback find the following code:
engee.set_param!(engee.gcb(), "InputPort1BusType" => "BusSignal($(mask.parameters.m_input_signal_names.value), Tuple(fill(Float64, $(mask.parameters.m_num_input_signals.value))), ntuple(_ -> (), $(mask.parameters.m_num_input_signals.value)), $(mask.parameters.m_input_bus_name.value))")
engee.set_param!(engee.gcb(), "OutputPort1BusType" => "BusSignal($(mask.parameters.m_output_signal_names.value), Tuple(fill(Float64, $(mask.parameters.m_num_output_signals.value))), ntuple(_ -> (), $(mask.parameters.m_num_output_signals.value)), $(mask.parameters.m_output_bus_name.value))")
In this code:
-
m_num_input_signals— number of input signals; -
m_num_output_signals— number of output signals; -
m_input_signal_names— the names of the input signals (for example,(:a,:b,:c)); -
m_output_signal_names— names of the output signals; -
m_input_bus_name— the name of the input bus (Symbol, for example,:InputBus); -
m_output_bus_name— the name of the output bus (Symbol, for example,:OutputBus).
Values m_input_bus_name and m_output_bus_name They must come in the form of correct Julia literals. Symbol that is , with a colon: :InputBus, :MyHwIn, etc .
|
The code sets the path to the current block using the function gcb(), after which the values of the mask parameters are read m_input_signal_names, m_num_input_signals, m_output_signal_names, m_num_output_signals, and also m_input_bus_name and m_output_bus_name. These values are used to form a string representation of the type BusSignal with specified signal names, basic types (Float64), dimensions () and the name of the bus (the fourth argument of the type Symbol). In order for the values to be read, the parameter names must be declared in the Engee Function block. To do this, go to the hardware unit settings and click Look Under Mask to open the parameters of the Engee Function block. The Parameters tab contains the program names of the parameters from the mask.:

Then the function engee.set_param! automatically updates the Output bus type parameters for Input port 1 (program name InputPort1BusType) and Output bus type (program name OutputPort1BusType) for Output port 1 on the Ports tab, substituting the generated bus types for the input and output ports:

Thus, the whole setup boils down to filling in the mask fields. The bus types are generated automatically, without manually editing the Ports tab. This makes the unit user-friendly, adaptive, and ready for reuse.