Developing a Mobile App for CNC Machine Control
A CNC machine is neither a 3D printer nor an IoT sensor. Control systems like Fanuc, Siemens Sinumerik, Heidenhain TNC, LinuxCNC are industrial equipment with strict reliability, safety, and precision control requirements. A mobile app in this context doesn't replace the control panel—it complements it: program monitoring, NC file upload, spindle and axis telemetry, emergency stop alerts.
Industrial CNC Interfaces
Each CNC manufacturer provides its own API. Openness varies:
| System | Protocol/API | Transport |
|---|---|---|
| Siemens Sinumerik | OPC UA (S7 protocol optional) | Ethernet |
| Fanuc FOCAS | FOCAS2 DLL (Windows) or REST via MTConnect | TCP |
| Mitsubishi CNC | CNC Open API REST | Ethernet |
| LinuxCNC | XML-RPC, HAL remote | TCP |
| Heidenhain TNC | DNC interface, LSV2 protocol | RS-232/Ethernet |
| Haas | MTConnect / Haas API | Ethernet |
MTConnect—an open standard (ANSI/MTC1.4) for reading machine state. MTConnect Agent adapts proprietary data to an XML stream. Most major CNC manufacturers support MTConnect as an optional interface.
MTConnect: Reading Machine Data
MTConnect Agent provides HTTP API. Streaming via current (snapshot) and sample (history with buffer):
class MTConnectClient(private val agentUrl: String) {
suspend fun getCurrent(): MTConnectDocument {
val response = httpClient.get("$agentUrl/current") {
accept(ContentType.Application.Xml)
}
return parseMTConnectXml(response.bodyAsText())
}
// Long-polling stream of changes from sequence number
fun streamSamples(from: Long? = null): Flow<MTConnectEvent> = flow {
var nextSequence = from
while (true) {
val url = if (nextSequence != null)
"$agentUrl/sample?from=$nextSequence&count=100"
else
"$agentUrl/sample?count=100"
val response = httpClient.get(url)
val doc = parseMTConnectXml(response.bodyAsText())
doc.streams.forEach { stream ->
stream.events.forEach { event -> emit(event) }
}
nextSequence = doc.header.nextSequence
if (doc.streams.isEmpty()) delay(500)
}
}
}
Typical DataItems from MTConnect stream: execution (ACTIVE/STOPPED/INTERRUPTED), program (current program name), line (G-code line number), Xact/Yact/Zact (actual axis positions), Sspeed (spindle RPM), load (spindle load %).
OPC UA: Siemens Sinumerik
For Sinumerik 840D sl—OPC UA Server is built into NCU. Siemens namespace (urn:Siemens:SINUMERIK:NC) contains nodes for all CNC parameters: /NC/Channel/State/actSystemTime, /NC/Spindle/actSpeed, /NC/Axis/actPos.
Connection via Eclipse Milo (Java/Kotlin):
class SinumerikOpcUaClient(private val endpoint: String) {
private lateinit var client: OpcUaClient
suspend fun connect() {
val endpoints = DiscoveryClient.getEndpoints(endpoint).await()
val ep = endpoints.first { it.securityPolicyUri == SecurityPolicy.None.uri }
client = OpcUaClient.create(ep.endpointUrl,
endpointFilter = { it == ep },
configurer = { config ->
config.setIdentityProvider(UsernameProvider("OpcUaClient", "password"))
})
client.connect().await()
}
suspend fun readSpindleSpeed(): Double {
val nodeId = NodeId.parse("ns=2;s=/NC/Spindle[u1,1]/actSpeed")
val value = client.readValue(0.0, TimestampsToReturn.Both, nodeId).await()
return (value.value.value as Number).toDouble()
}
fun subscribeToAxisPositions(callback: (AxisPositions) -> Unit) {
val subscription = client.subscriptionManager.createSubscription(500.0).await()
val nodes = listOf("Xact", "Yact", "Zact").map { axis ->
NodeId.parse("ns=2;s=/NC/Channel[u1,1]/MachineAxis[$axis]/actPos")
}
// MonitoredItem for each axis with 100 ms interval
subscription.createMonitoredItems(
TimestampsToReturn.Both,
nodes.map { MonitoredItemCreateRequest(ReadValueId(it, AttributeId.Value.uid(), null, null),
MonitoringMode.Reporting, MonitoringParameters(0.0, 100.0, null, 10, true)) },
) { item, _ ->
item.setValueConsumer { _, value ->
// update axis position
}
}
}
}
Safety of Control Commands
Writing commands to CNC (program change, cycle start) is a potentially dangerous operation. Requirements:
- Authentication and authorization: only authorized operators send commands. JWT with short TTL, MFA for critical operations.
- Network zone: CNC in isolated production network, API gateway is the single entry point with command logging.
- Locks: program start is blocked if machine door is open (data from safety PLC). Mobile app doesn't bypass physical interlocks.
- Audit: every command logged with timestamp, user ID, source IP.
suspend fun startProgram(programName: String) {
// Pre-check state
val state = getMachineState()
require(state.doorsClosed) { "Machine door is open" }
require(state.execution == Execution.STOPPED) { "Machine is not stopped" }
require(state.emergencyStop == EmergencyStop.ARMED) { "E-Stop not armed" }
auditLogger.log(Action.START_PROGRAM, programName, currentUser)
mtConnectAdapter.sendCommand(StartProgramCommand(programName))
}
Uploading NC Programs
G-code programs (.nc, .mpf for Siemens, .cnc for Fanuc) are uploaded via FTP (LinuxCNC, most industrial CNCs support FTP server) or proprietary DNC interface. In Flutter:
Future<void> uploadNcProgram(File program, String remotePath) async {
final ftp = FtpConnect(
host: machineIp,
user: ftpUser,
pass: ftpPassword,
timeout: 30,
);
try {
await ftp.connect();
await ftp.changeDirectory(remotePath);
final uploaded = await ftp.uploadFile(program);
if (!uploaded) throw Exception('FTP upload failed');
} finally {
await ftp.disconnect();
}
}
Developing a mobile app for CNC monitoring and control via MTConnect or OPC UA: 10–16 weeks. Complexity determined by CNC type and scope of control functions. Cost individually quoted after interface audit of specific equipment.







