Overview Link to heading

The QML Remote Server is a practical Qt/QML-based application that provides a straightforward solution to a common embedded systems challenge: displaying sensor data and controlling actuators from resource-constrained devices on graphics-rich systems like embedded Linux platforms.

This project addresses the typical scenario where you have sensors and actuators connected to microcontrollers (Arduino, ESP32, STM32) that need to be monitored and controlled through modern graphical interfaces. Rather than implementing complex GUI frameworks on the embedded device itself, this solution separates concerns: the embedded device handles hardware interfacing while a separate system manages the user interface through a simple communication protocol.

The Challenge of Embedded GUI Development Link to heading

Developing graphical user interfaces for embedded systems presents several unique challenges:

Resource Constraints Link to heading

  • Limited Memory: Most embedded devices have severely constrained RAM and flash memory
  • Processing Power: GUI rendering can overwhelm modest microcontrollers
  • Real-time Requirements: Many embedded systems cannot afford the overhead of complex UI frameworks

Development Complexity Link to heading

  • Platform Dependencies: Different embedded platforms require different GUI solutions
  • Rapid Prototyping: Quick iteration on GUI designs is difficult with embedded constraints
  • Maintenance Overhead: GUI updates often require complete firmware updates

Modern UI Expectations Link to heading

  • Responsive Design: Users expect modern, adaptive interfaces that work across devices
  • Rich Visualizations: Complex data visualization requires sophisticated rendering capabilities
  • Real-time Updates: Live data display with smooth animations and transitions

QML Remote Server Solution Link to heading

The QML Remote Server takes a pragmatic approach to this common problem by separating the GUI from the embedded device entirely. This is not a revolutionary concept, but rather a practical implementation of a well-established pattern: let each system do what it does best.

The embedded device focuses on:

  • Reading sensors efficiently
  • Controlling actuators reliably
  • Sending data over simple communication channels

The display system (embedded Linux, PC, etc.) handles:

  • Rich graphical interfaces
  • Complex data visualization
  • User interaction and animations

This separation allows developers to use familiar tools and frameworks for GUI development while keeping the embedded firmware simple and focused.

Key Features Link to heading

Automatic Property Discovery Link to heading

The system automatically detects and exposes QML properties for remote access, reducing manual configuration:

void GenericQMLBridge::discoverProperties()
{
    if (!m_rootObject) return;
    scanObjectProperties(m_rootObject);
    qDebug() << "Discovered" << m_properties.size() << "properties";
}

Real-time Updates Link to heading

Live synchronization of property values between embedded devices and GUI ensures data consistency:

auto observer = QmlPropertyObserver::watch(qmlProp, [this, id](QVariant newValue) {
    QCborMap change;
    change[QStringLiteral("id")] = id;
    change[QStringLiteral("value")] = QCborValue::fromVariant(newValue);
    // Send property change notification
    sendSlipData(packet);
});

Simple Communication Channels Link to heading

Supports both serial (UART) and TCP/IP communication for flexibility in different deployment scenarios:

  • Serial Communication: Direct UART connection for simple microcontroller setups
  • TCP/IP Communication: Network-based communication for distributed systems or WiFi-enabled devices

Reliable Protocol Implementation Link to heading

Uses Serial Line Internet Protocol (RFC 1055) for reliable packet framing, a well-tested standard:

QByteArray SlipProcessor::encodeSlip(const QByteArray &input)
{
    QByteArray encoded;
    for (char byte : input) {
        if ((uint8_t)byte == SLIP_END) {
            encoded.append(SLIP_ESC);
            encoded.append(SLIP_ESC_END);
        } else if ((uint8_t)byte == SLIP_ESC) {
            encoded.append(SLIP_ESC);
            encoded.append(SLIP_ESC_ESC);
        } else {
            encoded.append(byte);
        }
    }
    encoded.append(SLIP_END);
    return encoded;
}

Cross-platform Support Link to heading

Runs on common embedded Linux platforms, desktops, and development systems, making it suitable for both prototyping and production deployments.

Architecture Overview Link to heading

QML Remote Server Architecture

The QML Remote Server follows a straightforward client-server architecture with three main layers:

flowchart TD subgraph ED["🔧 Embedded Device"] MCU["Microcontroller - Arduino, ESP32, STM32"] SENS["Sensors & Actuators - Temperature, Pressure, etc."] end subgraph COMM["📡 Communication Layer"] SERIAL["Serial/UART Connection"] TCP["TCP/IP Network"] end subgraph PROTO["🔒 Protocol Layer"] SLIP["SLIP Protocol - RFC 1055"] CBOR["CBOR Encoding - Binary Data"] end subgraph SERVER["🖥️ QML Remote Server"] BRIDGE["GenericQMLBridge - Core Coordinator"] DISCOVERY["Property Discovery - Auto-detection"] ENGINE["QML Engine - Runtime"] OBSERVER["Property Observer - Change tracking"] end subgraph DASH["📊 QML Dashboard"] DISPLAY["Real-time Display - Live data"] ANIM["Animations & Effects - Visual feedback"] LAYOUT["Responsive Layout - Adaptive UI"] STATUS["Status Monitoring - Connection state"] end %% Data Flow ED -->|"Raw Data"| COMM COMM -->|"Framed Data"| PROTO PROTO -->|"Structured Commands"| SERVER SERVER -->|"Property Updates"| DASH %% Bidirectional Communication DASH -.->|"User Commands"| SERVER SERVER -.->|"Control Messages"| PROTO PROTO -.->|"Encoded Data"| COMM COMM -.->|"Device Control"| ED %% Styling classDef embedded fill:#f8cecc,stroke:#b85450,stroke-width:2px classDef comm fill:#e1d5e7,stroke:#9673a6,stroke-width:2px classDef protocol fill:#d5e8d4,stroke:#82b366,stroke-width:2px classDef server fill:#dae8fc,stroke:#6c8ebf,stroke-width:2px classDef dashboard fill:#fff2cc,stroke:#d6b656,stroke-width:2px class ED,MCU,SENS embedded class COMM,SERIAL,TCP comm class PROTO,SLIP,CBOR protocol class SERVER,BRIDGE,DISCOVERY,ENGINE,OBSERVER server class DASH,DISPLAY,ANIM,LAYOUT,STATUS dashboard

1. GenericQMLBridge (Main Application Logic) Link to heading

The core component that handles the application logic:

  • QML Management: Loads and manages QML interface files
  • Property Discovery: Automatically detects available properties and methods from the QML interface
  • Protocol Handling: Manages communication over serial and TCP connections
  • Command Processing: Processes incoming data and updates the interface accordingly
enum ProtocolCommand {
    CMD_GET_PROPERTY_LIST = 0x01,
    CMD_SET_PROPERTY      = 0x02,
    CMD_INVOKE_METHOD     = 0x03,
    CMD_HEARTBEAT         = 0x04,
    CMD_WATCH_PROPERTY    = 0x20
};

2. SlipProcessor (Communication Layer) Link to heading

Implements standard SLIP protocol for reliable data transmission:

  • Packet Framing: Provides reliable framing for data packets over serial or TCP
  • Escape Handling: Manages escape sequences according to RFC 1055
  • Data Integrity: Ensures data integrity over potentially unreliable communication channels

3. QML Dashboard (User Interface) Link to heading

Standard QML interface components that handle display and user interaction:

  • Data Display: Shows sensor readings and system status with appropriate formatting
  • Visual Elements: Provides gauges, indicators, and animations using Qt’s built-in capabilities
  • Layout Management: Adapts to different screen sizes using QML’s responsive layout features
  • Status Information: Displays connection status and error information

Communication Protocol Link to heading

The system uses a straightforward binary protocol over SLIP framing with CBOR encoding for efficient data exchange.

Protocol Structure Link to heading

┌──────────────┬────────────────────┐
│ Command/Resp │ CBOR Payload       │
│   (1 byte)   │ (optional, varies) │
└──────────────┴────────────────────┘

Supported Commands Link to heading

CodeNameDirectionDescription
0x01CMD_GET_PROPERTY_LISTC→SRequest available properties
0x02CMD_SET_PROPERTYC→SSet property values
0x03CMD_INVOKE_METHODC→SInvoke methods with parameters
0x04CMD_HEARTBEATC→SKeep-alive packet
0x20CMD_WATCH_PROPERTYC→SWatch properties for changes
0x81RESP_GET_PROPERTY_LISTS→CProperty list response
0x82RESP_PROPERTY_CHANGES→CProperty change notification

Protocol State Machine Link to heading

