Skip to content

halcmd: add -p option to generate PlantUML diagram of HAL pins/signals#4214

Open
petterreinholdtsen wants to merge 1 commit into
LinuxCNC:2.9from
petterreinholdtsen:halcmd-plantuml-output
Open

halcmd: add -p option to generate PlantUML diagram of HAL pins/signals#4214
petterreinholdtsen wants to merge 1 commit into
LinuxCNC:2.9from
petterreinholdtsen:halcmd-plantuml-output

Conversation

@petterreinholdtsen

Copy link
Copy Markdown
Collaborator

Emit bracket-style component boxes grouped by instance, with the component type name (loadrt/loadusr module name) in parentheses. Signals are rendered as queue entities so one writer can fan out to multiple readers via a single node.

Components with all unconnected pins are filtered out.

Also documents the new -p option in the halcmd man page.

This patch was created with help from OpenCode using local llama.cpp server with Qwen 3.6.

Emit bracket-style component boxes grouped by instance, with the
component type name (loadrt/loadusr module name) in parentheses.
Signals are rendered as queue entities so one writer can fan out
to multiple readers via a single node.

Components with all unconnected pins are filtered out.

Also documents the new -p option in the halcmd man page.

This patch was created with help from OpenCode using local llama.cpp
server with Qwen 3.6.
@BsAtHome

BsAtHome commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Why can't you just use script-mode for output (halcmd -s) and post-process with your own script?
I cannot see a reason why you need to have plantuml specific output to be part of halcmd. If script-mode shows a deficiency for post-processing , then we can simply fix that (in a compatible way).

@petterreinholdtsen

Copy link
Copy Markdown
Collaborator Author

Why can't you just use script-mode for output (halcmd -s) and post-process with your own script?

Who say I can not? I just believe it is more convenient and available for a large audience if the program do this directly and on its own, instead of having to track down a separate tool.

This patch is actually a byproduct of an experiment adding a graph tab to halshow. Have not yet found a way to make that graph pretty and useful, so in the mean time I have been using plantuml to get an overview of the HAL setup and changes on machines. :)

@andypugh

Copy link
Copy Markdown
Collaborator

Do you have a sample of the plantuml output as SVG?

@BsAtHome

Copy link
Copy Markdown
Contributor

But still, the correct way then is to make the script that is part of LinuxCNC for that "large audience". You still need plantuml for it to function. So, you make a script that calls halcmd -s processes the output and wraps it all up. No need to add another mode to halcmd. Just make a script that does it all for that large audience.

The problem I'm addressing here is that you can add N+1 output interfaces to halcmd and still not be able to capture them all for any audience's liking. However, it makes halcmd unnecessarily complex and difficult to maintain. Therefore, using one standard script-friendly output format, which we have, should suffice and everything else is external or encapsulated post-processing.

@petterreinholdtsen

petterreinholdtsen commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Do you have a sample of the plantuml output as SVG?

Sure. Attached.
simulation

@petterreinholdtsen

Copy link
Copy Markdown
Collaborator Author

The problem I'm addressing here is that you can add N+1 output interfaces to halcmd and still not be able to capture them all for any audience's liking.

While it of course is true and sound very dramatic, I suspect that in reality, there are two major formats that are relevant, the graphviz dot format and plantuml. So N=0 will make a lot of people happy and N=1 will provide a useful format for the wast majority. I would be happy to add a -g option for graphviz dot, if you believe it is vital to increase the audience approval.

@BsAtHome

Copy link
Copy Markdown
Contributor

What about PGF/TikZ? Or R? Or Vym?. Then there are numerous other packages. Just looking at the very long list of available UML tools should give you an indication how many different ways there are to tell a similar story. There is the whole category of argument/concept mapping software and associated languages. You may not be able to surface once you down that rabbit hole and forever stay in the Red Queen's prison with your head chopped off.

FWIW, we already have N=1you know the saying, use N=0, N=1 or N=∞. With N=1 we have the script-friendly output. As said, if it is insufficient or incomplete, then it should be fixed to be generic. But I'd strongly resist adding any specific target language.

@petterreinholdtsen

petterreinholdtsen commented Jun 30, 2026 via email

Copy link
Copy Markdown
Collaborator Author

@andypugh

andypugh commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

The current script-friendly output isn't something that any apt-installable package can parse into a diagram.
This UML output can go straight into existing tools (as could .dot format)
There are a number of third-party tools, though some require a running HAL session to query in order to work, whereas some work from the HAL file itself.

Ideally we would have a version of Halshow where you could drag the blocks around to untangle lines, and the values were displayed live on the nets. But at that point we would be re-inventing Simulink or LabView.

It's worth remembering, also, that most HAL files are fit-and-forget. after initial configuration they are not looked at again for years at a time. For most users there are more valuable places to invest developer effort.

This PR was prompted by trying to get to the bottom of the spindle control on Petter's Mazak, which after a few years was a mystery to both of us. Using this script (converted to Python3) was very helpful when we were trying to work out how the spindle was controlled

@hdiethelm

hdiethelm commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

I agree that halcmd is the wrong place to implement a specific graph format. At the end, someone wants to implement a VisualStudio drawing generator in there... ;-)

However, having ways to generate a graph from the connections is for sure nice and looking at the above repo, parsing the output of halcmd -s looks cumbersome.

Options:

  • Use the available https://linuxcnc.org/docs/devel/html/en/config/python-hal-interface.html to gather the data. It looks like a function to show the output to signal connection is missing. Signal to input can be shown using hal.get_info_signals(). If you add generic functions here that can be used to generate a nice diagram in what ever form you want in a python script will probably be approved. I could help.
  • Add json output to halcmd which can easily parse by most programming languages. I would say that's not needed, the python hal library is the better way.

Adding a few python graph generators into the repo in an appropriate location would probably also be fine.

Together with python hal -> graph generator -> render tool, this can be even integrated into an UI.

The drag mode is also realizable by improving python hal so all info can be gathered and then a python tool that does this. As much as I know, there are nice library's to do that.

@BsAtHome

BsAtHome commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

In principle, you need a "dump" of all modules/threads/functions/components/signal/pins/parameters with all the retained information in a structured format. Although halmodule may be interesting, it is not necessarily the right place. However, there may be a case for it (see below).
The current constructs in these tools require you to instantiate a (temporary) component, just to get access to the shared memory. This is something I am working on so it no longer is a requirement to create temporary components. It simplifies things significantly. Many functions, like getting/setting pins/params/signals do not need a component at all to work.
I already have a complete new query API in (user-space) hal_lib in my tree that lets you look at everything without the need for people poking around in HAL's internal structures (as part of complete removal of access to hal_priv.h). A wrapper could be implemented in halcmd to dump all content in a structured way. It can also be implemented as a query interface in halmodule when it no longer requires a (temporary) component to function. Then you can write a new program "haldump.py" if you like.

@hdiethelm

hdiethelm commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Hmm, is this an issue if you have to create a temporary halmodule? Many components do this like halshow / halcmd and so on.

Of course, having better separation would help and there is really no need for a component just to access shm.

A small python script that shows what is already available in hal python. For the get_info_pins() a DRIVER field is missing and there is also no get_info_threads(). But this could be done and then all should be there needed to create a graph in python.

import hal
import os

comp_name = f"halpy{os.getpid()}"
if not hal.is_initialized():
    comp = hal.component(comp_name)


print("pins-----------")
for pin in hal.get_info_pins():
    for k, v in pin.items():
        print(k, v)
    print()

print("signals-----------")
for sig in hal.get_info_signals():
    for k, v in sig.items():
        print(k, v)
    print()

print("params-----------")
for par in hal.get_info_params():
    for k, v in par.items():
        print(k, v)
    print()

@hdiethelm

hdiethelm commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

A small change in halmodule.cc and I think everything needed to generate a diagram from python is there. If hal.get_info_components() / hal.get_info_threads() is needed, this should also be possible. It would just expand an already existing pattern.

@BsAtHome Do you think this could be a way? Yes, it needs a component but for now, all modules accessing the HAL need one. If your changes gets in, the component creation could be dropped, done.

diff --git a/src/hal/halmodule.cc b/src/hal/halmodule.cc
index b0a132fd0c..8be61a160d 100644
--- a/src/hal/halmodule.cc
+++ b/src/hal/halmodule.cc
@@ -1554,10 +1554,12 @@ PyObject *get_info_pins(PyObject * /*self*/, PyObject * /*args*/) {
     static const char str_v[] = "VALUE";
     static const char str_t[] = "TYPE";
     static const char str_d[] = "DIRECTION";
+    static const char str_s[] = "SIGNAL";
     hal_data_u *d_ptr;
 
     hal_pin_t *pin;
     hal_sig_t *sig;
+    char* sig_name;
 
     PyObject* python_list = PyList_New(0);
     PyObject *obj;
@@ -1573,61 +1575,69 @@ PyObject *get_info_pins(PyObject * /*self*/, PyObject * /*args*/) {
         if (pin->signal != 0) {
             sig = (hal_sig_t*)SHMPTR(pin->signal);
             d_ptr = reinterpret_cast<hal_data_u *>(SHMPTR(sig->data_ptr));
+            sig_name = sig->name;
         } else {
             sig = NULL;
             d_ptr = &(pin->dummysig);
+            sig_name = (char*)"UNCONNECTED";
         }
-
         /* convert to dict of python values */
         switch(type) {
             case HAL_BIT:
-                obj = Py_BuildValue("{s:s,s:N,s:N,s:N}",
+                obj = Py_BuildValue("{s:s,s:N,s:N,s:N,s:s}",
                         str_n, pin->name,
                         str_v, PyBool_FromLong((long)d_ptr->b),
                         str_d, PyLong_FromLong(pin->dir),
-                        str_t, PyLong_FromLong(HAL_BIT));
+                        str_t, PyLong_FromLong(HAL_BIT),
+                        str_s, sig_name);
                 break;
             case HAL_U32:
-                obj = Py_BuildValue("{s:s,s:k,s:N,s:N}",
+                obj = Py_BuildValue("{s:s,s:k,s:N,s:N,s:s}",
                         str_n, pin->name,
                         str_v, (unsigned long)d_ptr->u,
                         str_d, PyLong_FromLong(pin->dir),
-                        str_t, PyLong_FromLong(HAL_U32));
+                        str_t, PyLong_FromLong(HAL_U32),
+                        str_s, sig_name);
                 break;
             case HAL_S32:
-                obj = Py_BuildValue("{s:s,s:l,s:N,s:N}",
+                obj = Py_BuildValue("{s:s,s:l,s:N,s:N,s:s}",
                         str_n, pin->name,
                         str_v, (long)d_ptr->s,
                         str_d, PyLong_FromLong(pin->dir),
-                        str_t, PyLong_FromLong(HAL_S32));
+                        str_t, PyLong_FromLong(HAL_S32),
+                        str_s, sig_name);
                 break;
             case HAL_U64:
-                obj = Py_BuildValue("{s:s,s:K,s:N,s:N}",
+                obj = Py_BuildValue("{s:s,s:K,s:N,s:N,s:s}",
                         str_n, pin->name,
                         str_v, (unsigned long long)d_ptr->lu,
                         str_d, PyLong_FromLong(pin->dir),
-                        str_t, PyLong_FromLong(HAL_S64));
+                        str_t, PyLong_FromLong(HAL_S64),
+                        str_s, sig_name);
                 break;
             case HAL_S64:
-                obj = Py_BuildValue("{s:s,s:L,s:N,s:N}",
+                obj = Py_BuildValue("{s:s,s:L,s:N,s:N,s:s}",
                         str_n, pin->name,
                         str_v, (long long)d_ptr->ls,
                         str_d, PyLong_FromLong(pin->dir),
-                        str_t, PyLong_FromLong(HAL_S64));
+                        str_t, PyLong_FromLong(HAL_S64),
+                        str_s, sig_name);
                 break;
             case HAL_FLOAT:
-                obj = Py_BuildValue("{s:s,s:d,s:N,s:N}",
+                obj = Py_BuildValue("{s:s,s:d,s:N,s:N,s:s}",
                         str_n, pin->name,
                         str_v, (double)d_ptr->f,
                         str_d, PyLong_FromLong(pin->dir),
-                        str_t, PyLong_FromLong(HAL_FLOAT));
+                        str_t, PyLong_FromLong(HAL_FLOAT),
+                        str_s, sig_name);
                 break;
             case HAL_PORT:
-                obj = Py_BuildValue("{s:s,s:l,s:N,s:N}",
+                obj = Py_BuildValue("{s:s,s:l,s:N,s:N,s:s}",
                         str_n, pin->name,
                         str_v, (long)d_ptr->p,
                         str_d, PyLong_FromLong(pin->dir),
-                        str_t, PyLong_FromLong(HAL_PORT));
+                        str_t, PyLong_FromLong(HAL_PORT),
+                        str_s, sig_name);
                 break;
             case HAL_TYPE_UNSPECIFIED: /* fallthrough */ ;
             case HAL_TYPE_UNINITIALIZED: /* fallthrough */ ;
@@ -1636,7 +1646,8 @@ PyObject *get_info_pins(PyObject * /*self*/, PyObject * /*args*/) {
                         str_n, pin->name,
                         str_v, NULL,
                         str_d, PyLong_FromLong(pin->dir),
-                        str_t, NULL);
+                        str_t, NULL,
+                        str_s, sig_name);
                  break;
         }

Python script:

import hal
import os

comp_name = f"halpy{os.getpid()}"
if not hal.is_initialized():
    comp = hal.component(comp_name)


print("pins-----------")
for pin in hal.get_info_pins():
    for k, v in pin.items():
        print(k, v, end='; ')
    print()

print("signals-----------")
for sig in hal.get_info_signals():
    for k, v in sig.items():
        print(k, v, end='; ')
    print()

print("paramteters-----------")
for par in hal.get_info_params():
    for k, v in par.items():
        print(k, v, end='; ')
    print()

Output:

pins-----------
NAME fast.time; VALUE 40; DIRECTION 32; TYPE 3; SIGNAL UNCONNECTED; 
NAME lat.bj; VALUE 1731629; DIRECTION 16; TYPE 3; SIGNAL bj; 
NAME lat.bl; VALUE 1756629; DIRECTION 16; TYPE 3; SIGNAL bl; 
NAME lat.bt; VALUE 90; DIRECTION 16; TYPE 3; SIGNAL bt; 
NAME lat.reset; VALUE False; DIRECTION 32; TYPE 1; SIGNAL reset; 
NAME lat.sj; VALUE 1741692; DIRECTION 16; TYPE 3; SIGNAL sj; 
NAME lat.sl; VALUE 2741692; DIRECTION 16; TYPE 3; SIGNAL sl; 
NAME lat.st; VALUE 969250; DIRECTION 16; TYPE 3; SIGNAL st; 
NAME slow.time; VALUE 150; DIRECTION 32; TYPE 3; SIGNAL UNCONNECTED; 
NAME timedelta.0.avg-err; VALUE -0.002530682045127165; DIRECTION 32; TYPE 2; SIGNAL UNCONNECTED; 
NAME timedelta.0.current-error; VALUE -24910; DIRECTION 32; TYPE 3; SIGNAL UNCONNECTED; 
NAME timedelta.0.current-jitter; VALUE 24910; DIRECTION 32; TYPE 3; SIGNAL UNCONNECTED; 
NAME timedelta.0.err; VALUE -52410; DIRECTION 32; TYPE 3; SIGNAL UNCONNECTED; 
NAME timedelta.0.jitter; VALUE 1731629; DIRECTION 32; TYPE 3; SIGNAL bj; 
NAME timedelta.0.max; VALUE 1756629; DIRECTION 32; TYPE 3; SIGNAL bl; 
NAME timedelta.0.min; VALUE 79; DIRECTION 32; TYPE 3; SIGNAL UNCONNECTED; 
NAME timedelta.0.out; VALUE 90; DIRECTION 32; TYPE 3; SIGNAL bt; 
NAME timedelta.0.reset; VALUE False; DIRECTION 16; TYPE 1; SIGNAL reset; 
NAME timedelta.0.time; VALUE 40; DIRECTION 32; TYPE 3; SIGNAL UNCONNECTED; 
NAME timedelta.1.avg-err; VALUE -0.0012931562094168484; DIRECTION 32; TYPE 2; SIGNAL UNCONNECTED; 
NAME timedelta.1.current-error; VALUE -30750; DIRECTION 32; TYPE 3; SIGNAL UNCONNECTED; 
NAME timedelta.1.current-jitter; VALUE 30750; DIRECTION 32; TYPE 3; SIGNAL UNCONNECTED; 
NAME timedelta.1.err; VALUE -26781; DIRECTION 32; TYPE 3; SIGNAL UNCONNECTED; 
NAME timedelta.1.jitter; VALUE 1741692; DIRECTION 32; TYPE 3; SIGNAL sj; 
NAME timedelta.1.max; VALUE 2741692; DIRECTION 32; TYPE 3; SIGNAL sl; 
NAME timedelta.1.min; VALUE 321; DIRECTION 32; TYPE 3; SIGNAL UNCONNECTED; 
NAME timedelta.1.out; VALUE 969250; DIRECTION 32; TYPE 3; SIGNAL st; 
NAME timedelta.1.reset; VALUE False; DIRECTION 16; TYPE 1; SIGNAL reset; 
NAME timedelta.1.time; VALUE 150; DIRECTION 32; TYPE 3; SIGNAL UNCONNECTED; 
signals-----------
NAME bj; VALUE 1731629; DRIVER timedelta.0.jitter; TYPE 3; 
NAME bl; VALUE 1756629; DRIVER timedelta.0.max; TYPE 3; 
NAME bt; VALUE 90; DRIVER timedelta.0.out; TYPE 3; 
NAME reset; VALUE False; DRIVER lat.reset; TYPE 1; 
NAME sj; VALUE 1741692; DRIVER timedelta.1.jitter; TYPE 3; 
NAME sl; VALUE 2741692; DRIVER timedelta.1.max; TYPE 3; 
NAME st; VALUE 969250; DRIVER timedelta.1.out; TYPE 3; 
paramteters-----------
NAME fast.tmax; DIRECTION 192; VALUE 46206; TYPE 3; 
NAME slow.tmax; DIRECTION 192; VALUE 8265; TYPE 3; 
NAME timedelta.0.tmax; DIRECTION 192; VALUE 46206; TYPE 3; 
NAME timedelta.0.tmax-increased; DIRECTION 64; VALUE False; TYPE 1; 
NAME timedelta.1.tmax; DIRECTION 192; VALUE 8265; TYPE 3; 
NAME timedelta.1.tmax-increased; DIRECTION 64; VALUE False; TYPE 1;

@multigcs

multigcs commented Jul 2, 2026

Copy link
Copy Markdown

here my one-script solution:

https://www.youtube.com/watch?v=isgavyVCYJc
https://github.com/multigcs/halviewer

it using graphviz for the initial layout and QGraphicsScene for display and editing

@multigcs

multigcs commented Jul 2, 2026

Copy link
Copy Markdown
PlantUML natively supports Graphviz (DOT) code. You do not need to rewrite or convert your DOT files. Simply change @startdot to @startuml and place your DOT code directly inside your PlantUML block.

So if you're going to use an export format, Graphviz / dot is the better choice

@BsAtHome

BsAtHome commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Halmodule does not provide any relation to the owner component of the pins and parameters. You also need to implement component listing. And while you are at it, you want all the other info too. That is simply not implemented in halmodule and is a bit off-topic for halmodule. It would then be better to create a halquery module specifically to get information on the HAL shared memory content and structure.

And, about the format, there is no point in having a "well, most use XYZ" format implemented. You need a generic interface so that the question of "who can interface" is answered with "anyone, use your own parser/converter to mangle the generic output".

FWIW, I already created a halquery module in my tree. It can query individual parts or list entire categories. A run on a default axis sim gives axis-idle.txt with this script:

#!/usr/bin/env python3
import halquery
print("# Pins")
for k, v in halquery.pins().items():
    print(k, v)
print("# Params")
for k, v in halquery.params().items():
    print(k, v)
print("# Signals")
for k, v in halquery.signals().items():
    print(k, v)
print("# Components")
for k, v in halquery.comps().items():
    print(k, v)
print("# Functions")
for k, v in halquery.functs().items():
    print(k, v)
print("# Threads")
for k, v in halquery.threads().items():
    fc = v['functions']
    v['functions'] = '...'
    print(k, v)
    for f in fc:
        print("   ", f)

It reveals the complete internal structure in python dictionaries which you can use in any way you want and mangle into whatever you like.

@hdiethelm

Copy link
Copy Markdown
Contributor
PlantUML natively supports Graphviz (DOT) code. You do not need to rewrite or convert your DOT files. Simply change @startdot to @startuml and place your DOT code directly inside your PlantUML block.

So if you're going to use an export format, Graphviz / dot is the better choice

That's why I think we should not export a specific format at all as @BsAtHome pointed out to. This is a discussion that can never be ended... ;-)

The better way is good support for everyone that wants to write a graph exporter or a real time graph. Based on all scripts I have seen so far, having this as a python module probably would do it.

That would involve the following steps:

  1. Create a new module or improve the hal module to allow for easy creations of such viewers.
  2. Create a common entry point to start, something like halviewer which is installed in the path from where all nice tools can be started.
  3. Integrate a few very useful tools in there.

This would result in the end:
halviewer --export-dot -> uses lib/python/halviewer/export-dot.py
halviewer --export-plantuml -> uses lib/python/halviewer/export-plantuml.py
halviewer --live-view -> uses lib/python/halviewer/live-view.py (Based on multigcs code for example)

This way, all can be integrated without one integration breaking an other one and without having a single line of change in halcmd.

Reasoning behind:

  • If halcmd breaks, linuxcnc is toast.
  • If a single viewer breaks, who cares, it can be fixed or also just dropped it it is unfixable or the original contributor is not available. A few people won't be happy but they are free to fix it and create a PR.
  • Dependency's of a viewer can be suggestions, so you can install them if you want it or also not.

That said, this is just my opinion and a short concept. Other opinions are welcome.

@multigcs I really like you viewer. The only thing I don't really like is that you have to use halcmd and decode the text to get the graph. Not your fault, I think there is just now other way to do this but we should change that.

@hdiethelm

hdiethelm commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

It reveals the complete internal structure in python dictionaries which you can use in any way you want and mangle into whatever you like.

Looks nice! I was not aware you are already working on it.

One thing: Having type / dir as the C enum value doesn't look python style to me: https://docs.python.org/3/library/enum.html @grandixximo seams you know python better than me, any insight?

I know it is also done this way in halmodule.cc but I think there are better ways. Either directly in the C Python library or also on the python side by using the C constants.

Do you have a branch to share?

@multigcs

multigcs commented Jul 2, 2026

Copy link
Copy Markdown

@multigcs I really like you viewer. The only thing I don't really like is that you have to use halcmd and decode the text to get the graph. Not your fault, I think there is just now other way to do this but we should change that.

Thanks !

I would have liked to do it entirely using the HAL module, but as you already mentioned, I was missing some information:

For the get_info_pins() a DRIVER field is missing

But the advantage of the halcmd parser is that you can use a dump to view other people's configurations. :)

@hdiethelm

Copy link
Copy Markdown
Contributor

I would have liked to do it entirely using the HAL module, but as you already mentioned, I was missing some information:

Looks like @BsAtHome is working on it.

But the advantage of the halcmd parser is that you can use a dump to view other people's configurations. :)

The disadvantage is, it will break as soon someone decides to change the way the values are shown on the command line... ;-) The scripting mode halcmd -s show should probably be more stable but you use halcmd show as it seams, this can change any time.

You can still dump the config. Just dump the python structure as json. Then you can read it in again on your side to check. https://docs.python.org/3/library/json.html should do the trick.

@grandixximo

Copy link
Copy Markdown
Contributor

The values are raw C enum ints, and I think it's worth noting the module already exports matching constants (hal.HAL_BIT, hal.HAL_IN, ...), so pin['TYPE'] == hal.HAL_BIT already works today. IMO the only real gap here is readability: TYPE 3 is just opaque when you print it.

I'd lean toward the enum route you linked, but with one distinction I think matters: type is a plain enum, but direction is really a bitmask. HAL_IO == HAL_IN | HAL_OUT and HAL_RW == HAL_RO | HAL_WO (see hal.h). So in my view the pythonic mapping is enum.IntEnum for type and enum.IntFlag for direction. I like that IntFlag composes in the repr (HalPinDir.IN|OUT), which reads well.

The nice part, I think, is that since both are int subclasses this stays fully backward compatible: == hal.HAL_BIT keeps working, and json.dumps still serializes them as the plain int, so dumping a config to json is unaffected. The only visible change is that printing gives you HalType.BIT instead of 3.

My preference would be to define these enums once on the python side (in hal.py, or a shared module that both halmodule and your new halquery could import) and alias hal.HAL_BIT = HalType.BIT so there's a single source of truth, rather than building enum objects in the C extension. I think that keeps the C side simple and avoids duplicating the mapping across halmodule and halquery. But that's just my opinion, happy to hear if you'd rather keep it C-side.

@BsAtHome

BsAtHome commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Yes, the enum question is a bit harder because they are slightly disjoint between C/C++ and Python. An enum is part of a class in python. That is in itself is fine, but you need to set it from C, which is involved afaik. Secondly, you need to split the enum definition out into a separate module (like f.ex. haldefs) because both halmodule and halquery would need them.

Creating the a halquery module was very easy because I already have separated out all access to HAL in a query API. It also was a good test-case for the query API. There is no code uploaded in a branch, yet. This is my working tree that builds on #4099 (and that seems to be in limbo). There is, in my version, a very clear separation between hal_lib and everything else. No code can any longer include hal_priv.h (only halrmt still needs fixing). I'd rather not start backporting all that because it means distributing more mess to cleanup again later.

Let me see if I can fork off a branch so you can see what is living in it. Will take a some time...

@multigcs for dumping running configs we just add a command haldump that wraps the above example in a more structured way (encapsulated in json, yaml, plain text or whatever generic formats you deem appropriate).

@multigcs

multigcs commented Jul 2, 2026

Copy link
Copy Markdown

The disadvantage is, it will break as soon someone decides to change the way the values are shown on the command line... ;-) The scripting mode halcmd -s show should probably be more stable but you use halcmd show as it seams, this can change any time.

Oops, I didn't know about that option—I've changed it. Thanks!

@BsAtHome

BsAtHome commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Do you have a branch to share?

This is the local tree pushed:
https://github.com/BsAtHome/linuxcnc/tree/wip_hal-types-and-isolation

Beware: this is WIP. Adjustments and fixing some ordering/naming issues are expected before the query API is finalized and fixed (have a look at the bottom of hal.h). Nearly all old hal types are gone and replaced, still working on the remaining few.

Tests pass, except... Warnings are not an error at the moment in the build. Rtai has several deprecation warnings that are exposed but I don't fully understand why they trigger. Uspace RIP builds should be warning free.
There is an awkward cppcheck issue that depends on cppcheck's version. It might be quelled by include ordering issue, but not yet sure why it happens. It doesn't happen on newer cppcheck.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants