I just finished having strong typed ApiRoutes and AppRoutes as well as some basic axios config, but it’s not ready for pushing yet – we need to finalise some UI and backend decisions before I start adding the UI layer (changing tests will slow the thing down if it changes a lot, so I’ll post some sketches later).
Also, as I said in the PR linked above, I need some recommendations for flask: How is it going to be architected based on pyuavcan? I’ve not used pyuavcan and the async version is under development so I don’t know for sure. Some routes are created, but swagger documentation, strongly typed route names and minor configuration is soon to be added.
Most importantly, regarding the UI:
Are we OK by using bootstrap 4? I mean, it’s ‘The default face of the internet’ and free, provided by twitter, straight forward and in general very easy to use. Also, some other libraries that could be of interest could be chartjs and perhaps d3js. If you have a different recommendation on the UI toolkit let us know below of course.
Home page + Node details:
On the previous devcall we agreed that we should start with presenting a list of nodes on the homepage.
The list should contain Node Name, Node Id, Node Status (Enum), Uptime (seconds?)
Is this supposed to be an accordeon-list with red green blue yellow / whatever colour depending on node state? Uptime in seconds? Is it going to refresh automatically?
Also, taking in mind these requirements for the initial index page, the json response should only be composed of an array of these 4 members on each object inside.
Proposed actions:
on click it should expand to further node details?
a click on the Id should copy the id contents on the clipboard for example
pressing tab should traverse each list element, pressing enter should open node details
Do we add some clientside filters / sorting? Ex. Sort by node status first, by uptime, also filter based on regex / matching on node names.
(Imagine some rectangle that has text or a status dot on each corner, as a list entry)
This is not to say that we can’t use Flask. We can, but we’d have to add a synchronous facade on top of pyuavcan, which is doable but it’s best to avoid doing that unless we can’t help it.
And the vendor-specific status code, too. Basically, we should re-implement what we have in the current GUI tool.
My calendar says it’s 2019. What doesn’t refresh automatically these days, really?
I’d encourage you to look at bokeh (BSD 3-clause). It’s already architected as a javascript/python graphing solution so there may be some efficiencies there.
I’ll write some tests and push as soon as the frontend structure PR gets merged.
Edit: @pavel.kirienko I think we need to also show NodeStatus::MODE_* and NodeStatus::HEALTH_* messages as well and add some sorting to that as well. What do you think?
Of course, mode and health are paramount. They are not messages though, they are fields within the uavcan.node.Heartbeat message (used to be called uavcan.protocol.NodeStatus).
This looks quite solid. There are some minor things to bikeshed, such as unnecessary capitalization (Sort On, With Order), unnecessary word “List” in “Online Nodes List”, and British spelling (INITIALISATION?), but they are probably not important enough to focus on right now.
Yukon related dev-call contents:
Start working on:
pyuavcan node reconfiguration options (target url, debug node id on the bus)
node details by id tab
documentation for these
*Postpone actual development of the python backend till Pavel finishes pyuavcan work, but keep stubbing out the backend api in modules, providing at least some mock responses, so that we are ready to rapidly integrate pyuavcan when the time comes.
It seems to me that it needs to have these 3 main sub-components:
Node/Constant information: Stuff like the crc64, hardware version, etc.
Controls panel: shutdown, start firmware update
Parameter list
The parameter list, upon clicking, pops up a modal which the user reconfigures the parameter on the fly, based on it’s type.
Regarding the shutdown and start firmware update controls, what should happen? One thing would be to have a confirmation dialogue for accidental presses, and then …? Do you poll for the node status on the clientside? How much time is a node firmwar eupdate supposed to take? Do I need to add some extra dialogue to pick a file (or whatever) before the update starts?
PS. I made a new component for the auto-copyable text – so most of the things will copy their contents to clipboard on click (ids, crc, uid, names, … )
I am thinking that putting the parameter list into the node detail view, as I made in the original gooey tool, was a mistake. We also need to keep in mind that we don’t really have “parameters” per se in UAVCAN v1.0; instead we have “registers”, which are slightly different. The details are doc’ed here: https://github.com/UAVCAN/dsdl/blob/uavcan-v1.0/uavcan/register/384.Access.0.1.uavcan
I described another approach which should work much better here: GUI Tool – Next Generation - #2 by pavel.kirienko (look for “Multi-node concurrent configuration tool”). Basically, it should be like a large table; the horizontal dimension represents nodes (i.e., one column per node), and the vertical dimension represents parameters. Ideally, the parameters should be grouped into a tree-like structure, like this:
Except that, on the screenshot, we’re dealing with only one device; in order to free up the horizontal dimension for other nodes, various register metadata will have to be collapsed into one rich cell: the current value, minimum/maximum/default, data type, edit/reset buttons, flags (editable/persistent), etc. Notice how the tree branches can be expanded and collapsed, this is very convenient. The exact UX/presentation is a matter of discussion, suggestions are highly welcome.
There also should be buttons for other commands, and an option for invoking vendor-specific commands; i.e., with arbitrary user-specified code.
The frontend should not care about the time it takes to update a firmware (for the sake of a general reference, a regular node with ~400 KB large firmware takes about 2 minutes to update itself over CAN 2.0B). The only thing we need the frontend to do is to ask the user for the location of the file, then make this file available to the backend (e.g. upload), and then make the backend call the service. When this is done, the frontend can safely forget the context. See chapter 5 for more info about this process. Nodes may emit progress information while update is ongoing by publishing messages of type uavcan.diagnostic.Record.
I would say, anything more than 4 levels would be impractical.
The use case when a user needs to bulk configure multiple nodes or compare their settings is very common in UAVCAN. I am not sure how it can be adequately supported with the old paradigm implemented in the UAVCAN GUI tool where the settings of each node were displayed in a separate window. The old GUI tool also permits the user to have multiple windows open, but that doesn’t really help; I think the old design was a mistake.
The exact user story of the new multi-node configuration tool still needs to be worked out. Perhaps the user should be allowed to pick which nodes among the available they want to configure, then the GUI will discover their registers and display them on the table?
Inline might work well for simple scalar types like booleans, integers, and floats. Strings and arrays will likely require a popup. Or we could use popup everywhere for consistency and simplicity reasons.
The screenshot which I posted above is of Zubax Kucher: https://github.com/Zubax/kucher. It supports array parameter editing by rendering them as text when the editor is invoked, and parsing the edited text array back into its internal representation when the user finishes their edits. This approach is probably suboptimal though.
We have to invoke one service request per parameter, the protocol does not support batch editing, so it makes sense to reflect this underlying model in the UI.
I think CSV should certainly be supported because it’s so ubiquitous. JSON would also be cool to have because it is well-supported by different software products, e.g., Wolfram Mathematica. The JSON schema should probably resemble the definition of the uavcan.register.Access data type.
So, the batch configure is supposed to happen on nodes of the same ‘type’? So, we are going to have let’s say 4 ESCs and display their setting ‘X’ on a single or 4 different rows, on the concurrent configuration tool?
The tree depth does not really matter that much, but I’m just asking for design considerations (inline margin-left’s would be dynamic based on depth, after all).
Shall I change the endpoints of the backend from /nodes/:nodeId/parameters/… to …/registers/… ? Also, on the register definition posted above, I see no min/max or default setting, just value. Are these obtained separately? (I have to update the current spec to match the extra timestamp, persistent/etc states.) Reading the values back on each request really helps for automatic error checking. Same thing for register type. Parameters had some real/integer/boolean type. Where are the register ones?
They don’t necessarily have to be of the same type; for example, many different nodes may have a register named can.bit_rate, for example. We are probably going to encourage node designers to follow particular register naming pattern to ensure good coherence between different vendors.
Yes. I should have been more explicit about that probably from the start. But then again, it’s explained in the current spec draft.
See the docs for uavcan.register.Access:
For example, suppose we have a register named uavcan.node_id. Its min, max, and default values will be contained inside three other registers named uavcan.node_id>, uavcan.node_id<, and uavcan.node_id=, respectively. These metadata registers do not need to be displayed for the user, since they can’t be changed anyway; they are to be used by the tool to figure out the relevant parameters of the target register. The Kucher application which I linked above deals with these metadata entries as follows (open the link please, the excerpt displayed on the forum is incomplete):
I’m sorry, I must have ended up reading some wrong version of the spec of the draft in the process.
Here’s what I’m thinking about, regarding the Register list and editing UI:
UI Ideas:
Mutable registers only, show as clickable (blue anchor tag).
Persistency / Volatility show up in a different column named ‘Lifetime’ (or recommend something better). - -
Clicking them pops up the register update UI, which performs the request and gets the returned value back. If the returned value matches the expected one, the register update process is completed sucessfully.
Regarding the concurrent node configure, maybe we can have a checkbox that switches the view of available registers: based on selected nodes and/or selected register names. For example, you can type in some regex to match the nodes, and/or some regex (or text) for the parameter you want to change. These filters run independently but result in a set of nodes and parameters that are shown: Of course, as an error prevention feature, only the nodes that have the parameter listed are being contacted through the API. We can have some extra check on the popup register edit window that selects nodes and upon the press of the ‘Update Register(s)’ button, shows which nodes failed to accept the changes (did not report back new value).
API json responses format questions:
What’s the format to expose each individual’s register details? For example, do we send a list out of all the register names existing? (That means, each client needs to parse each individual one by name, decide which have min/max/default values based on $name[ <|>|= ]) Instead, we can do that on the backend and send the response in the current-ish format, which contains all the information for one register-details object, including the min/max/default values. The second one is my preferred way, which is more user-friendly on the client consuming the API. This does not mean that you are prohibited from querying each parameter by name: If you specify a name that ends with ><=, other parameters of that register (ex. current value, or the other min/max/default ones) are not queried.
How are the Primitives going to be exposed as a register’s data type? Instead of providing an enum of possible data types, we could have a field showing it’s object type [scalar, array, empty, string, unstructured] and if that object type is scalar or array, we can have an extra item type [bit, int natural, real] as well as a bit length type (not for bits) [2~64].
If we’re going to aggregate registers by name, the columns will be used for the node dimension, so there won’t be any free columns left for mutability/volatility (and possibly other flags which we may add in the future). I would instead recommend to display flags in the same cell as the parameter value (together with other parameter metadata). Different parameters sharing the same name may be mutable on one node and immutable on other nodes.
From the above indirectly follows that we probably shouldn’t hide any registers unless the user explicitly requested that. For example, the set of filters that you described later might also include options for mutability and persistency.
Yes, this makes sense. It might also be useful to allow the user to multi-select several register cells on the same row and then click some button to bulk-edit them, but it can be done incrementally. Please keep in mind that in theory, some nodes may not support the standard service uavcan.node.GetInfo, which means that we won’t be able to know their names, and the only way to select them would be to specify their node ID manually. Although our experience with UAVCAN v0 seems to indicate that implementers tend to always support this service.
For example, a user may want to see registers that match the following filters:
the node name matches some_vendor.* (suppose this is a wildcard); this automatically removes all nodes whose name we don’t know, e.g. which do not support uavcan.node.GetInfo.
the register name matches uavcan.*.
show only persistent mutable registers (i.e., only configuration parameters).
Take my response with a grain of salt, but I would actually prefer the first way, because in that case you can implement the whole register thing without introducing any special logic on the backend side. You will merely need a generic service invocation API: the frontend tells the backend to invoke a service such-and-such on the node so-and-so; the backend does that and simply returns the response structure as-is. It’s up to you, but the first case seems simpler, provided that you are okay with implementing the register type checking and input validation on the frontend.
Maybe it’s better to find a generic way of representing DSDL objects in JSON, and then use that model everywhere, including the register management logic? I recall @scottdixon said that he made something to that end.