stateDiagram-v2 [*] --> Disconnected Disconnected --> Connecting : Connect to Server Connecting --> Connected : Connection Established Connected --> PropertyDiscovery : Request Properties PropertyDiscovery --> PropertyWatch : Setup Watching PropertyWatch --> ActiveMonitoring : Start Monitoring ActiveMonitoring --> ActiveMonitoring : Property Updates ActiveMonitoring --> PropertyControl : Set Properties PropertyControl --> ActiveMonitoring : Acknowledge ActiveMonitoring --> Heartbeat : Periodic Check Heartbeat --> ActiveMonitoring : Connection OK Connected --> Error : Connection Lost PropertyDiscovery --> Error : Discovery Failed PropertyWatch --> Error : Watch Setup Failed ActiveMonitoring --> Error : Communication Error Heartbeat --> Error : Heartbeat Timeout Error --> Disconnected : Reset Connection ActiveMonitoring --> Disconnected : Explicit Disconnect note right of PropertyDiscovery GET_PROPERTY_LIST (0x01) RESP_PROPERTY_LIST (0x81) end note note right of PropertyWatch WATCH_PROPERTY (0x20) end note note right of ActiveMonitoring RESP_PROPERTY_CHANGE (0x82) Real-time updates end note note right of PropertyControl SET_PROPERTY (0x02) INVOKE_METHOD (0x03) end note note right of Heartbeat HEARTBEAT (0x04) Keep-alive mechanism end note

Example Communication Session Link to heading

sequenceDiagram participant C as Client participant S as Server Note over C,S: Initial Connection and Discovery C->>+S: GET_PROPERTY_LIST 0x01 Note right of C: SLIP packet S->>-C: RESP_PROPERTY_LIST 0x81 Note left of S: Returns properties with IDs and types Note over C,S: Property Watching Setup C->>S: WATCH_PROPERTY 0x20 Note right of C: Subscribe to property ID 5 Note over C,S: Real-time Property Updates S->>C: RESP_PROPERTY_CHANGE 0x82 Note left of S: Property 5 changed value Note over C,S: Property Control C->>S: SET_PROPERTY 0x02 Note right of C: Set property 5 to value 42 Note over C,S: Connection Maintenance C->>S: HEARTBEAT 0x04 Note right of C: Keep-alive packet Note over C,S: Continuous Monitoring loop Property Monitoring S->>C: RESP_PROPERTY_CHANGE 0x82 Note left of S: Live property updates end

Protocol Flow Breakdown: Link to heading

  1. Property Discovery: Client requests available properties and their types
  2. Watch Setup: Client subscribes to specific property changes
  3. Real-time Updates: Server sends notifications when watched properties change
  4. Property Control: Client can set property values on the server
  5. Connection Maintenance: Periodic heartbeats ensure connection stability
  6. Continuous Monitoring: Ongoing property change notifications

Practical Implementation Example Link to heading

Example Dashboard Interface Link to heading

The included SCADA-style dashboard demonstrates real-world usage:

ApplicationWindow {
    property real temperature: 25.0
    property real pressure: 1013.25
    property real humidity: 45.0
    property bool pump1Active: false
    property bool pump2Active: false
    property bool alarmActive: false
    property int tankLevel: 75
    property int setpoint: 50

    function formatTemperature(temp) {
        if (isNaN(temp)) return "--°C"
        return Math.abs(temp) >= 100 ? 
            Math.round(temp) + "°C" : 
            temp.toFixed(1) + "°C"
    }
}

Python Test Client Link to heading

A complete Python test client demonstrates protocol implementation:

class DashboardTester:
    def send_properties(self, prop_map):
        cbor_data = cbor2.dumps(prop_map)
        data = bytes([ProtocolCommand.SET_PROPERTY]) + cbor_data
        encoded_data = SlipProcessor.encode_slip(data)
        self.sock.send(encoded_data)

    def watch_properties(self, id_list):
        cbor_data = cbor2.dumps(id_list)
        data = bytes([ProtocolCommand.WATCH_PROPERTY]) + cbor_data
        encoded_data = SlipProcessor.encode_slip(data)
        self.sock.send(encoded_data)

Usage and Deployment Link to heading

Building the Project Link to heading

# Prerequisites: Qt 6.5+, CMake 3.16+, C++17 compiler
mkdir build && cd build
cmake ..
make

