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
The QML Remote Server follows a straightforward client-server architecture with three main layers:
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
Code | Name | Direction | Description |
---|---|---|---|
0x01 | CMD_GET_PROPERTY_LIST | C→S | Request available properties |
0x02 | CMD_SET_PROPERTY | C→S | Set property values |
0x03 | CMD_INVOKE_METHOD | C→S | Invoke methods with parameters |
0x04 | CMD_HEARTBEAT | C→S | Keep-alive packet |
0x20 | CMD_WATCH_PROPERTY | C→S | Watch properties for changes |
0x81 | RESP_GET_PROPERTY_LIST | S→C | Property list response |
0x82 | RESP_PROPERTY_CHANGE | S→C | Property change notification |
Protocol State Machine Link to heading
Example Communication Session Link to heading
Protocol Flow Breakdown: Link to heading
- Property Discovery: Client requests available properties and their types
- Watch Setup: Client subscribes to specific property changes
- Real-time Updates: Server sends notifications when watched properties change
- Property Control: Client can set property values on the server
- Connection Maintenance: Periodic heartbeats ensure connection stability
- 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 measurementsfloat/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