C++ and Python Property Synchronization¶
This document describes how object properties instanced in C++ and Python are synchronized using the uLib property framework (uprops::Property<T>, uprops::PropertyContainer, and uprops::LocalSignalBackend).
graph LR
subgraph C++ Runtime
CppObj["C++ Object Instance<br/>(uprops::PropertyContainer)"]
CppProp["Property<T><br/>(e.g., Speed, Status)"]
CppLSB["uprops::LocalSignalBackend"]
CppObj --> CppProp
CppProp -->|Set() / PublishChange()| CppLSB
end
subgraph IPC Socket Bridge / Pybind11
Bridge["Event Bus / TCP Socket Bridge<br/>(PropertyChangePayload JSON)"]
end
subgraph Python Runtime
PyLSB["uLib_python.Core.LocalSignalBackend"]
PySub["Python Callback / Subscriber"]
PyObj["Python Object Proxy"]
PySub --> PyObj
PyLSB --> PySub
end
CppLSB <-->|Payload Notification| Bridge
Bridge <-->|SubscribeCallback / PublishChange| PyLSB
1. Overview¶
In uLib, domain objects inherit from uprops::PropertyContainer and hold strongly typed properties (uprops::Property<T>). Whenever a property is updated via Set(), it publishes a PropertyChangePayload notification containing metadata:
* propertyName: Short property identifier (e.g. "Speed").
* qualifiedName: Full property path (e.g. "Device.Speed").
* valueAsString: String representation of the updated value (e.g. "45.5").
* typeName: Type index string (e.g. "double").
Because uLib_python exposes LocalSignalBackend and PropertyContainer to Python via pybind11, applications can synchronize properties bidirectionally either in-process (embedded runtime) or inter-process (separate C++ daemon and Python client connected over TCP/IPC).
2. In-Process Synchronization Example¶
When Python imports uLib_python, both C++ and Python share the LocalSignalBackend singleton within the process.
C++ Code¶
#include "Core/uprops/PropertyContainer.h"
#include "Core/uprops/Property.h"
#include "Core/uprops/backend/LocalSignalBackend.h"
class SynchronizedDevice : public uprops::PropertyContainer {
public:
SynchronizedDevice()
: speed(this, "Speed", 10.0, "m/s", "Motion"),
status(this, "Status", std::string("Idle"), "", "System") {}
uprops::Property<double> speed;
uprops::Property<std::string> status;
};
void ModifyFromCpp(SynchronizedDevice& dev) {
// Changing property automatically emits PropertyChangePayload
dev.speed.Set(85.5);
dev.status.Set("Running");
}
Python Code¶
import uLib_python.Core as uCore
# 1. Subscribe to property change notifications
backend = uCore.LocalSignalBackend.Instance()
def on_property_changed(payload):
print(f"[Python Received] Property '{payload.propertyName}' changed to '{payload.valueAsString}'")
subscriber = backend.SubscribeCallback(on_property_changed)
# 2. Publish property change from Python
payload = uCore.PropertyChangePayload()
payload.propertyName = "Status"
payload.qualifiedName = "Device.Status"
payload.valueAsString = "ConfiguredFromPython"
payload.typeName = "string"
backend.PublishChange(payload)
3. Inter-Process (Socket IPC) Synchronization Example¶
For standalone applications running in separate processes (a C++ server daemon and a Python control script), property changes are serialized to JSON over a TCP socket connection.
C++ App (cpp_py_sync_app.cpp)¶
The C++ application registers a LocalSignalBackend callback that forwards property updates to the connected Python client and receives incoming property updates to set on C++ object instances:
// Forwarding local C++ property changes to Python socket client
auto subscriber = uprops::LocalSignalBackend::Instance().SubscribeCallback(
[clientSocket](const uprops::PropertyChangePayload& payload) {
std::stringstream ss;
ss << "{\"propertyName\":\"" << payload.propertyName
<< "\",\"valueAsString\":\"" << payload.valueAsString << "\"}\n";
std::string json = ss.str();
send(clientSocket, json.c_str(), json.length(), 0);
}
);
// Applying incoming property change from Python to C++ object
uprops::PropertyBase* prop = device.GetProperty(propName);
if (prop && propName == "Status") {
device.status.Set(valStr);
}
Python Script (cpp_py_sync_test.py)¶
Python connects to the C++ server over TCP, sends JSON property update commands, and receives real-time property change events published by C++:
import socket, json
# Connect to C++ server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 9885))
# Send property update from Python to C++ object instance
update_payload = {
"propertyName": "Status",
"valueAsString": "RunningFromPython",
"typeName": "string"
}
s.sendall((json.dumps(update_payload) + "\n").encode('utf-8'))
# Receive acknowledgment from C++
response = s.recv(1024).decode('utf-8')
print("[Python <- C++] Received ACK:", response)
4. Building and Running¶
Build the C++ Application and Python Bindings¶
micromamba run -n uLib cmake --preset clang-make
micromamba run -n uLib cmake --build build/clang-make --target cpp_py_sync_app uLib_python -j"$(nproc)"
Run Automated CTest Verification¶
Run Manually¶
- Launch C++ Server:
- In another terminal, execute Python client: