Repository navigation
Replies: 36 comments 70 replies
|
Yes, it outlines the same problems, and also suggests flatbuffers. My question is mostly, is there anything Im overlooking when using websockets as a transport? |
|
Moving halui into task and bypassing NML would be a good thing. The real problem currently in NML is the fact that error messages are not available on all readers and errors can therefore not be correlated correctly over all connections. This needs to be rethought, how errors are generated, propagated and correlated. I'm not to thrilled about using boost derived packages. It is very heavy and most of it, if not all, has been made redundant in modern C++. Actually, I'd rather have the boost dependency removed completely. Weren't you a fan of pybind11? ;-) With the proper interface, a "view" of HAL can be made visible remotely. As long as it stays within the confines of "slow as hell will do", then it should work. However, using a different interface structure would probably be better and leave HAL completely on the server-/machine-side. Using websockets, just like many other network communication layers, is how to handle errors. Errors are very much more recurring on network connections than any local interface. How to handle state-full information and sync/recovery is the art of the beast, so to say. |
|
talking of which, I have a branch that removes boost-python dependency, replacing it with pybind11. currently UIs are a hal userspace component, and that is what killed remote UIs. (it was possible before hal) agree on the error handling. |
|
yes, the error thing is a major pain with halui. |
|
A problem with TCP can be that it is not easy to notice if and when the other end of a connection goes away. Another problem can be connections that only transmit in one direction, without anything coming back, TCP delayed ACK can slow down connections to a crawl. E.g. Windows uses AFAIK 200ms per default, and you really are not supposed to change that. 200ms means 5 packets per second. It seems websocket does some message framing, that should take care of that. |
|
Remote HAL interface should enable to use something like halshow to run remotely. Remote HAL components with uncritical timing could also make sense to run remotely, things like a VFD interface. Anything "realtime" I would say doesn't need supporting and should be discouraged, at least over websockets. OTOH something like mesa hostmot network protocol or remora could possibly be extended to talk "hal" directly. |
|
chrome has a feature specifically for this: https://developer.chrome.com/docs/devtools/settings/throttling |
|
Some questions from reading the code, since a few assumptions in this thread deserve testing: On "zero copy matters locally": did you know the local SHMEM path already XDR-serializes every message? linuxcnc.nml says On the error problem: @BsAtHome, you said errors cannot be correlated across readers. Looking at the mechanism, it is worse: emcError is a queue with head/tail stored in the shared buffer itself, and read() destructively pops (cms_in.cc:817-828). No per-reader cursor, no retention. With two UIs attached, each error goes to exactly one of them, chosen by timing. Queue full and the writer drops silently (emctaskmain.cc:173-186). And motion/io faults mostly bypass the channel altogether; several emcOperatorError call sites are commented out (emctaskmain.cc:2801-2823). So the question is not "how do we serialize errors" but: what should the error model be? My sketch: task keeps a monotonic sequence-numbered ring, entries carry source (task/interp/motion/io), severity, and the originating command's serial; new entries are pushed to all subscribers, and a client sends its last-seen seq on connect to get replayed what it missed. Broadcast, correlation, and reconnect recovery from one mechanism, maybe 100 lines in task. What would it break? On scope: after halui moves into task, is the seam not just three files? shcom.cc (~60 functions; linuxcncrsh, schedrmt, emcsh all ride on it), emcmodule.cc (~40 command methods, ~88 stat attributes; every Python GUI), and emctaskmain.cc itself. The tool channel is already mmap (tooldata_mmap.cc), iocontrol already merged. Is anything else left that I missed? On the command handshake: today completion is a poll race on echo_serial_number with exponential backoff (shcom.cc:263-325; Python spins with esleep under a 5 s timeout, emcmodule.cc:922-960). If the transport is request/reply, the ack becomes explicit and carries an error reference. Any reason to preserve the echo mechanism at all? On transport: the rates involved are tiny (UIs poll status at 20 ms, errors at 200 ms, axis.py:789,834; nobody blocks on NML anywhere in-tree). Given that, is there an argument against running the same websocket server on loopback for local clients too, at least for v1? One code path, no parallel shm protocol; if profiling shows the status publish hurts, add a shm ring behind the same client API later, schema unchanged. On versioning: today the only skew check is the EMC_STAT_TYPE peek at connect (shcom.cc:90); emcmodule does a raw memcpy(sizeof(EMC_STAT)) plus an offsetof-based member table (emcmodule.cc:1022, :1124-1125). UI and task from different builds are silently undetected. FlatBuffers field evolution plus a handshake version fixes this for free. Worth stating as a hard requirement? On prior art: Machinekit already did this migration (machinetalk: protobuf over ZeroMQ, command/status/error/file services). Two lessons seem relevant: they kept NML alive beside it for years and the dual-stack dragged, which Rene's phasing avoids by shrinking NML to one seam first; and ZeroMQ never worked in browsers, so web UIs still needed a bridge, which websocket removes. Anyone here have firsthand machinetalk experience to confirm or correct that? And a starting point: rmu75's rs/zmq-experiments branch already has emc_cmd.fbs (583 lines), emc_stat.fbs, emc_error.fbs. The error schema there is a verbatim copy of the three NML operator messages, so it needs the seq/source/correlator treatment above, but the cmd/stat IDL looks like real groundwork. Rene, does it match what you had in mind for phase 3? |
|
Yes, machinekit never fully replaced nml, they did the classical Linuxcnc thing: putting things on top of other things, not removing the old one. I now have a branch with the first steps done, but will wait until bertos hal changes are done, as they do touch the same things in some places. |
|
On Sa, 2026-09-19 at 14:39 -0700, BsAtHome wrote:
> A problem with TCP can be that it is not easy to notice if and when
> the other end of a connection goes away.
Network connections are notoriously difficult when you want
guarantees.
We should not overthink this. Of course with TCP/IP you are on shaky
grounds in principle and there are no real guarantees in an IP network.
Token ring also had problems, it was slow, and, the token could get
lost, then somebody would have to generate a new one, after some
timeouts.
We just need to be prepared that a websocket-client can go away and
that it is not straight forward to detect that.
|
can not it be done with ZMQ+flatbuffers instead ? it seems to be logical and easier. once it is done and proven to work, that is not a problem to make a bridge to websocket for those who interested. I would not want to control CNC machine via websocket... it is a little bit uncomfortable. ZMQ supports ipc:// transport protocol which sounds like an oblivious default setting for LinuxCNC. No TCP on this path... P.S. I am a web developer during all my professional life, and if necessary I am ready to create a sample implementation of websocket connection for this design + basic web GUI. Have done a really lot of those things. Another thought: current linuxcnc design requires a UI process to be running and linuxcnc dies when it is finished. So even if real GUI is remote/in-browser, some small daemon still have to be running on the CNC machine to pretend as UI. |
|
0mq has (draft) websocket transport |
|
The focus on websockets as transport layer is wrong. The point of a good (generic) interface is that it is independent of the transport layer. |
|
I see it like this: we definitely want web browsers to be viable platforms for GUI-type or pendant-type applications, and that use case should be supported without too many hoops or bridges or whatever, else we get a similar mess like we have now where stuff like python -> JSON -> 0mq -> halui -> python -> deJSON -> halui -> NML -> task is proposed. From that requirement you can derive that some sort of websocket support will be necessary, because that is the only thing that is universally available from webbrowsers (apart from "naked" HTTP/AJAX, but we don't want that) -- correct me if I'm wrong. So now that we have to support websocket in one way or another, the question is, do we really need something else, and why. And we will need some sort of management layer that keeps track of all the connections/subscriptions/etc.... and some sort of RPC mechanism, and we really don't want to implement all of that ourselves. |
|
There is a lot of talk that "websockets is the way to go", but there is no one who actually has described how the whole software stack is supposed to function. Please, explain, carefully and in detail, how the whole software stack is to be rewritten. How it communicates on every level, not just websockets, but the backend/task software stack and the frontend/VCP software stack. How is both consistency and synchronicity between clients and between clients and machine ensured. And how do you assure that the controlled machine not will go haywire when multiple clients are acting upon it. There is no point in fixating on any one single technology until we know what we are trying to solve. |
|
The NML and associated code more and more proves to be a burden in the linuxcnc project. The upstream libnml seems to be more or less unmaintained, the protocol seems to only have C++ bindings (also using a quite old C++ pre 2000 style) and lacks a kind of IDL and associated code generation. People feel uncomfortable changing CMS structures or implementing new services with NML and resort to "bolting on" parallel communications facilities (see the recent zmq/halui controversy, even motion state tags is an example of hesitation to properly extend CMS structs). In other words, the system is obsolete and the sensible way forward seems to be to replace all that with something that is understood, maintained and supports modern languages. As demonstrated in a few experiments, replacing the CMS structures and NML with something else, flatbuffers or even bare C++, is not the hard part, as far as only in-process communication is concerned. Even 1:1 port of current functionality to something like websocket or zmq is more or less straightforward and prototypes exist. The motivation to do all that is to get a new future proof system that solves painpoints like errors currently going only to one consumer (an arbitrary one if there are more listeners of the error channel), that is easier to extend and maintain, and that supports new features and avoids "putting things on top of other things" and creating parallel infrastructure. So, as a long-term goal, vision and IMHO, the system should support multiple GUIs running on networked computers and even in a webbrowser. GUI may be something like gmoccapy or something lightweight like just a DRO or a typescript application on your phone that allows you to use it as a virtual handwheel. Multiple GUIs that can connect/disconnect at arbitrary times means the GUIs should not carry any machine-state. We want to avoid things like one GUI talking to another over ad-hoc mechanisms to change state there (like a jog rate). Such state should either be kept centrally in task or in a separate component that lives independently of the GUI. The current approach of generating the previews in the GUI process is not without problems, there are cases where the main interpreter is desynchronised from the GUI and surprising things can happen with regards to machine limits, offsets etc... A new RPC system should allow for efficient transport of previews of G-Code and backplot of machine movements. (There is also some proof of concept of preview generation in task floating around somewere)
|
|
Some possible comm frameworks (please add)
Serialization frameworks:
|
|
There is one argument against websockets is that it involves TCP/IP stack. It is not necessary true. libwebsockets (libwebsocket-dev debian) provides both server and client bindings, and it supports TCP, TLS (wss://), HTTP/1.1, HTTP/2 (WebSockets over h2), raw sockets, adopting an existing fd (serial, pipe, socketpair). Can run on its own poll loop or on glib, libuv, libev or libevent. The glib option lets it share a GTK GUI's loop [ good choice for UI integration ] So websockets here does not mean linuxcnc go to networking, this protocol only defines handshake and messages framing and it is well supported anywhere. I agree that default setup should use AF_UNIX sockets and not TCP networking to avoid security risks. |
|
Am Mittwoch, dem 07.10.2026 um 23:52 -0700 schrieb BsAtHome:
> currently you cant instantiate 2 interpreters.
Is this a requirement?
It has nothing to do with the NML discussion.
|
|
I'd like to keep one more note because I am unfortunately unable to attend meeting, but I hope it can be considered in planning. It is important to separate WS streams (or whatever there will be ) to at least 2 channels [for WS it could be 2 URLS]
Another one that could be good to have is ability to stream NGC file contents. Browser will hang if it get megabytes of text at once, and it is usually not necessary. Server should reply to any client request to read NGC file , however in usual mode it would be nice to have such protocol:
|
|
When I added hal_port pins for laser rastering data... the thought of replacing NML shared memory communication with port pins crossed my mind.
I envisioned using a text based protocol for easy debugging to store commands in the trajectory planner. The trajectory planner would then have functionality to execute commands, store motion points and respond via an output port much like a terminal application. It would also be able to report status and logs via that port.
I think web ui would be the next layer up and communicate to the USPACE side via some sort of standard sockets.
Thanks,
Curt
…On Fri, Oct 9, 2026, at 10:43 AM, BsAtHome wrote:
This is again choosing technology before the problem is clear and analysed. It is completely backwards.
How about I tell you to require using one specific material, lets say, sand, and you have to build a 12 story house out of that.
—
Reply to this email directly, view it on GitHub <#4571?email_source=notifications&email_token=AACTC4YW2HUV4JATC7WWFJ35TD2RZA5CNFSNUABIM5UWIORPF5TWS5BNNB2WEL2ENFZWG5LTONUW63SDN5WW2ZLOOQXTCOBYGM3TINJQUZZGKYLTN5XKU43VMJZWG4TJMJSWJJLFOZSW45FMMZXW65DFOJPWG3DJMNVQ#discussioncomment-18837450>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AACTC44ALJUI2ULFRMIDH5T5TD2RZAVCNFSNUABGKJSXA33TNF2G64TZHMZTMNRSHEYDKO2ENFZWG5LTONUW63R3GEYDQNBXGEZDHILWAI>.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS <https://github.com/notifications/mobile/ios/AACTC45HYLVSBLIMQYCGXML5TD2RZA5CNFSNUABIM5UWIORPF5TWS5BNNB2WEL2ENFZWG5LTONUW63SDN5WW2ZLOOQXTCOBYGM3TINJQUZZGKYLTN5XKU43VMJZWG4TJMJSWJJLFOZSW45FKMZXW65DFOJPWS33T> and Android <https://github.com/notifications/mobile/android/AACTC47I4G4VTYR4SZW2I7T5TD2RZA5CNFSNUABIM5UWIORPF5TWS5BNNB2WEL2ENFZWG5LTONUW63SDN5WW2ZLOOQXTCOBYGM3TINJQUZZGKYLTN5XKU43VMJZWG4TJMJSWJJLFOZSW45FOMZXW65DFOJPWC3TEOJXWSZA>. Download it today!
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
|
Am Freitag, dem 09.10.2026 um 07:43 -0700 schrieb BsAtHome:
This is again choosing technology before the problem is clear and
analysed. It is completely backwards.
How about I tell you to require using one specific material, lets
say, sand, and you have to build a 12 story house out of that.
Thats not a very good example. It is obvious that with sand alone you
can't build that house, but you could if you add cement steel and
water.
Also it is obvious that websockets can be used to implement remote GUIs
and be used as transport for every other imaginable remote procedure
call that could occur. The questions are *) does it block something
that might come up in the future and *) are there better solutions,
with the additional problem that it is not obvious what "better" means
in this context.
|
|
Before Sunday, a sketch of an architecture that could address the current issues, to be refined with community input; a first attempt at the architectural base @BsAtHome keeps asking for. The transport question comes after this. @c-morley's problems in #4613 are real: halui and every GUI keep their own jog rate, axis selection and increment, and a panel's cycle start has to let the GUI do its pre-checks. What is missing is a model of where each kind of state lives and who may act on which input. #4613, my #4616, #4653 and #4644 all move code between processes without one. Three kinds of state
Two kinds of input
#4613 mixes the two: broadcast to every GUI, two running GUIs both react to the same OK. Focus and arbitration With several UIs running, one must be the one in use. A UI claims focus explicitly, the core arbitrates, and screen inputs go to the UI holding focus. Machine commands need arbitration as well: today there is one mode and whoever switches it last wins, which is where the mode-switch races come from. Run names the program it expects One copy of the state is not enough. Operator A sees program X, another UI loads program Y, A presses cycle start, Y runs. A run command should name the program it expects, and the core refuses if a different one is loaded. A panel cycle start names nothing itself, but it still goes through the request and answer step below: the UI holding focus can confirm what is about to run and say which program it shows, so a program changed from another UI is refused there too. Pre-checks
The GUI no longer intercepts buttons; the core stays the arbiter. One rule Feed hold, stop and abort never depend on a GUI being alive. What I would ask the meeting to agree
With that, the #4613 pins split cleanly (machine commands to the core, screen inputs to the focused UI), #4644 has a clear target, and the NML replacement has requirements to be measured against, whatever the transport. Edit: narrowed the revision idea to the run command, after @rmu75's comment below. |
|
State being kept in the UI should be avoided. IMO firing an event like "workoffsets have changed to G55" would be a mistake, only complete machine-state should be transmitted, and edges / events should be derived in the GUI if needed. Thus synchronisation issues are completely avoided. Stuff that is too big to transfer with each update like the g-code of the currently running program, the preview/backplot and the tooldb needs to be cached on the client-side. Re. tool db we should also have a closer look at the stuff done by Brad Collette / FreeCAD https://github.com/loobric/loobric-freecad Adding a generation number to state to try to prevent stupid races is fruitless IMO and just adds complexity for little benefit and may even lead to "surprising" behaviour. Two operators working on two stations affecting one machine where one loads a program while the other hits cycle start is not a problem of the technology. In an industrial setting, you would need interlocks and whatever anyways. Apart from that, I don't see how this discussion helps in deciding what comms technology to use because comm patterns involved seem to be exactly like they are now with NML and seem to be pretty ordinary: central server, clients connecting to server, server "broadcasting" status, either continously or per request, and clients doing request/response stuff. Any comms layer will support that much. |
|
Following the meeting (conclusions: prototype the replacement and see what has to change, evaluate ZMQ over websocket), three things for the prototype. In his comment on #4613 Chris listed what makes GUI work painful today. These are not requirements for the sketch, but things worth moving towards fixing, and the new layer is the chance to do it. Where the sketch stands on each:
View state stored by the core Agreed with @alex-pres: the core can keep view state for a UI as an opaque blob it never interprets, so a browser reload or a GUI restart picks up where it left off. I will add that to the sketch. Encoding: JSON with deltas as an alternative to flatbuffers A colleague suggested JSON plus a protocol that only sends what changed:
Since the prototype is there, the fair way to decide is to measure both encodings on the same messages, |
|
Some preliminary numbers on the "JSON, send only what changed" idea, measured against rene's websocket-wip branch. Setup: rene's branch as is, with a hook in its websocket server that encodes the status every task cycle (1 ms) while a sim runs. Two configs: the axis 3-axis sim running 3D_Chips.ngc, and the 9-axis sim running a program that moves all nine axes at once, as a worst case. The JSON delta is a merge patch (RFC 7386) against the previous status, with joints, axes and spindles keyed by index so only the entries that changed are sent. Size per update:
An idle machine sends about 27 bytes per update, since only the heartbeats change. Encode time per update, from EMC_STAT inside task's server thread:
The changes-only encoder compares each field with the previous status and writes text directly, so it is cheaper than building the full FlatBuffer. Replaying the deltas onto a snapshot reproduces the full status exactly. Decode time per update:
The last row uses simdjson with a decoder generated from the .fbs, writing each changed field straight into the struct. Replaying the deltas that way also reproduces the full status exactly. For a pure Python client talking to the socket itself (3 axes): unpacking a full FlatBuffers status takes 509 µs and reading just 5 fields 18.5 µs, because the Python FlatBuffers runtime is plain Python; parsing and merging a JSON delta takes 5.6 µs with the standard library. GUIs going through the My reading: on size and encode cost, changes-only JSON is at least as good as FlatBuffers for status, and with a generated decoder it is within a few µs of FlatBuffers on decode too, small next to a GUI refresh period. Pure Python and browser clients get JSON from their standard library. The schema does not have to go away either: both the JSON encoder and decoder in the offline test were generated from the same .fbs files. What it costs, and what I have not done yet:
The code is on my fork, rene's branch plus one commit: https://github.com/grandixximo/linuxcnc/tree/nml-json-proto. |
|
Following up on "prototype it and see what has to change": a sidecar sketch that needs no core change, https://github.com/SyncTwin/lcnc-state-layer. It is a plain NML client (the stock Numbers on stock master 2655e62 (axis sim, axis.ngc, all 91
This agrees with @grandixximo and @alex-pres: size comes from the data model, not the format. With stat's fixed 16-entry Also run once on real servos (EtherCAT, three drives on X/Y/Z, jog ±2 mm through the sidecar, 100 ms): the streamed position matches the drive encoder within one tick, 0 seq gaps, sidecar 1.2–1.4 % of one core with one client. Two findings: encoder noise (±4 counts) changes positions every tick at rest, so without a deadband there are no empty ticks; and Not done: operating state / halui sync (Chris's first point), several clients sending commands at once, CPU with N clients, flatbuffers' own JSON output (@rmu75's point). |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
During the Stuttgart meeting, I gave a NML replacement some thought.
The only positive thing to say about NML is that it has served us well over the years.
I think we all agree that it is unmaintainable, and nobody wants to touch it.
In the past, and probably even more likely in the future, people always wanted to have/create remote UIs, especially web based UIs.
While this is in theory possible with nml, there are very few examples showing how to do it, and web UIs require a bridge.
there have been experiments with zmq and flatbuffers, e.g. https://github.com/rmu75/linuxcnc/tree/rs/zmq-experiments
and flatbuffers actually seem like a good idea. they are zero copy, efficient, and bindings can be generated for almost any language. Leaves the question of transport and framing. websockets do both, and work native in a browser, and almost any language supports websockets. It is mostly just a tcp socket that has been set up in a special way.
so here is my plan:
1 remove nml where it is used only for helpers
this are mostly timers, some types, and other little helpers. I am currently working on this, it can go straight into master, as it doesn't really change much.
2 move halui into task
halui talks to task via nml, and mostly exposes its state machine to hal. there is no reason task cant do this directly.
I have done the same with iocontrol, and it saved over 2000 lines of code, and exposed a lot of race conditions.
#2497
this can also go in master, as it doesnt affect anything user facing.
after this, nml is only used in communication between task and the UI.
3 new c++ api
create new c++ api, that talks via flatbuffers over a websocket to task, with the same messages as nml.
this wil be used by the c++ clients, used mostly for testing. this is what shcom.cc currently does, but extended so all UIs can use it.
4 create python bindings for the c++ interface
this is what emcmodule.cc currently does. In theory, no change to UI code will be required. Practice will probably tell that there are quirks of nml addressed in the UI.
as a stack, I suggest using beast, which is part of boost, which we already depend on.
Preview
currently the preview interpreter instance lives in the UI, which means every UI has to load it, it needs bindings, and every UI needs to manage it. I had the idea to move it to task, so that the UI can request parsed chunks of gcode, and only needs to render it. it has the advantage that limits are applied in headless systems, and it massively simplifies UI code.this is a big change for the future, and needs changes to the canon interface. just dropping the idea here.
HAL
currently a remote UI doesn't have access to hal. I am still thinking about how to solve this in a way that the same c++/python api can be used via SHM or network transport.
All reactions