Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DNC Android

build

The DNC serial engine ported to a dedicated Android tablet — one bolted to each CNC machine, talking to it over USB OTG → FTDI → RS-232.

Qt 6 · QML · C++17 · JNI · usb-serial-for-android

Built by Kaiki Quadros Ferreira at SMI, published with the company's permission. This replaces the Windows version, which is being retired.


Why a tablet

The desktop DNC needs a Windows PC per machine — in practice an old laptop on a cart, shared between machines, running an OS that is out of support. A €90 tablet screwed to the machine's enclosure is cheaper, always there, has a touch screen the operator can use with gloves on, and never needs to be walked anywhere.

The hard part is that Android has no serial port. QSerialPort does not exist there. Getting bytes onto an RS-232 line means going out through USB host mode, through a JNI bridge into a Java USB library, into an FTDI chip, and only then onto the wire.

What changed, and what deliberately didn't

Windows Android
Serial QSerialPort USB Host (OTG) + usb-serial-for-android over JNI
UI Qt Widgets, 5 pages QML / Qt Quick, touch targets
Qt 5.15.2 (pinned by Windows 7) 6.5+
Network QtNetwork none — fully offline

The transmission engine did not change. DncEngine, TxScheduler, FlowController, DrainController, Watchdog, StateMachine and RxInterpreter were ported with no change in logic — the only edits were enum swaps, because the QtSerialPort module does not exist on Android. That engine was tuned over the V28V32E patches against live machines; reimplementing it for a new platform would have thrown away every one of those bench sessions.

What made that possible was extracting an interface:

DncEngine
    │
    ▼
ISerialPort  ......................... core/serial/ISerialPort.h
    │
    ├── AndroidUsbSerialPort ......... production (JNI)
    │        ▼
    │   UsbSerialBridge.java ......... usb-serial-for-android
    │        ▼
    │   FTDI / CP210x / CH340 ........ USB bulk → UART → CNC
    │
    └── LoopbackSerialPort ........... desktop development only

SerialPortFactory picks the backend at compile time. The engine above the interface never learns which one it got.

Config files are portable too: the enums in core/serial/SerialTypes.h keep the same numeric values as QSerialPort, so a machine_config.json from a machine already tuned on Windows imports straight into the tablet instead of being re-tuned from scratch.

Five reasons it wouldn't send

Receiving worked immediately. Sending did not — and none of the five causes produced a visible error. All were divergences from what the Windows driver was doing silently. docs/README-pt.md has the full write-up; briefly:

  1. USB reads blocked the connection forever. SerialInputOutputManager defaults to a read timeout of zero, parking the reader thread inside bulkTransfer indefinitely. UsbDeviceConnection is not thread-safe, so concurrent writes were swallowed without error — which is exactly why receiving worked and sending didn't.
  2. Unserialised USB access. The writer called bulkTransfer while the signal poller called controlTransfer on the same connection with no lock — and the poller ran every 4 ms, roughly 1000 control transfers per second competing with the send. Fixed with a usbLock, a 25 ms poll, and write priority over signal reads.
  3. accepted == 0 swallowed silently. When writeBytes() returned 0 because the queue was full, the code did nothing at all — no commit, no error, no log. The pump spun at 1 ms forever while the machine waited with nothing on screen.
  4. XON/XOFF was never programmed into the chip. FtdiSerialPort.getSupportedFlowControl() returns XON_XOFF_INLINE, not XON_XOFF. The support check rejected the request and then continued without programming any register. Confirmed against the library bytecode: controlTransfer(0x40, 2, 0x1311, port|0x0400) is SIO_SET_FLOW_CTRL with SIO_XON_XOFF_HS — byte for byte what the Windows driver writes.
  5. A write timeout treated as a fatal error. Once flow control finally worked, the FTDI started refusing data on XOFF — which is the correct behaviour. The write blew through its 200 ms budget and the session died on the very first block. On Windows writes are asynchronous, so the bytes simply waited in the driver queue. The fix was to treat SerialTimeoutException as backpressure: read bytesTransferred, push the remainder back onto the head of the queue, retry — and raise the give-up threshold from 3 s to 30 s, because a machine legitimately holds for several seconds while it processes.

One diagnostic note worth keeping: it was hypothesised that the FT230X was TTL-level and that was why the CNC heard nothing. That was wrong. A measurement of +3 to +5 V on idle TX led there, but the reading was untrustworthy because the app was holding the port open in an undefined state at the time. The real causes were all in software.

Hardware

Tablet must support USB Host (OTG) — many don't, and it is not written on the box. The manifest declares uses-feature required="true" deliberately, so an unsupported tablet fails at install time rather than on the factory floor.

Converter: FTDI FT232R (0403:6001) is the homologated kit; FT230X (0403:6015) is also validated — send, receive and online mode were all proven against a Fadal/Fanuc control with it. CP210x and CH340 are in device_filter.xml and should work, but are not machine-validated.

Prefer FTDI, for one specific reason: the latency timer can be set to 1 ms. At the 16 ms default the chip batches deliveries, TxScheduler's pacing becomes fiction, and bytes arrive in bursts — which is precisely what overflows an old CNC.

Power: in host mode the tablet feeds the bus instead of charging. A tablet mounted on a machine and running all day needs a USB-C hub with Power Delivery passthrough, or it goes flat mid-shift.

Isolation: consider an optically isolated converter. A potential difference between the CNC and the tablet destroys USB ports.

Building

Desktop (UI development, loopback backend, no hardware):

cmake -S . -B build-desktop -G Ninja -DCMAKE_PREFIX_PATH=<qt>/mingw_64
cmake --build build-desktop

Android: Qt 6.5+ for Android, SDK 34, NDK, JDK 17. Generate the Qt templates in Qt Creator, then merge the blocks marked [SMI] from android/ into them — the USB-host uses-feature, the USB_DEVICE_ATTACHED intent filter, the device_filter meta-data, and the usb-serial-for-android gradle dependency. See docs/README-pt.md.

Platforms

Android The target. A tablet, an OTG cable and a USB-to-RS-232 adapter
Linux, macOS, Windows The desktop target — same engine, same QML, LoopbackSerialPort instead of the USB bridge. CI builds it on Linux and macOS
iOS Not possible, and not for lack of trying

The desktop target is how the UI is actually developed, so a Mac is a first-class machine for this repository: brew install qt cmake ninja, then the same two cmake lines above. What it cannot do is talk to a CNC, because the serial backend it compiles is the loopback one.

iOS is worth being precise about, because "Flutter and Qt are cross-platform" invites the wrong conclusion. iOS exposes no public API for USB serial. The only sanctioned path to an accessory is the ExternalAccessory framework, and it only speaks to hardware carrying an Apple-certified MFi coprocessor — which a 0403:6001 FTDI cable from a shop-floor drawer does not have and cannot be given. This is not a port that hasn't been written; it is a door that is closed. An iPhone in this system could be a viewer over the network, never the thing holding the serial line.

Licensing build flag

One source tree produces both the free internal build and the licensed sales build, selected by -DSMI_LICENCIAMENTO=ON/OFF (default OFF), rather than by duplicating the folder — because two copies diverge on the first fix somebody forgets to replicate. The signing secret is never in the repository; it comes from -DSMI_LICENSE_SECRET at build time, and the checked-in default is a deliberately obvious placeholder. See docs/LICENCIAMENTO-pt.md.

The licence issuing tools are not part of this repository — they stay with the vendor.

License

MIT — see LICENSE. Published with permission from SMI.

About

DNC engine ported to Android tablets: USB OTG + FTDI + JNI to reach RS-232 on CNC machines. Qt6/QML, C++17.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages