Portfolio project for a Linux C++ remote device monitoring and visualization system.
Goal:
- Create a minimal C++20/CMake project.
- Verify that the Ubuntu VM can configure, build, and run a small executable.
Files:
CMakeLists.txtsrc/main.cpp
Build:
cmake -S . -B build
cmake --build buildRun:
./build/rdvc_smokeExpected output:
RDVC Phase 0: C++20/CMake environment check
__cplusplus = 202002
Temporary compiler-only check if CMake is not installed yet:
mkdir -p build
g++ -std=c++20 -Wall -Wextra -Wpedantic src/main.cpp -o build/rdvc_smoke
./build/rdvc_smokeUbuntu package to install before the next CMake verification:
sudo apt-get update
sudo apt-get install -y cmakeGoal:
- Create the first project layout for apps and libraries.
- Build separate hello-level executables for the server, simulator, and viewer.
Directories:
apps/serverapps/simulatorapps/viewerlibs/commonlibs/protocollibs/net
Build:
cmake -S . -B build
cmake --build buildRun:
./build/rdvc_server
./build/rdvc_simulator
./build/rdvc_viewerGoal:
- Let the simulator connect to the server over TCP.
- Send one
STATUSline from the simulator. - Print the received line on the server.
Behavior:
- Server listens on
127.0.0.1:5000. - Simulator sends:
STATUS device_id=sim-001 state=OK battery=87
Build:
cmake -S . -B build
cmake --build buildRun in terminal 1:
./build/rdvc_serverRun in terminal 2:
./build/rdvc_simulatorExpected server output:
RDVC server listening on 127.0.0.1:5000
Received: STATUS device_id=sim-001 state=OK battery=87
Expected simulator output:
Sent: STATUS device_id=sim-001 state=OK battery=87
Goal:
- Move
STATUSline parsing intolibs/protocol. - Introduce a reusable
DeviceStatusstructure. - Let the server print parsed device fields after receiving a line.
Protocol format:
STATUS device_id=<id> state=<state> battery=<percent>
Build:
cmake -S . -B build
cmake --build buildRun in terminal 1:
./build/rdvc_serverRun in terminal 2:
./build/rdvc_simulatorExpected server output:
RDVC server listening on 127.0.0.1:5000
Received: STATUS device_id=sim-001 state=OK battery=87
Parsed device status: id=sim-001 state=OK battery=87
Goal:
- Change the server from one blocking client to a non-blocking
epollloop. - Accept multiple simulator connections.
- Parse each received
STATUSline withlibs/protocol.
Behavior:
- Server keeps running until
Ctrl+C. - Server listens on
127.0.0.1:5000. - Simulator accepts an optional device id argument.
Build:
cmake -S . -B build
cmake --build buildRun in terminal 1:
./build/rdvc_serverRun in terminal 2:
./build/rdvc_simulator sim-001
./build/rdvc_simulator sim-002
./build/rdvc_simulator sim-003Expected server output includes:
RDVC server listening on 127.0.0.1:5000
Client connected: fd=...
Received: STATUS device_id=sim-001 state=OK battery=87
Parsed device status: id=sim-001 state=OK battery=87
Client disconnected: fd=...
Received: STATUS device_id=sim-002 state=OK battery=87
Parsed device status: id=sim-002 state=OK battery=87
Goal:
- Add an in-memory store for the latest status of each device.
- Update the store whenever the server receives a valid
STATUSline. - Keep parsing in
libs/protocoland state storage inlibs/common.
Implementation:
DeviceStatusStorestores one latestDeviceStatusperdevice_id.- Repeated reports from the same
device_idreplace the previous snapshot. - New
device_idvalues increase the stored device count.
Build:
cmake -S . -B build
cmake --build buildRun in terminal 1:
./build/rdvc_serverRun in terminal 2:
./build/rdvc_simulator sim-001
./build/rdvc_simulator sim-002
./build/rdvc_simulator sim-001Expected server output includes:
Parsed device status: id=sim-001 state=OK battery=87
Stored devices: 1
Parsed device status: id=sim-002 state=OK battery=87
Stored devices: 2
Parsed device status: id=sim-001 state=OK battery=87
Stored devices: 2
Goal:
- Replace the viewer hello executable with a minimal Qt Widgets GUI.
- Add a server connect/disconnect button.
- Add a
QTableViewprepared for device status rows.
Implementation:
rdvc_viewerusesQApplication,QTcpSocket,QPushButton,QLabel,QTableView, andQStandardItemModel.- The viewer connects to
127.0.0.1:5000. - Incoming newline-delimited
STATUSlines are parsed withlibs/protocoland displayed in the table. - The current server stores device states but does not broadcast them to the viewer yet. Live table updates from server-published data are a later step.
Build:
cmake -S . -B build
cmake --build buildRun server in a VS Code SSH terminal:
./build/rdvc_serverRun viewer from the Ubuntu desktop session:
./build/rdvc_viewerOptional simulator test:
./build/rdvc_simulator sim-001
./build/rdvc_simulator sim-002Expected result:
- Viewer window opens.
- Pressing
Connectchanges the connection state. - The table shows columns for
Device ID,State, andBattery.
Goal:
- Move viewer socket handling out of the UI thread.
- Use a
QThreadand worker object for network events. - Update the UI through Qt signal/slot connections.
Implementation:
NetworkWorkerownsQTcpSocketinside the worker thread.- The UI asks the worker to connect/disconnect using queued calls.
- The worker parses complete newline-delimited
STATUSlines and emitsDeviceStatusobjects to the UI. - The UI updates
QTableViewrows only from the main thread.
Build:
cmake -S . -B build
cmake --build buildRun server in a VS Code SSH terminal:
./build/rdvc_serverRun viewer from the Ubuntu desktop session:
./build/rdvc_viewerExpected result:
- Viewer window opens.
- Pressing
Connectchanges the state without putting socket work in the UI object. - The current server still stores simulator status internally and does not broadcast status rows to connected viewers yet.
Goal:
- Add a
QOpenGLWidgetviewport to the Qt viewer. - Extend
DeviceStatuswithx,y, andzcoordinates. - Draw simple coordinate axes and device markers.
Implementation:
- Simulator sends
x=... y=... z=...in eachSTATUSline. - Server parses, stores, logs, and broadcasts valid status lines to connected clients.
- Viewer receives status lines in the network worker, updates the table, and upserts device markers in the OpenGL viewport.
- The viewport uses a lightweight isometric projection. VTK and real model loading are intentionally left for later phases.
Build:
cmake -S . -B build
cmake --build buildRun server in terminal 1:
./build/rdvc_serverRun viewer from the Ubuntu desktop session:
./build/rdvc_viewerRun simulators in terminal 2:
./build/rdvc_simulator sim-001 10 0 0
./build/rdvc_simulator sim-002 0 12 4
./build/rdvc_simulator sim-003 -8 -6 10Expected result:
- The table shows
Device ID,State,Battery,X,Y, andZ. - The OpenGL viewport shows coordinate axes and one marker per simulator.
Goal:
- Publish device status updates to MQTT.
- Subscribe to a command topic shape for future device commands.
- Keep MQTT optional so the project still builds when Mosquitto development files are not installed.
Topics:
- Publish:
devices/{id}/status - Subscribe:
devices/+/commands/reset
Implementation:
MqttBridgewraps the Mosquitto C client with RAII-style cleanup.- If
mosquitto.handlibmosquittoare found, CMake builds the server withRDVC_HAS_MQTT=1. - If Mosquitto is not installed, CMake builds the server with
RDVC_HAS_MQTT=0and logs that MQTT is disabled. - Published payloads use the same JSON shape as the REST device response.
Optional Ubuntu packages:
sudo apt-get update
sudo apt-get install -y libmosquitto-dev mosquitto mosquitto-clientsBuild:
cmake -S . -B build
cmake --build buildRun broker in terminal 1:
mosquitto -vSubscribe in terminal 2:
mosquitto_sub -h 127.0.0.1 -t 'devices/+/status'Run server in terminal 3:
./build/rdvc_serverSend simulator statuses in terminal 4:
./build/rdvc_simulator sim-001 10 0 0
./build/rdvc_simulator sim-002 0 12 4Expected MQTT payload:
{"device_id":"sim-001","state":"OK","battery":87,"x":10.000000,"y":0.000000,"z":0.000000}Optional command-topic smoke test:
mosquitto_pub -h 127.0.0.1 -t devices/sim-001/commands/reset -m '{}'Goal:
- Add an optional ACK path for latency measurement.
- Prepare the protocol for a high-concurrency load generator.
- Keep existing simulator/viewer behavior compatible.
Protocol extension:
STATUS device_id=<id> state=<state> battery=<percent> x=<x> y=<y> z=<z> seq=<n> sent_ms=<ms> ack=1
Server response when ack=1:
ACK device_id=<id> seq=<n> server_ms=<ms>
Build:
cmake -S . -B build
cmake --build buildRun server in terminal 1:
./build/rdvc_serverRun ACK smoke test in terminal 2:
./build/rdvc_simulator sim-001 10 0 0 --ackExpected simulator output includes:
Sent: STATUS device_id=sim-001 state=OK battery=87 x=10 y=0 z=0 seq=1 sent_ms=... ack=1
Received: ACK device_id=sim-001 seq=1 server_ms=...
Goal:
- Add a dedicated load generator executable.
- Open many non-blocking TCP connections from one process.
- Send one ACK-requesting
STATUSmessage per connection. - Report basic latency and throughput numbers.
Build:
cmake -S . -B build
cmake --build buildRun server in terminal 1:
./build/rdvc_serverRun load generator in terminal 2:
./build/rdvc_loadgen --connections 100Expected output shape:
connections: 100
acked: 100
errors: 0
elapsed_ms: ...
throughput_ack_per_sec: ...
latency_ms_p50: ...
latency_ms_p95: ...
latency_ms_p99: ...
Notes:
- v1 measures one ACK round trip per connection.
- Rate-controlled, duration-based traffic is intentionally left for the next load generator phase.
Goal:
- Keep TCP connections open during the test.
- Send repeated ACK-requesting
STATUSmessages. - Separate connection count, target message rate, and duration.
- Reduce server per-message logging for ACK load-test traffic.
Build:
cmake -S . -B build
cmake --build buildRun server in terminal 1:
./build/rdvc_serverRun load generator in terminal 2:
./build/rdvc_loadgen --connections 100 --rate 1000 --duration 10Expected output shape:
connections: 100
target_rate_per_sec: 1000
duration_sec: 10
sent: ...
acked: ...
errors: 0
elapsed_ms: ...
traffic_ms: ...
throughput_ack_per_sec: ...
latency_ms_p50: ...
latency_ms_p95: ...
latency_ms_p99: ...
Goal:
- Report server-side health and throughput while loadgen is running.
- Keep client-observed latency percentiles in
rdvc_loadgen. - Prepare metrics as the data source for a future viewer dashboard.
Server metrics output:
METRICS uptime_sec=10 active=100 accepted=100 disconnected=0 devices=100 received=9200 ack_sent=9200 parse_errors=0 broadcast_errors=0 msg_per_sec=997
Build:
cmake -S . -B build
cmake --build buildRun server in terminal 1:
./build/rdvc_serverRun load generator in terminal 2:
./build/rdvc_loadgen --connections 100 --rate 1000 --duration 10Expected result:
rdvc_loadgenprints client-side RTT latency p50/p95/p99.rdvc_serverprints server-side connection and throughput metrics once per second.
Goal:
- Stream server-side metrics to the Qt viewer.
- Show a compact dashboard above the device table.
- Keep load generator latency percentiles in
rdvc_loadgen.
Protocol addition:
HELLO role=viewer
Server metrics stream:
METRICS uptime_sec=10 active=100 accepted=100 disconnected=0 devices=100 received=9200 ack_sent=9200 parse_errors=0 broadcast_errors=0 msg_per_sec=997
Build:
cmake -S . -B build
cmake --build buildRun server in terminal 1:
./build/rdvc_serverRun viewer from the Ubuntu desktop session:
./build/rdvc_viewerRun load generator in terminal 2:
./build/rdvc_loadgen --connections 100 --rate 1000 --duration 10Expected result:
- The viewer dashboard updates
Active,Msg/s,Devices,Received,ACK, andErrors. - Load generator sockets receive ACK lines only; dashboard metrics are sent only to registered viewer clients.
Goal:
- Move
METRICSline formatting/parsing intolibs/protocol. - Keep server-side cumulative counters internal to
rdvc_server. - Draw recent
Msg/sandActivesamples in the Qt viewer.
Implementation notes:
libs/protocol::ServerMetricsrepresents the wire-level metrics snapshot.apps/server/main.cppkeeps internalServerCountersand formats snapshots throughformat_metrics_line().apps/viewer/metrics_chart_widget.cppusesQWidget+QPainterand keeps the latest 60 samples.
Expected result:
- The viewer still updates the dashboard labels.
- The viewer chart shows recent throughput and active connection movement.
Goal:
- Replace the OpenGL-flavored device viewport with a 2D fleet observability map.
- Show devices created by real
rdvc_loadgen --connections NTCP connections. - Keep load generator sockets receiving ACK lines only while viewer clients receive the corresponding
STATUSstream.
Implementation notes:
apps/viewer/fleet_map_widget.cppusesQWidget+QPainter.- The map uses
xandyas top-down position coordinates. - Wheel zoom, drag pan, double-click auto-fit, hover, and click selection are supported.
- Recently updated devices pulse; stale devices fade.
apps/server/main.cppnow broadcastsack=1STATUSlines to registered viewers after sending ACKs to the source client.QOpenGLWidgetis no longer required by the viewer target.
Run server in terminal 1:
./build/rdvc_serverRun viewer from the Ubuntu desktop session:
./build/rdvc_viewerRun load generator in terminal 2:
./build/rdvc_loadgen --connections 100 --rate 1000 --duration 10Expected result:
- Server metrics show real active load generator connections.
- The viewer table receives
load-0,load-1, and later loadgen device statuses. - The fleet map displays those loadgen-backed devices instead of locally generated dummy markers.
Goal:
- Keep the viewer responsive under high-rate loadgen traffic.
- Make chart lines self-explanatory without external explanation.
- Keep stale devices visible in both the fleet map and the table.
Implementation notes:
- The viewer batches incoming
STATUSupdates every 100ms before updating the table and fleet map. - The device table uses a device-id index instead of scanning every row per update.
- The table now includes an
Agecolumn and marks rows asSTALEafter 5 seconds without a new status. - The fleet map updates stale/fresh visual effects on a timer instead of only when new status arrives.
- The metrics chart labels the green
Msg/sline and blueActive connsline directly.
Network observability note:
- The current viewer chart uses server-side
METRICS. - ACK RTT latency percentiles are still measured only inside
rdvc_loadgen. - Showing p50/p95/p99 latency in the viewer needs a future loadgen metrics stream.