Running the Server Link to heading

For Serial Communication:

./qml-remoteserver examples/dashboard.qml --port /dev/ttyUSB0 --baudrate 115200

For TCP Communication:

./qml-remoteserver examples/dashboard.qml --tcp 8080

Testing with Python Client Link to heading

cd examples
python3 test_dashboard.py --host localhost --port 8080

Use Cases and Applications Link to heading

Typical Embedded System Scenarios Link to heading

  • Sensor Monitoring: Display temperature, pressure, humidity readings from microcontroller-based sensors
  • Equipment Control: Control pumps, valves, motors, and other actuators from a central interface
  • System Status: Monitor device health, connection status, and operational parameters

Development and Prototyping Link to heading

  • Rapid Prototyping: Quick setup of monitoring interfaces for embedded prototypes
  • Debug and Testing: Visual debugging tools for embedded system development
  • Educational Projects: Teaching interface between embedded systems and desktop applications

Industrial Applications Link to heading

  • Simple SCADA Systems: Basic supervisory control for small industrial installations
  • Local Monitoring: On-site monitoring stations for equipment and processes
  • Maintenance Interfaces: Service and calibration interfaces for embedded equipment

Supported Data Types Link to heading

The system supports all common embedded data types:

  • bool: Boolean values for digital states (ON/OFF, enabled/disabled)
  • int: Integer values for counters, levels, and discrete measurements
  • float/double: Floating-point values for analog measurements (temperature, pressure)
  • string: Text values for status messages and identifiers

Advantages and Benefits Link to heading

For Embedded Developers Link to heading

  • Simplified Firmware: No need to implement GUI frameworks on resource-constrained devices
  • Faster Development: Iterate on interfaces without reflashing embedded firmware
  • Focus on Core Logic: Embedded device focuses on sensor reading and actuator control

For Interface Developers Link to heading

  • Familiar Tools: Use standard Qt/QML development tools and techniques
  • Rich Libraries: Access to Qt’s extensive widget and animation libraries
  • Standard Platforms: Develop for common Linux/desktop environments

For System Designers Link to heading

  • Clear Separation: Well-defined boundary between embedded and display systems
  • Flexibility: GUI can run on different hardware than the embedded device
  • Maintainability: Interface updates don’t require embedded firmware changes

Technical Specifications Link to heading

System Requirements Link to heading

  • Qt 6.5+ with QML support
  • CMake 3.16+ for build system
  • C++17 compatible compiler
  • Python 3.6+ for test clients and utilities

Performance Characteristics Link to heading

  • Low Latency: Real-time property updates with minimal delay
  • Reliable Communication: SLIP protocol ensures data integrity
  • Efficient Encoding: CBOR provides compact binary encoding
  • Scalable Architecture: Supports multiple concurrent client connections

Future Enhancements Link to heading

The QML Remote Server architecture provides a solid foundation for future enhancements:

Planned Features Link to heading

  • Authentication and Security: Secure communication protocols
  • Data Logging: Historical data storage and retrieval
  • Plugin Architecture: Extensible functionality through plugins
  • Web Interface: Browser-based access to embedded devices

Extension Points Link to heading

  • Custom Protocols: Support for additional communication protocols
  • Advanced Visualizations: Integration with charting and graphing libraries
  • Mobile Support: Native mobile applications using Qt for Android/iOS

Conclusion Link to heading

The QML Remote Server represents a paradigm shift in embedded GUI development by completely decoupling the user interface from the embedded device. This approach solves the fundamental challenges of embedded GUI development while providing developers with modern, powerful tools for creating rich, responsive user interfaces.

By leveraging the strengths of both embedded systems (efficiency, real-time capability, hardware integration) and modern GUI frameworks (rich visualizations, responsive design, cross-platform compatibility), the QML Remote Server enables the creation of sophisticated control and monitoring applications that would be impossible to implement directly on embedded hardware.

Whether you’re developing industrial automation systems, IoT dashboards, testing equipment, or educational platforms, the QML Remote Server provides a robust, flexible foundation for connecting embedded devices to modern user interface expectations.

The project’s open-source nature, comprehensive documentation, and practical examples make it an excellent starting point for anyone looking to bridge the gap between embedded systems and modern GUI applications.


Project Repository: github.com/martinribelotta/qml-remoteserver

License: MIT License - suitable for both commercial and educational use