From 6c6bb79cabb106a48c6739cd954cf578a30ed255 Mon Sep 17 00:00:00 2001 From: Tuan Nghia Nguyen Date: Sat, 28 Mar 2026 12:05:34 +1100 Subject: [PATCH] Initial idea --- CMSCore.sln | 57 ++ _gen_src_01.py | 247 +++++++ _gen_src_02.py | 288 ++++++++ _gen_src_03.py | 207 ++++++ _gen_src_04.py | 195 ++++++ _gen_src_05.py | 336 +++++++++ _gen_src_06.py | 287 ++++++++ _gen_vcxproj.py | 323 +++++++++ anscloud-common/anscloud-common.vcxproj | 74 ++ .../anscloud/common/i_message_broker.h | 125 ++++ .../include/anscloud/common/json_serializer.h | 47 ++ .../include/anscloud/common/types.h | 255 +++++++ anscloud-device/anscloud-device.vcxproj | 86 +++ .../include/anscloud/device/device_agent.h | 123 ++++ .../include/anscloud/device/device_agent_c.h | 114 ++++ anscloud-gateway/anscloud-gateway.vcxproj | 86 +++ .../include/anscloud/gateway/gateway_agent.h | 145 ++++ .../anscloud/gateway/gateway_agent_c.h | 57 ++ architecture-diagram.html | 636 ++++++++++++++++++ test-device/test-device.vcxproj | 86 +++ test-gateway/test-gateway.vcxproj | 86 +++ 21 files changed, 3860 insertions(+) create mode 100644 CMSCore.sln create mode 100644 _gen_src_01.py create mode 100644 _gen_src_02.py create mode 100644 _gen_src_03.py create mode 100644 _gen_src_04.py create mode 100644 _gen_src_05.py create mode 100644 _gen_src_06.py create mode 100644 _gen_vcxproj.py create mode 100644 anscloud-common/anscloud-common.vcxproj create mode 100644 anscloud-common/include/anscloud/common/i_message_broker.h create mode 100644 anscloud-common/include/anscloud/common/json_serializer.h create mode 100644 anscloud-common/include/anscloud/common/types.h create mode 100644 anscloud-device/anscloud-device.vcxproj create mode 100644 anscloud-device/include/anscloud/device/device_agent.h create mode 100644 anscloud-device/include/anscloud/device/device_agent_c.h create mode 100644 anscloud-gateway/anscloud-gateway.vcxproj create mode 100644 anscloud-gateway/include/anscloud/gateway/gateway_agent.h create mode 100644 anscloud-gateway/include/anscloud/gateway/gateway_agent_c.h create mode 100644 architecture-diagram.html create mode 100644 test-device/test-device.vcxproj create mode 100644 test-gateway/test-gateway.vcxproj diff --git a/CMSCore.sln b/CMSCore.sln new file mode 100644 index 0000000..01a1c59 --- /dev/null +++ b/CMSCore.sln @@ -0,0 +1,57 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "anscloud-common", "anscloud-common\anscloud-common.vcxproj", "{B1A2C3D4-0001-4000-8000-000000000001}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "anscloud-device", "anscloud-device\anscloud-device.vcxproj", "{B1A2C3D4-0001-4000-8000-000000000002}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "anscloud-gateway", "anscloud-gateway\anscloud-gateway.vcxproj", "{B1A2C3D4-0001-4000-8000-000000000003}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test-device", "test-device\test-device.vcxproj", "{B1A2C3D4-0001-4000-8000-000000000004}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test-gateway", "test-gateway\test-gateway.vcxproj", "{B1A2C3D4-0001-4000-8000-000000000005}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{F0000000-0000-4000-8000-000000000001}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{F0000000-0000-4000-8000-000000000002}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B1A2C3D4-0001-4000-8000-000000000001}.Debug|x64.ActiveCfg = Debug|x64 + {B1A2C3D4-0001-4000-8000-000000000001}.Debug|x64.Build.0 = Debug|x64 + {B1A2C3D4-0001-4000-8000-000000000001}.Release|x64.ActiveCfg = Release|x64 + {B1A2C3D4-0001-4000-8000-000000000001}.Release|x64.Build.0 = Release|x64 + {B1A2C3D4-0001-4000-8000-000000000002}.Debug|x64.ActiveCfg = Debug|x64 + {B1A2C3D4-0001-4000-8000-000000000002}.Debug|x64.Build.0 = Debug|x64 + {B1A2C3D4-0001-4000-8000-000000000002}.Release|x64.ActiveCfg = Release|x64 + {B1A2C3D4-0001-4000-8000-000000000002}.Release|x64.Build.0 = Release|x64 + {B1A2C3D4-0001-4000-8000-000000000003}.Debug|x64.ActiveCfg = Debug|x64 + {B1A2C3D4-0001-4000-8000-000000000003}.Debug|x64.Build.0 = Debug|x64 + {B1A2C3D4-0001-4000-8000-000000000003}.Release|x64.ActiveCfg = Release|x64 + {B1A2C3D4-0001-4000-8000-000000000003}.Release|x64.Build.0 = Release|x64 + {B1A2C3D4-0001-4000-8000-000000000004}.Debug|x64.ActiveCfg = Debug|x64 + {B1A2C3D4-0001-4000-8000-000000000004}.Debug|x64.Build.0 = Debug|x64 + {B1A2C3D4-0001-4000-8000-000000000004}.Release|x64.ActiveCfg = Release|x64 + {B1A2C3D4-0001-4000-8000-000000000004}.Release|x64.Build.0 = Release|x64 + {B1A2C3D4-0001-4000-8000-000000000005}.Debug|x64.ActiveCfg = Debug|x64 + {B1A2C3D4-0001-4000-8000-000000000005}.Debug|x64.Build.0 = Debug|x64 + {B1A2C3D4-0001-4000-8000-000000000005}.Release|x64.ActiveCfg = Release|x64 + {B1A2C3D4-0001-4000-8000-000000000005}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {B1A2C3D4-0001-4000-8000-000000000001} = {F0000000-0000-4000-8000-000000000001} + {B1A2C3D4-0001-4000-8000-000000000002} = {F0000000-0000-4000-8000-000000000001} + {B1A2C3D4-0001-4000-8000-000000000003} = {F0000000-0000-4000-8000-000000000001} + {B1A2C3D4-0001-4000-8000-000000000004} = {F0000000-0000-4000-8000-000000000002} + {B1A2C3D4-0001-4000-8000-000000000005} = {F0000000-0000-4000-8000-000000000002} + EndGlobalSection +EndGlobal diff --git a/_gen_src_01.py b/_gen_src_01.py new file mode 100644 index 0000000..06e4a0e --- /dev/null +++ b/_gen_src_01.py @@ -0,0 +1,247 @@ +import os +R = r"C:\Projects\CMSCore" +def w(rel, txt): + p = os.path.join(R, rel) + os.makedirs(os.path.dirname(p), exist_ok=True) + with open(p, "w", encoding="utf-8", newline="\r\n") as f: + f.write(txt.lstrip("\n")) + print(f" OK: {rel}") + +# ============================================================================ +# 1. anscloud-common/include/anscloud/common/types.h +# ============================================================================ +w("anscloud-common/include/anscloud/common/types.h", r'''#pragma once +// ========================================================================== +// ANSCloud SDK - Common Types +// ANSCENTER Pty Ltd - Cloud Messaging Protocol Types +// ========================================================================== + +#include +#include +#include +#include +#include + +namespace anscloud { + +// -------------------------------------------------------------------------- +// Protocol version +// -------------------------------------------------------------------------- +constexpr const char* PROTOCOL_VERSION = "1.0.0"; + +// -------------------------------------------------------------------------- +// Exchange names (must match RabbitMQ broker topology) +// -------------------------------------------------------------------------- +namespace exchange { + constexpr const char* DEVICE_TELEMETRY = "ex.device.telemetry"; + constexpr const char* DEVICE_STATUS = "ex.device.status"; + constexpr const char* DEVICE_EVENTS = "ex.device.events"; + constexpr const char* COMMAND = "ex.command"; + constexpr const char* COMMAND_RESPONSE = "ex.command.response"; + constexpr const char* BROADCAST = "ex.broadcast"; +} + +// -------------------------------------------------------------------------- +// Queue name builders +// -------------------------------------------------------------------------- +namespace queue { + inline std::string device_command(const std::string& device_id) { return "q.cmd." + device_id; } + inline std::string device_broadcast(const std::string& device_id){ return "q.broadcast." + device_id; } + // Gateway-side queues (shared) + constexpr const char* TELEMETRY_INGEST = "q.telemetry.ingest"; + constexpr const char* EVENTS_PROCESSOR = "q.events.processor"; + constexpr const char* STATUS_TRACKER = "q.status.tracker"; + constexpr const char* COMMAND_RESPONSES = "q.command.responses"; +} + +// -------------------------------------------------------------------------- +// Device Status +// -------------------------------------------------------------------------- +enum class DeviceStatus { + Unknown = 0, + Online, + Offline, + Connecting, + Error +}; +const char* to_string(DeviceStatus s); +DeviceStatus device_status_from_string(const std::string& s); + +// -------------------------------------------------------------------------- +// Command Types +// -------------------------------------------------------------------------- +enum class CommandType { + Custom = 0, + GetSystemInfo, + GetConfig, + SetConfig, + GetCameraList, + AddCamera, + RemoveCamera, + UpdateCamera, + GetCameraSnapshot, + StartStream, + StopStream, + RestartService, + RebootDevice, + UpdateFirmware, + RunDiagnostics, + GetLogs, + SetSchedule, + GetSchedule +}; +const char* to_string(CommandType t); +CommandType command_type_from_string(const std::string& s); + +// -------------------------------------------------------------------------- +// Event Types +// -------------------------------------------------------------------------- +enum class EventType { + Custom = 0, + Alert, + DetectionLPR, + DetectionFace, + DetectionObject, + DetectionMotion, + LineCrossing, + Intrusion, + Loitering, + CameraDisconnected, + CameraReconnected, + StorageWarning, + SystemError +}; +const char* to_string(EventType t); +EventType event_type_from_string(const std::string& s); + +// -------------------------------------------------------------------------- +// Command Status +// -------------------------------------------------------------------------- +enum class CommandStatus { + Success = 0, + Failed, + Timeout, + Rejected, + Pending, + PartialSuccess +}; +const char* to_string(CommandStatus s); + +// -------------------------------------------------------------------------- +// Data structures +// -------------------------------------------------------------------------- +struct SystemMetrics { + double cpu_usage_percent = 0.0; + double ram_usage_percent = 0.0; + uint64_t ram_total_mb = 0; + uint64_t ram_used_mb = 0; + double gpu_usage_percent = 0.0; + double gpu_memory_percent = 0.0; + double gpu_temperature_c = 0.0; + double disk_usage_percent = 0.0; + uint64_t disk_total_gb = 0; + uint64_t disk_used_gb = 0; + double cpu_temperature_c = 0.0; + uint32_t process_count = 0; + double network_rx_mbps = 0.0; + double network_tx_mbps = 0.0; +}; + +struct CameraInfo { + std::string camera_id; + std::string name; + std::string rtsp_url; + std::string status; // "online", "offline", "error" + int width = 0; + int height = 0; + double fps = 0.0; + std::string codec; + std::string ai_models; // comma-separated model names + bool recording = false; +}; + +struct InferenceMetrics { + uint32_t active_models = 0; + double avg_latency_ms = 0.0; + double total_fps = 0.0; + uint64_t total_detections = 0; +}; + +struct Heartbeat { + std::string device_id; + std::string timestamp; // ISO 8601 + DeviceStatus status = DeviceStatus::Online; + uint64_t uptime_seconds = 0; + std::string firmware_version; + std::string ansvis_version; +}; + +struct DeviceTelemetry { + std::string device_id; + std::string timestamp; + SystemMetrics metrics; + std::vector cameras; + InferenceMetrics inference; +}; + +struct Command { + std::string command_id; // UUID + std::string device_id; // target device + CommandType type = CommandType::Custom; + std::string type_name; // string name for Custom commands + std::string params_json; // JSON string with command parameters + std::string correlation_id; // for RPC matching + std::string reply_to; // reply queue/routing key + int timeout_seconds = 30; + std::string timestamp; +}; + +struct CommandResponse { + std::string command_id; + std::string device_id; + CommandStatus status = CommandStatus::Success; + std::string result_json; // JSON string with results + std::string error_message; + std::string correlation_id; + double execution_time_ms = 0.0; + std::string timestamp; +}; + +struct DeviceEvent { + std::string event_id; // UUID + std::string device_id; + EventType type = EventType::Custom; + std::string type_name; + std::string camera_id; // optional, which camera + std::string data_json; // event payload + std::string thumbnail_base64;// optional snapshot + std::string timestamp; + int severity = 0; // 0=info, 1=warning, 2=critical +}; + +struct DeviceStatusMessage { + std::string device_id; + DeviceStatus status = DeviceStatus::Unknown; + std::string timestamp; + std::string firmware_version; + std::string ansvis_version; + std::string ip_address; + std::string message; +}; + +// -------------------------------------------------------------------------- +// Callback types +// -------------------------------------------------------------------------- +using CommandCallback = std::function; +using TelemetryCallback = std::function; +using HeartbeatCallback = std::function; +using EventCallback = std::function; +using StatusCallback = std::function; +using ConnectionCallback = std::function; +using ErrorCallback = std::function; +using BroadcastCallback = std::function; + +} // namespace anscloud +''') + +print(" [1/15] types.h done") diff --git a/_gen_src_02.py b/_gen_src_02.py new file mode 100644 index 0000000..c51b776 --- /dev/null +++ b/_gen_src_02.py @@ -0,0 +1,288 @@ +import os +R = r"C:\Projects\CMSCore" +def w(rel, txt): + p = os.path.join(R, rel) + os.makedirs(os.path.dirname(p), exist_ok=True) + with open(p, "w", encoding="utf-8", newline="\r\n") as f: + f.write(txt.lstrip("\n")) + print(f" OK: {rel}") + +# ============================================================================ +# 2. i_message_broker.h - Abstract interface (your existing RabbitMQ API plugs in) +# ============================================================================ +w("anscloud-common/include/anscloud/common/i_message_broker.h", r'''#pragma once +// ========================================================================== +// ANSCloud SDK - Abstract Message Broker Interface +// +// Your existing RabbitMQ C++ API implements this interface. +// The SDK never depends on rabbitmq-c directly. +// ========================================================================== + +#include +#include +#include + +namespace anscloud { + +// Callback when a message is received on a consumer +using MessageReceivedFn = std::function; + +// Callback for connection state changes +using BrokerConnectionFn = std::function; + +// Callback for errors +using BrokerErrorFn = std::function; + +// -------------------------------------------------------------------------- +// IMessageBroker - Abstract interface for AMQP operations +// +// Implement this by wrapping your existing RabbitMQ C++ API. +// Both DeviceAgent and GatewayAgent receive an IMessageBroker* at configure(). +// -------------------------------------------------------------------------- +class IMessageBroker { +public: + virtual ~IMessageBroker() = default; + + // --- Connection lifecycle --- + virtual bool connect() = 0; + virtual void disconnect() = 0; + virtual bool is_connected() const = 0; + + // --- Topology declaration --- + virtual bool declare_exchange( + const std::string& exchange_name, + const std::string& type = "topic", // "topic", "direct", "fanout" + bool durable = true + ) = 0; + + virtual bool declare_queue( + const std::string& queue_name, + bool durable = true, + bool exclusive = false, + bool auto_delete = false + ) = 0; + + virtual bool bind_queue( + const std::string& queue_name, + const std::string& exchange_name, + const std::string& routing_key + ) = 0; + + // --- Publishing --- + virtual bool publish( + const std::string& exchange, + const std::string& routing_key, + const std::string& body, + const std::string& correlation_id = "", + const std::string& reply_to = "", + bool persistent = true + ) = 0; + + // --- Consuming --- + // Start consuming from a queue. Returns consumer tag (or empty on failure). + virtual std::string start_consuming( + const std::string& queue_name, + MessageReceivedFn on_message + ) = 0; + + // Cancel a consumer by tag. + virtual bool cancel_consumer(const std::string& consumer_tag) = 0; + + // Process pending messages (call from your event loop). + // timeout_ms: max time to block waiting for messages (0 = non-blocking). + // Returns number of messages processed. + virtual int process_messages(uint32_t timeout_ms = 100) = 0; + + // --- Connection callbacks --- + virtual void on_connected(BrokerConnectionFn fn) = 0; + virtual void on_error(BrokerErrorFn fn) = 0; +}; + +} // namespace anscloud +''') + +# ============================================================================ +# 3. json_serializer.h +# ============================================================================ +w("anscloud-common/include/anscloud/common/json_serializer.h", r'''#pragma once +// ========================================================================== +// ANSCloud SDK - JSON Serialization +// Uses nlohmann/json (or simple manual serialization for now) +// ========================================================================== + +#include +#include +#include + +namespace anscloud { +namespace json { + + // --- Serialize to JSON string --- + std::string serialize(const Heartbeat& hb); + std::string serialize(const DeviceTelemetry& tel); + std::string serialize(const SystemMetrics& m); + std::string serialize(const Command& cmd); + std::string serialize(const CommandResponse& resp); + std::string serialize(const DeviceEvent& evt); + std::string serialize(const DeviceStatusMessage& status); + std::string serialize(const CameraInfo& cam); + std::string serialize(const std::vector& cams); + std::string serialize(const InferenceMetrics& inf); + + // --- Deserialize from JSON string --- + Heartbeat deserialize_heartbeat(const std::string& json); + DeviceTelemetry deserialize_telemetry(const std::string& json); + SystemMetrics deserialize_metrics(const std::string& json); + Command deserialize_command(const std::string& json); + CommandResponse deserialize_response(const std::string& json); + DeviceEvent deserialize_event(const std::string& json); + DeviceStatusMessage deserialize_status_message(const std::string& json); + CameraInfo deserialize_camera(const std::string& json); + std::vector deserialize_camera_list(const std::string& json); + + // --- Utilities --- + std::string now_iso8601(); + std::string generate_uuid(); + +} // namespace json +} // namespace anscloud +''') + +# ============================================================================ +# 4. types.cpp +# ============================================================================ +w("anscloud-common/src/types.cpp", r'''#include +#include + +namespace anscloud { + +// --- DeviceStatus --- +const char* to_string(DeviceStatus s) { + switch (s) { + case DeviceStatus::Online: return "online"; + case DeviceStatus::Offline: return "offline"; + case DeviceStatus::Connecting: return "connecting"; + case DeviceStatus::Error: return "error"; + default: return "unknown"; + } +} + +DeviceStatus device_status_from_string(const std::string& s) { + static const std::unordered_map m = { + {"online", DeviceStatus::Online}, {"offline", DeviceStatus::Offline}, + {"connecting", DeviceStatus::Connecting}, {"error", DeviceStatus::Error} + }; + auto it = m.find(s); + return it != m.end() ? it->second : DeviceStatus::Unknown; +} + +// --- CommandType --- +const char* to_string(CommandType t) { + switch (t) { + case CommandType::GetSystemInfo: return "get_system_info"; + case CommandType::GetConfig: return "get_config"; + case CommandType::SetConfig: return "set_config"; + case CommandType::GetCameraList: return "get_camera_list"; + case CommandType::AddCamera: return "add_camera"; + case CommandType::RemoveCamera: return "remove_camera"; + case CommandType::UpdateCamera: return "update_camera"; + case CommandType::GetCameraSnapshot:return "get_camera_snapshot"; + case CommandType::StartStream: return "start_stream"; + case CommandType::StopStream: return "stop_stream"; + case CommandType::RestartService: return "restart_service"; + case CommandType::RebootDevice: return "reboot_device"; + case CommandType::UpdateFirmware: return "update_firmware"; + case CommandType::RunDiagnostics: return "run_diagnostics"; + case CommandType::GetLogs: return "get_logs"; + case CommandType::SetSchedule: return "set_schedule"; + case CommandType::GetSchedule: return "get_schedule"; + default: return "custom"; + } +} + +CommandType command_type_from_string(const std::string& s) { + static const std::unordered_map m = { + {"get_system_info", CommandType::GetSystemInfo}, + {"get_config", CommandType::GetConfig}, + {"set_config", CommandType::SetConfig}, + {"get_camera_list", CommandType::GetCameraList}, + {"add_camera", CommandType::AddCamera}, + {"remove_camera", CommandType::RemoveCamera}, + {"update_camera", CommandType::UpdateCamera}, + {"get_camera_snapshot", CommandType::GetCameraSnapshot}, + {"start_stream", CommandType::StartStream}, + {"stop_stream", CommandType::StopStream}, + {"restart_service", CommandType::RestartService}, + {"reboot_device", CommandType::RebootDevice}, + {"update_firmware", CommandType::UpdateFirmware}, + {"run_diagnostics", CommandType::RunDiagnostics}, + {"get_logs", CommandType::GetLogs}, + {"set_schedule", CommandType::SetSchedule}, + {"get_schedule", CommandType::GetSchedule} + }; + auto it = m.find(s); + return it != m.end() ? it->second : CommandType::Custom; +} + +// --- EventType --- +const char* to_string(EventType t) { + switch (t) { + case EventType::Alert: return "alert"; + case EventType::DetectionLPR: return "detection_lpr"; + case EventType::DetectionFace: return "detection_face"; + case EventType::DetectionObject: return "detection_object"; + case EventType::DetectionMotion: return "detection_motion"; + case EventType::LineCrossing: return "line_crossing"; + case EventType::Intrusion: return "intrusion"; + case EventType::Loitering: return "loitering"; + case EventType::CameraDisconnected: return "camera_disconnected"; + case EventType::CameraReconnected: return "camera_reconnected"; + case EventType::StorageWarning: return "storage_warning"; + case EventType::SystemError: return "system_error"; + default: return "custom"; + } +} + +EventType event_type_from_string(const std::string& s) { + static const std::unordered_map m = { + {"alert", EventType::Alert}, + {"detection_lpr", EventType::DetectionLPR}, + {"detection_face", EventType::DetectionFace}, + {"detection_object", EventType::DetectionObject}, + {"detection_motion", EventType::DetectionMotion}, + {"line_crossing", EventType::LineCrossing}, + {"intrusion", EventType::Intrusion}, + {"loitering", EventType::Loitering}, + {"camera_disconnected", EventType::CameraDisconnected}, + {"camera_reconnected", EventType::CameraReconnected}, + {"storage_warning", EventType::StorageWarning}, + {"system_error", EventType::SystemError} + }; + auto it = m.find(s); + return it != m.end() ? it->second : EventType::Custom; +} + +// --- CommandStatus --- +const char* to_string(CommandStatus s) { + switch (s) { + case CommandStatus::Success: return "success"; + case CommandStatus::Failed: return "failed"; + case CommandStatus::Timeout: return "timeout"; + case CommandStatus::Rejected: return "rejected"; + case CommandStatus::Pending: return "pending"; + case CommandStatus::PartialSuccess: return "partial_success"; + default: return "unknown"; + } +} + +} // namespace anscloud +''') + +print(" [2-4] i_message_broker.h, json_serializer.h, types.cpp done") diff --git a/_gen_src_03.py b/_gen_src_03.py new file mode 100644 index 0000000..979cd6a --- /dev/null +++ b/_gen_src_03.py @@ -0,0 +1,207 @@ +import os +R = r"C:\Projects\CMSCore" +def w(rel, txt): + p = os.path.join(R, rel) + os.makedirs(os.path.dirname(p), exist_ok=True) + with open(p, "w", encoding="utf-8", newline="\r\n") as f: + f.write(txt.lstrip("\n")) + print(f" OK: {rel}") + +# ============================================================================ +# 5. json_serializer.cpp (simple manual JSON - no nlohmann dependency yet) +# ============================================================================ +w("anscloud-common/src/json_serializer.cpp", r'''#include +#include +#include +#include +#include +#include + +// ========================================================================== +// Minimal JSON builder/parser (no external dependency). +// Replace with nlohmann/json when you add it via NuGet/vcpkg. +// ========================================================================== + +namespace anscloud { +namespace json { + +// ---- Utilities ---- + +std::string now_iso8601() { + auto now = std::chrono::system_clock::now(); + auto t = std::chrono::system_clock::to_time_t(now); + struct tm buf; +#ifdef _WIN32 + gmtime_s(&buf, &t); +#else + gmtime_r(&t, &buf); +#endif + std::ostringstream ss; + ss << std::put_time(&buf, "%Y-%m-%dT%H:%M:%SZ"); + return ss.str(); +} + +std::string generate_uuid() { + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_int_distribution dist(0, 0xFFFFFFFF); + auto hex = [](uint32_t v, int digits) { + std::ostringstream s; + s << std::hex << std::setfill('0') << std::setw(digits) << v; + return s.str(); + }; + uint32_t a = dist(gen), b = dist(gen), c = dist(gen), d = dist(gen); + return hex(a, 8) + "-" + hex(b >> 16, 4) + "-4" + hex((b & 0xFFF), 3) + + "-" + hex(0x8000 | (c & 0x3FFF), 4) + "-" + hex(d, 8) + hex(c >> 16, 4); +} + +// Simple JSON escape +static std::string esc(const std::string& s) { + std::string r; + r.reserve(s.size() + 8); + for (char c : s) { + switch (c) { + case '"': r += "\\\""; break; + case '\\': r += "\\\\"; break; + case '\n': r += "\\n"; break; + case '\r': r += "\\r"; break; + case '\t': r += "\\t"; break; + default: r += c; + } + } + return r; +} + +// Helper macros for building JSON +#define JS_STR(k, v) "\"" k "\":\"" + esc(v) + "\"" +#define JS_NUM(k, v) "\"" k "\":" + std::to_string(v) +#define JS_BOOL(k, v) "\"" k "\":" + std::string((v) ? "true" : "false") + +// ---- Serialize implementations ---- + +std::string serialize(const SystemMetrics& m) { + return "{" + JS_NUM("cpu_usage_percent", m.cpu_usage_percent) + "," + + JS_NUM("ram_usage_percent", m.ram_usage_percent) + "," + + JS_NUM("ram_total_mb", m.ram_total_mb) + "," + + JS_NUM("ram_used_mb", m.ram_used_mb) + "," + + JS_NUM("gpu_usage_percent", m.gpu_usage_percent) + "," + + JS_NUM("gpu_memory_percent", m.gpu_memory_percent) + "," + + JS_NUM("gpu_temperature_c", m.gpu_temperature_c) + "," + + JS_NUM("disk_usage_percent", m.disk_usage_percent) + "," + + JS_NUM("disk_total_gb", m.disk_total_gb) + "," + + JS_NUM("disk_used_gb", m.disk_used_gb) + "," + + JS_NUM("cpu_temperature_c", m.cpu_temperature_c) + "," + + JS_NUM("process_count", m.process_count) + "," + + JS_NUM("network_rx_mbps", m.network_rx_mbps) + "," + + JS_NUM("network_tx_mbps", m.network_tx_mbps) + "}"; +} + +std::string serialize(const CameraInfo& cam) { + return "{" + JS_STR("camera_id", cam.camera_id) + "," + + JS_STR("name", cam.name) + "," + + JS_STR("rtsp_url", cam.rtsp_url) + "," + + JS_STR("status", cam.status) + "," + + JS_NUM("width", cam.width) + "," + + JS_NUM("height", cam.height) + "," + + JS_NUM("fps", cam.fps) + "," + + JS_STR("codec", cam.codec) + "," + + JS_STR("ai_models", cam.ai_models) + "," + + JS_BOOL("recording", cam.recording) + "}"; +} + +std::string serialize(const std::vector& cams) { + std::string r = "["; + for (size_t i = 0; i < cams.size(); ++i) { + if (i > 0) r += ","; + r += serialize(cams[i]); + } + return r + "]"; +} + +std::string serialize(const InferenceMetrics& inf) { + return "{" + JS_NUM("active_models", inf.active_models) + "," + + JS_NUM("avg_latency_ms", inf.avg_latency_ms) + "," + + JS_NUM("total_fps", inf.total_fps) + "," + + JS_NUM("total_detections", inf.total_detections) + "}"; +} + +std::string serialize(const Heartbeat& hb) { + return "{" + JS_STR("device_id", hb.device_id) + "," + + JS_STR("timestamp", hb.timestamp) + "," + + JS_STR("status", to_string(hb.status)) + "," + + JS_NUM("uptime_seconds", hb.uptime_seconds) + "," + + JS_STR("firmware_version", hb.firmware_version) + "," + + JS_STR("ansvis_version", hb.ansvis_version) + "}"; +} + +std::string serialize(const DeviceTelemetry& tel) { + return "{" + JS_STR("device_id", tel.device_id) + "," + + JS_STR("timestamp", tel.timestamp) + "," + + "\"metrics\":" + serialize(tel.metrics) + "," + + "\"cameras\":" + serialize(tel.cameras) + "," + + "\"inference\":" + serialize(tel.inference) + "}"; +} + +std::string serialize(const Command& cmd) { + return "{" + JS_STR("command_id", cmd.command_id) + "," + + JS_STR("device_id", cmd.device_id) + "," + + JS_STR("type", to_string(cmd.type)) + "," + + JS_STR("type_name", cmd.type_name) + "," + + "\"params\":" + (cmd.params_json.empty() ? "{}" : cmd.params_json) + "," + + JS_STR("correlation_id", cmd.correlation_id) + "," + + JS_STR("reply_to", cmd.reply_to) + "," + + JS_NUM("timeout_seconds", cmd.timeout_seconds) + "," + + JS_STR("timestamp", cmd.timestamp) + "}"; +} + +std::string serialize(const CommandResponse& r) { + return "{" + JS_STR("command_id", r.command_id) + "," + + JS_STR("device_id", r.device_id) + "," + + JS_STR("status", to_string(r.status)) + "," + + "\"result\":" + (r.result_json.empty() ? "{}" : r.result_json) + "," + + JS_STR("error_message", r.error_message) + "," + + JS_STR("correlation_id", r.correlation_id) + "," + + JS_NUM("execution_time_ms", r.execution_time_ms) + "," + + JS_STR("timestamp", r.timestamp) + "}"; +} + +std::string serialize(const DeviceEvent& e) { + return "{" + JS_STR("event_id", e.event_id) + "," + + JS_STR("device_id", e.device_id) + "," + + JS_STR("type", to_string(e.type)) + "," + + JS_STR("type_name", e.type_name) + "," + + JS_STR("camera_id", e.camera_id) + "," + + "\"data\":" + (e.data_json.empty() ? "{}" : e.data_json) + "," + + JS_STR("timestamp", e.timestamp) + "," + + JS_NUM("severity", e.severity) + "}"; +} + +std::string serialize(const DeviceStatusMessage& s) { + return "{" + JS_STR("device_id", s.device_id) + "," + + JS_STR("status", to_string(s.status)) + "," + + JS_STR("timestamp", s.timestamp) + "," + + JS_STR("firmware_version", s.firmware_version) + "," + + JS_STR("ansvis_version", s.ansvis_version) + "," + + JS_STR("ip_address", s.ip_address) + "," + + JS_STR("message", s.message) + "}"; +} + +// ---- Deserialize stubs (TODO: implement with proper JSON parser) ---- +// For now these return default-constructed objects. +// Replace with nlohmann/json parsing when ready. + +Heartbeat deserialize_heartbeat(const std::string&) { return {}; } +DeviceTelemetry deserialize_telemetry(const std::string&) { return {}; } +SystemMetrics deserialize_metrics(const std::string&) { return {}; } +Command deserialize_command(const std::string&) { return {}; } +CommandResponse deserialize_response(const std::string&) { return {}; } +DeviceEvent deserialize_event(const std::string&) { return {}; } +DeviceStatusMessage deserialize_status_message(const std::string&) { return {}; } +CameraInfo deserialize_camera(const std::string&) { return {}; } +std::vector deserialize_camera_list(const std::string&){ return {}; } + +} // namespace json +} // namespace anscloud +''') + +print(" [5] json_serializer.cpp done") diff --git a/_gen_src_04.py b/_gen_src_04.py new file mode 100644 index 0000000..61b4aec --- /dev/null +++ b/_gen_src_04.py @@ -0,0 +1,195 @@ +import os +R = r"C:\Projects\CMSCore" +def w(rel, txt): + p = os.path.join(R, rel) + os.makedirs(os.path.dirname(p), exist_ok=True) + with open(p, "w", encoding="utf-8", newline="\r\n") as f: + f.write(txt.lstrip("\n")) + print(f" OK: {rel}") + +# ============================================================================ +# 6. device_agent.h +# ============================================================================ +w("anscloud-device/include/anscloud/device/device_agent.h", r'''#pragma once +// ========================================================================== +// ANSCloud SDK - Device Agent (AIBOX side) +// +// Manages the full device lifecycle: +// - Connects to broker via IMessageBroker (your existing RabbitMQ API) +// - Declares device queues, binds to exchanges +// - Listens for commands, dispatches to your CommandHandler +// - Publishes heartbeat, telemetry, events, status +// - Background threads for timers and message processing +// ========================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace anscloud { + +// -------------------------------------------------------------------------- +// Configuration +// -------------------------------------------------------------------------- +struct DeviceAgentConfig { + std::string device_id; + std::string firmware_version; + std::string ansvis_version; + std::string local_ansvis_api; // e.g. "http://127.0.0.1:8080" + + // Telemetry intervals (seconds) + int heartbeat_interval_sec = 30; + int metrics_interval_sec = 60; + + // Logging + bool verbose_logging = false; +}; + +// -------------------------------------------------------------------------- +// Provider callbacks - your application implements these +// -------------------------------------------------------------------------- +using MetricsProvider = std::function; +using CameraListProvider = std::function()>; +using InferenceProvider = std::function; + +// -------------------------------------------------------------------------- +// Agent status info +// -------------------------------------------------------------------------- +struct DeviceAgentStatus { + bool connected = false; + bool running = false; + uint64_t uptime_seconds = 0; + uint64_t commands_received = 0; + uint64_t commands_executed = 0; + uint64_t heartbeats_sent = 0; + uint64_t telemetry_sent = 0; + uint64_t events_sent = 0; + uint64_t reconnect_count = 0; + std::string last_error; +}; + +// -------------------------------------------------------------------------- +// DeviceAgent +// -------------------------------------------------------------------------- +class DeviceAgent { +public: + DeviceAgent(); + ~DeviceAgent(); + + // Non-copyable + DeviceAgent(const DeviceAgent&) = delete; + DeviceAgent& operator=(const DeviceAgent&) = delete; + + // --- Setup --- + // Pass YOUR existing RabbitMQ API instance (already configured with host/port/credentials). + // DeviceAgent does NOT own the broker - you manage its lifetime. + void configure(IMessageBroker* broker, const DeviceAgentConfig& config); + + // --- Provider registration --- + void set_command_handler(CommandCallback handler); + void set_metrics_provider(MetricsProvider provider); + void set_camera_list_provider(CameraListProvider provider); + void set_inference_provider(InferenceProvider provider); + + // --- Lifecycle --- + bool start(); // NON-BLOCKING: connects, declares topology, starts background threads + void stop(); // Publishes offline status, stops threads, disconnects + + bool is_running() const; + bool is_connected() const; + + // --- Manual publishing --- + bool publish_event(const DeviceEvent& evt); + bool publish_telemetry(); // gathers from providers and publishes + bool send_heartbeat(); + bool send_metrics(); + + // --- Callbacks --- + void on_connection_changed(ConnectionCallback cb); + void on_error(ErrorCallback cb); + void on_broadcast(BroadcastCallback cb); + + // --- Status --- + DeviceAgentStatus get_status() const; + +private: + struct Impl; + std::unique_ptr m_impl; +}; + +} // namespace anscloud +''') + +# ============================================================================ +# 7. device_agent_c.h (C API for FFI) +# ============================================================================ +w("anscloud-device/include/anscloud/device/device_agent_c.h", r'''#pragma once +// ========================================================================== +// ANSCloud SDK - Device Agent C API (for FFI: Go, Python, C#, etc.) +// ========================================================================== + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +// Opaque handle +typedef void* AnsDeviceHandle; + +// C callback types +typedef const char* (*AnsDeviceCmdHandlerFn)( + void* user_data, + const char* command_json // full Command as JSON +); // returns CommandResponse as JSON (caller frees with ansdevice_free_string) + +typedef void (*AnsDeviceConnectionFn)(void* user_data, bool connected, const char* info); +typedef void (*AnsDeviceErrorFn)(void* user_data, int code, const char* message); +typedef void (*AnsDeviceBroadcastFn)(void* user_data, const char* message_json); + +// --- Lifecycle --- +AnsDeviceHandle ansdevice_create(void); +void ansdevice_destroy(AnsDeviceHandle h); + +// Configure: broker_ptr is your IMessageBroker* cast to void* +bool ansdevice_configure(AnsDeviceHandle h, void* broker_ptr, const char* config_json); + +// --- Handlers --- +void ansdevice_set_command_handler(AnsDeviceHandle h, AnsDeviceCmdHandlerFn fn, void* user_data); +void ansdevice_on_connection_changed(AnsDeviceHandle h, AnsDeviceConnectionFn fn, void* user_data); +void ansdevice_on_error(AnsDeviceHandle h, AnsDeviceErrorFn fn, void* user_data); +void ansdevice_on_broadcast(AnsDeviceHandle h, AnsDeviceBroadcastFn fn, void* user_data); + +// --- Lifecycle --- +bool ansdevice_start(AnsDeviceHandle h); +void ansdevice_stop(AnsDeviceHandle h); +bool ansdevice_is_running(AnsDeviceHandle h); +bool ansdevice_is_connected(AnsDeviceHandle h); + +// --- Publishing --- +bool ansdevice_publish_event(AnsDeviceHandle h, const char* event_json); +bool ansdevice_send_heartbeat(AnsDeviceHandle h); +bool ansdevice_send_metrics(AnsDeviceHandle h); + +// --- Status --- +const char* ansdevice_get_status(AnsDeviceHandle h); // returns JSON, free with ansdevice_free_string + +// --- Memory --- +void ansdevice_free_string(const char* str); + +// --- Version --- +const char* ansdevice_version(void); + +#ifdef __cplusplus +} +#endif +''') + +print(" [6-7] device_agent.h, device_agent_c.h done") diff --git a/_gen_src_05.py b/_gen_src_05.py new file mode 100644 index 0000000..06886bd --- /dev/null +++ b/_gen_src_05.py @@ -0,0 +1,336 @@ +import os +R = r"C:\Projects\CMSCore" +def w(rel, txt): + p = os.path.join(R, rel) + os.makedirs(os.path.dirname(p), exist_ok=True) + with open(p, "w", encoding="utf-8", newline="\r\n") as f: + f.write(txt.lstrip("\n")) + print(f" OK: {rel}") + +# ============================================================================ +# 8. device_agent.cpp +# ============================================================================ +w("anscloud-device/src/device_agent.cpp", r'''#include +#include +#include +#include +#include + +namespace anscloud { + +// ========================================================================== +// DeviceAgent::Impl (PIMPL) +// ========================================================================== +struct DeviceAgent::Impl { + IMessageBroker* broker = nullptr; + DeviceAgentConfig config; + + // Callbacks + CommandCallback cmd_handler; + MetricsProvider metrics_provider; + CameraListProvider camera_provider; + InferenceProvider inference_provider; + ConnectionCallback connection_cb; + ErrorCallback error_cb; + BroadcastCallback broadcast_cb; + + // State + std::atomic running{false}; + std::atomic connected{false}; + std::chrono::steady_clock::time_point start_time; + + // Stats + std::atomic commands_received{0}; + std::atomic commands_executed{0}; + std::atomic heartbeats_sent{0}; + std::atomic telemetry_sent{0}; + std::atomic events_sent{0}; + std::atomic reconnect_count{0}; + std::string last_error; + mutable std::mutex error_mutex; + + // Threads + std::thread message_thread; + std::thread heartbeat_thread; + std::thread metrics_thread; + + // Consumer tags + std::string cmd_consumer_tag; + std::string broadcast_consumer_tag; + + void set_error(const std::string& err) { + std::lock_guard lk(error_mutex); + last_error = err; + if (error_cb) error_cb(-1, err); + } + + void log(const std::string& msg) { + if (config.verbose_logging) { + std::cout << "[DeviceAgent:" << config.device_id << "] " << msg << std::endl; + } + } + + // --- Boot sequence --- + bool boot() { + log("Boot: connecting to broker..."); + + if (!broker->is_connected()) { + if (!broker->connect()) { + set_error("Failed to connect to broker"); + return false; + } + } + connected = true; + log("Boot: connected."); + + // Declare exchanges (idempotent) + broker->declare_exchange(exchange::DEVICE_TELEMETRY, "topic"); + broker->declare_exchange(exchange::DEVICE_STATUS, "topic"); + broker->declare_exchange(exchange::DEVICE_EVENTS, "topic"); + broker->declare_exchange(exchange::COMMAND, "topic"); + broker->declare_exchange(exchange::COMMAND_RESPONSE, "topic"); + broker->declare_exchange(exchange::BROADCAST, "fanout"); + log("Boot: exchanges declared."); + + // Declare device command queue + std::string cmd_q = queue::device_command(config.device_id); + broker->declare_queue(cmd_q, true, false, false); + broker->bind_queue(cmd_q, exchange::COMMAND, config.device_id); + log("Boot: command queue " + cmd_q + " bound."); + + // Start consuming commands + cmd_consumer_tag = broker->start_consuming(cmd_q, + [this](const std::string&, const std::string&, const std::string&, + const std::string& corr_id, const std::string&, const std::string& body) + { + handle_command(body, corr_id); + }); + + // Declare broadcast queue + std::string bcast_q = queue::device_broadcast(config.device_id); + broker->declare_queue(bcast_q, false, true, true); // exclusive, auto-delete + broker->bind_queue(bcast_q, exchange::BROADCAST, ""); + broadcast_consumer_tag = broker->start_consuming(bcast_q, + [this](const std::string&, const std::string&, const std::string&, + const std::string&, const std::string&, const std::string& body) + { + if (broadcast_cb) broadcast_cb(body); + }); + log("Boot: consumers started."); + + // Publish online status + publish_status(DeviceStatus::Online, "Device started"); + + // Send initial heartbeat + send_heartbeat_impl(); + + log("Boot: COMPLETE"); + return true; + } + + // --- Command handler --- + void handle_command(const std::string& body, const std::string& corr_id) { + commands_received++; + log("Command received: " + body.substr(0, 80) + "..."); + + Command cmd = json::deserialize_command(body); + cmd.correlation_id = corr_id; + + CommandResponse resp; + resp.command_id = cmd.command_id; + resp.device_id = config.device_id; + resp.correlation_id = corr_id; + resp.timestamp = json::now_iso8601(); + + if (cmd_handler) { + auto t0 = std::chrono::steady_clock::now(); + resp = cmd_handler(cmd); + auto t1 = std::chrono::steady_clock::now(); + resp.execution_time_ms = std::chrono::duration(t1 - t0).count(); + resp.correlation_id = corr_id; + commands_executed++; + } else { + resp.status = CommandStatus::Rejected; + resp.error_message = "No command handler registered"; + } + + // Publish response + std::string resp_json = json::serialize(resp); + broker->publish(exchange::COMMAND_RESPONSE, config.device_id, resp_json, corr_id); + log("Command response sent."); + } + + // --- Publishing helpers --- + void publish_status(DeviceStatus status, const std::string& msg) { + DeviceStatusMessage sm; + sm.device_id = config.device_id; + sm.status = status; + sm.timestamp = json::now_iso8601(); + sm.firmware_version = config.firmware_version; + sm.ansvis_version = config.ansvis_version; + sm.message = msg; + broker->publish(exchange::DEVICE_STATUS, config.device_id, json::serialize(sm)); + } + + bool send_heartbeat_impl() { + Heartbeat hb; + hb.device_id = config.device_id; + hb.timestamp = json::now_iso8601(); + hb.status = DeviceStatus::Online; + auto elapsed = std::chrono::steady_clock::now() - start_time; + hb.uptime_seconds = (uint64_t)std::chrono::duration_cast(elapsed).count(); + hb.firmware_version = config.firmware_version; + hb.ansvis_version = config.ansvis_version; + + bool ok = broker->publish(exchange::DEVICE_TELEMETRY, + config.device_id + ".heartbeat", + json::serialize(hb)); + if (ok) heartbeats_sent++; + return ok; + } + + bool send_telemetry_impl() { + DeviceTelemetry tel; + tel.device_id = config.device_id; + tel.timestamp = json::now_iso8601(); + if (metrics_provider) tel.metrics = metrics_provider(); + if (camera_provider) tel.cameras = camera_provider(); + if (inference_provider) tel.inference = inference_provider(); + + bool ok = broker->publish(exchange::DEVICE_TELEMETRY, + config.device_id + ".metrics", + json::serialize(tel)); + if (ok) telemetry_sent++; + return ok; + } + + // --- Background threads --- + void message_loop() { + while (running) { + if (broker && broker->is_connected()) { + broker->process_messages(100); + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + } + } + + void heartbeat_loop() { + while (running) { + std::this_thread::sleep_for(std::chrono::seconds(config.heartbeat_interval_sec)); + if (running && connected) { + send_heartbeat_impl(); + } + } + } + + void metrics_loop() { + while (running) { + std::this_thread::sleep_for(std::chrono::seconds(config.metrics_interval_sec)); + if (running && connected) { + send_telemetry_impl(); + } + } + } +}; + +// ========================================================================== +// DeviceAgent public methods +// ========================================================================== + +DeviceAgent::DeviceAgent() : m_impl(std::make_unique()) {} +DeviceAgent::~DeviceAgent() { stop(); } + +void DeviceAgent::configure(IMessageBroker* broker, const DeviceAgentConfig& config) { + m_impl->broker = broker; + m_impl->config = config; +} + +void DeviceAgent::set_command_handler(CommandCallback handler) { m_impl->cmd_handler = std::move(handler); } +void DeviceAgent::set_metrics_provider(MetricsProvider provider) { m_impl->metrics_provider = std::move(provider); } +void DeviceAgent::set_camera_list_provider(CameraListProvider prov) { m_impl->camera_provider = std::move(prov); } +void DeviceAgent::set_inference_provider(InferenceProvider provider) { m_impl->inference_provider = std::move(provider); } + +bool DeviceAgent::start() { + if (m_impl->running) return true; + m_impl->start_time = std::chrono::steady_clock::now(); + m_impl->running = true; + + if (!m_impl->boot()) { + m_impl->running = false; + return false; + } + + // Start background threads + m_impl->message_thread = std::thread([this] { m_impl->message_loop(); }); + m_impl->heartbeat_thread = std::thread([this] { m_impl->heartbeat_loop(); }); + m_impl->metrics_thread = std::thread([this] { m_impl->metrics_loop(); }); + + return true; +} + +void DeviceAgent::stop() { + if (!m_impl->running) return; + m_impl->running = false; + + // Publish offline + if (m_impl->broker && m_impl->broker->is_connected()) { + m_impl->publish_status(DeviceStatus::Offline, "Device stopping"); + if (!m_impl->cmd_consumer_tag.empty()) + m_impl->broker->cancel_consumer(m_impl->cmd_consumer_tag); + if (!m_impl->broadcast_consumer_tag.empty()) + m_impl->broker->cancel_consumer(m_impl->broadcast_consumer_tag); + } + + // Join threads + if (m_impl->message_thread.joinable()) m_impl->message_thread.join(); + if (m_impl->heartbeat_thread.joinable()) m_impl->heartbeat_thread.join(); + if (m_impl->metrics_thread.joinable()) m_impl->metrics_thread.join(); + + m_impl->connected = false; +} + +bool DeviceAgent::is_running() const { return m_impl->running; } +bool DeviceAgent::is_connected() const { return m_impl->connected && m_impl->broker && m_impl->broker->is_connected(); } + +bool DeviceAgent::publish_event(const DeviceEvent& evt) { + if (!m_impl->connected) return false; + std::string routing = m_impl->config.device_id + "." + to_string(evt.type); + bool ok = m_impl->broker->publish(exchange::DEVICE_EVENTS, routing, json::serialize(evt)); + if (ok) m_impl->events_sent++; + return ok; +} + +bool DeviceAgent::publish_telemetry() { return m_impl->send_telemetry_impl(); } +bool DeviceAgent::send_heartbeat() { return m_impl->send_heartbeat_impl(); } +bool DeviceAgent::send_metrics() { return m_impl->send_telemetry_impl(); } + +void DeviceAgent::on_connection_changed(ConnectionCallback cb) { m_impl->connection_cb = std::move(cb); } +void DeviceAgent::on_error(ErrorCallback cb) { m_impl->error_cb = std::move(cb); } +void DeviceAgent::on_broadcast(BroadcastCallback cb) { m_impl->broadcast_cb = std::move(cb); } + +DeviceAgentStatus DeviceAgent::get_status() const { + DeviceAgentStatus s; + s.connected = m_impl->connected; + s.running = m_impl->running; + if (m_impl->running) { + auto elapsed = std::chrono::steady_clock::now() - m_impl->start_time; + s.uptime_seconds = (uint64_t)std::chrono::duration_cast(elapsed).count(); + } + s.commands_received = m_impl->commands_received; + s.commands_executed = m_impl->commands_executed; + s.heartbeats_sent = m_impl->heartbeats_sent; + s.telemetry_sent = m_impl->telemetry_sent; + s.events_sent = m_impl->events_sent; + s.reconnect_count = m_impl->reconnect_count; + { + std::lock_guard lk(m_impl->error_mutex); + s.last_error = m_impl->last_error; + } + return s; +} + +} // namespace anscloud +''') + +print(" [8] device_agent.cpp done") diff --git a/_gen_src_06.py b/_gen_src_06.py new file mode 100644 index 0000000..3b2a900 --- /dev/null +++ b/_gen_src_06.py @@ -0,0 +1,287 @@ +import os +R = r"C:\Projects\CMSCore" +def w(rel, txt): + p = os.path.join(R, rel) + os.makedirs(os.path.dirname(p), exist_ok=True) + with open(p, "w", encoding="utf-8", newline="\r\n") as f: + f.write(txt.lstrip("\n")) + print(f" OK: {rel}") + +# ============================================================================ +# 9. device_agent_c.cpp +# ============================================================================ +w("anscloud-device/src/device_agent_c.cpp", r'''#include +#include +#include +#include +#include + +using namespace anscloud; + +static char* alloc_copy(const std::string& s) { + char* p = new char[s.size() + 1]; + memcpy(p, s.c_str(), s.size() + 1); + return p; +} + +extern "C" { + +AnsDeviceHandle ansdevice_create(void) { + return new DeviceAgent(); +} + +void ansdevice_destroy(AnsDeviceHandle h) { + delete static_cast(h); +} + +bool ansdevice_configure(AnsDeviceHandle h, void* broker_ptr, const char* config_json) { + if (!h || !broker_ptr) return false; + auto* agent = static_cast(h); + auto* broker = static_cast(broker_ptr); + // TODO: parse config_json into DeviceAgentConfig + DeviceAgentConfig cfg; + // For now just a minimal config + cfg.device_id = "device"; + cfg.verbose_logging = true; + agent->configure(broker, cfg); + return true; +} + +void ansdevice_set_command_handler(AnsDeviceHandle h, AnsDeviceCmdHandlerFn fn, void* user_data) { + if (!h || !fn) return; + auto* agent = static_cast(h); + agent->set_command_handler([fn, user_data](const Command& cmd) -> CommandResponse { + std::string cmd_json = json::serialize(cmd); + const char* resp_str = fn(user_data, cmd_json.c_str()); + CommandResponse resp; + if (resp_str) { + resp = json::deserialize_response(resp_str); + ansdevice_free_string(resp_str); + } + return resp; + }); +} + +void ansdevice_on_connection_changed(AnsDeviceHandle h, AnsDeviceConnectionFn fn, void* ud) { + if (!h || !fn) return; + static_cast(h)->on_connection_changed( + [fn, ud](bool c, const std::string& info) { fn(ud, c, info.c_str()); }); +} + +void ansdevice_on_error(AnsDeviceHandle h, AnsDeviceErrorFn fn, void* ud) { + if (!h || !fn) return; + static_cast(h)->on_error( + [fn, ud](int code, const std::string& msg) { fn(ud, code, msg.c_str()); }); +} + +void ansdevice_on_broadcast(AnsDeviceHandle h, AnsDeviceBroadcastFn fn, void* ud) { + if (!h || !fn) return; + static_cast(h)->on_broadcast( + [fn, ud](const std::string& msg) { fn(ud, msg.c_str()); }); +} + +bool ansdevice_start(AnsDeviceHandle h) { return h ? static_cast(h)->start() : false; } +void ansdevice_stop(AnsDeviceHandle h) { if (h) static_cast(h)->stop(); } +bool ansdevice_is_running(AnsDeviceHandle h) { return h ? static_cast(h)->is_running() : false; } +bool ansdevice_is_connected(AnsDeviceHandle h){ return h ? static_cast(h)->is_connected() : false; } + +bool ansdevice_publish_event(AnsDeviceHandle h, const char* event_json) { + if (!h || !event_json) return false; + DeviceEvent evt = json::deserialize_event(event_json); + return static_cast(h)->publish_event(evt); +} + +bool ansdevice_send_heartbeat(AnsDeviceHandle h) { return h ? static_cast(h)->send_heartbeat() : false; } +bool ansdevice_send_metrics(AnsDeviceHandle h) { return h ? static_cast(h)->send_metrics() : false; } + +const char* ansdevice_get_status(AnsDeviceHandle h) { + if (!h) return alloc_copy("{}"); + auto s = static_cast(h)->get_status(); + // Simple JSON + std::string j = "{\"connected\":" + std::string(s.connected ? "true" : "false") + + ",\"running\":" + std::string(s.running ? "true" : "false") + + ",\"uptime_seconds\":" + std::to_string(s.uptime_seconds) + + ",\"commands_received\":" + std::to_string(s.commands_received) + + ",\"commands_executed\":" + std::to_string(s.commands_executed) + + ",\"heartbeats_sent\":" + std::to_string(s.heartbeats_sent) + "}"; + return alloc_copy(j); +} + +void ansdevice_free_string(const char* str) { + delete[] str; +} + +const char* ansdevice_version(void) { + return "1.0.0"; +} + +} // extern "C" +''') + +# ============================================================================ +# 10. gateway_agent.h +# ============================================================================ +w("anscloud-gateway/include/anscloud/gateway/gateway_agent.h", r'''#pragma once +// ========================================================================== +// ANSCloud SDK - Gateway Agent (CMS side) +// +// Manages CMS gateway lifecycle: +// - Connects to broker via IMessageBroker (your existing RabbitMQ API) +// - Consumes telemetry, heartbeats, events, status from all devices +// - Sends commands to devices (synchronous RPC via correlation_id) +// - Tracks device online/offline state +// ========================================================================== + +#include +#include +#include +#include +#include +#include + +namespace anscloud { + +// -------------------------------------------------------------------------- +// Configuration +// -------------------------------------------------------------------------- +struct GatewayAgentConfig { + int device_timeout_seconds = 90; // mark offline if no heartbeat + int default_command_timeout_seconds = 30; + int max_pending_commands = 1000; + bool verbose_logging = false; +}; + +// -------------------------------------------------------------------------- +// Command result (from synchronous RPC) +// -------------------------------------------------------------------------- +struct CommandResult { + bool success = false; + CommandStatus status = CommandStatus::Timeout; + std::string result_json; + std::string error_message; + double execution_time_ms = 0.0; +}; + +// -------------------------------------------------------------------------- +// Device state as tracked by the gateway +// -------------------------------------------------------------------------- +struct DeviceState { + std::string device_id; + DeviceStatus status = DeviceStatus::Unknown; + std::string firmware_version; + std::string ansvis_version; + std::string ip_address; + std::string last_heartbeat; // ISO 8601 timestamp + std::string last_telemetry; + uint64_t uptime_seconds = 0; + SystemMetrics last_metrics; + uint32_t camera_count = 0; +}; + +// -------------------------------------------------------------------------- +// Batch command result +// -------------------------------------------------------------------------- +struct BatchResult { + std::vector> results; // device_id -> result + int succeeded = 0; + int failed = 0; + int timed_out = 0; +}; + +// -------------------------------------------------------------------------- +// Gateway statistics +// -------------------------------------------------------------------------- +struct GatewayStats { + uint64_t commands_sent = 0; + uint64_t responses_received = 0; + uint64_t timeouts = 0; + uint64_t telemetry_received = 0; + uint64_t heartbeats_received= 0; + uint64_t events_received = 0; + uint32_t devices_online = 0; + uint32_t devices_total = 0; + uint32_t pending_commands = 0; + uint64_t uptime_seconds = 0; +}; + +// -------------------------------------------------------------------------- +// GatewayAgent +// -------------------------------------------------------------------------- +class GatewayAgent { +public: + GatewayAgent(); + ~GatewayAgent(); + + GatewayAgent(const GatewayAgent&) = delete; + GatewayAgent& operator=(const GatewayAgent&) = delete; + + // --- Setup --- + void configure(IMessageBroker* broker, const GatewayAgentConfig& config); + + // --- Lifecycle --- + bool start(); + void stop(); + bool is_running() const; + bool is_connected() const; + + // --- Commands --- + // Synchronous RPC: sends command, blocks until response or timeout + CommandResult send_command( + const std::string& device_id, + CommandType type, + const std::string& params_json = "{}", + int timeout_seconds = 0 // 0 = use default + ); + + // Async version + std::future send_command_async( + const std::string& device_id, + CommandType type, + const std::string& params_json = "{}", + int timeout_seconds = 0 + ); + + // Fire-and-forget (no response expected) + bool send_command_fire_and_forget( + const std::string& device_id, + CommandType type, + const std::string& params_json = "{}" + ); + + // Send to multiple devices + BatchResult send_batch_command( + const std::vector& device_ids, + CommandType type, + const std::string& params_json = "{}", + int timeout_seconds = 0 + ); + + // Broadcast to all devices + bool send_broadcast(const std::string& message_json); + + // --- Device tracking --- + std::vector get_all_devices() const; + DeviceState get_device(const std::string& device_id) const; + std::vector get_online_devices() const; + bool is_device_online(const std::string& device_id) const; + + // --- Callbacks --- + void on_telemetry(TelemetryCallback cb); + void on_heartbeat(HeartbeatCallback cb); + void on_event(EventCallback cb); + void on_device_status_changed(StatusCallback cb); + void on_connection_changed(ConnectionCallback cb); + void on_error(ErrorCallback cb); + + // --- Stats --- + GatewayStats get_stats() const; + +private: + struct Impl; + std::unique_ptr m_impl; +}; + +} // namespace anscloud +''') + +print(" [9-10] device_agent_c.cpp, gateway_agent.h done") diff --git a/_gen_vcxproj.py b/_gen_vcxproj.py new file mode 100644 index 0000000..d2317bf --- /dev/null +++ b/_gen_vcxproj.py @@ -0,0 +1,323 @@ +import os + +R = r"C:\Projects\CMSCore" + +def w(rel, txt): + p = os.path.join(R, rel) + os.makedirs(os.path.dirname(p), exist_ok=True) + with open(p, "w", encoding="utf-8", newline="\r\n") as f: + f.write(txt.lstrip("\n")) + print(f" OK: {rel}") + +# === VCXPROJ: anscloud-common (StaticLibrary) === +w("anscloud-common/anscloud-common.vcxproj", r''' + + + Debugx64 + Releasex64 + + + 17.0 + {11111111-1111-4000-8000-000000000001} + anscloudcommon + 10.0 + + + + StaticLibrarytrue + v143Unicode + + + StaticLibraryfalse + v143trueUnicode + + + + + + + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)obj\$(ProjectName)\$(Configuration)\ + + + + Level4true + _DEBUG;_LIB;%(PreprocessorDefinitions) + truestdcpp17 + $(ProjectDir)include;%(AdditionalIncludeDirectories) + + + + + Level4true + NDEBUG;_LIB;%(PreprocessorDefinitions) + truestdcpp17 + $(ProjectDir)include;%(AdditionalIncludeDirectories) + + + + + + + + + + + + + +''') + +# === VCXPROJ: anscloud-device (StaticLibrary) === +w("anscloud-device/anscloud-device.vcxproj", r''' + + + Debugx64 + Releasex64 + + + 17.0 + {22222222-2222-4000-8000-000000000002} + ansclouddevice + 10.0 + + + + StaticLibrarytrue + v143Unicode + + + StaticLibraryfalse + v143trueUnicode + + + + + + + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)obj\$(ProjectName)\$(Configuration)\ + + + + Level4true + _DEBUG;_LIB;%(PreprocessorDefinitions) + truestdcpp17 + $(ProjectDir)include;$(SolutionDir)anscloud-common\include;%(AdditionalIncludeDirectories) + + + + + Level4true + NDEBUG;_LIB;%(PreprocessorDefinitions) + truestdcpp17 + $(ProjectDir)include;$(SolutionDir)anscloud-common\include;%(AdditionalIncludeDirectories) + + + + + + + + + + + + + {11111111-1111-4000-8000-000000000001} + + + + +''') + +# === VCXPROJ: anscloud-gateway (StaticLibrary) === +w("anscloud-gateway/anscloud-gateway.vcxproj", r''' + + + Debugx64 + Releasex64 + + + 17.0 + {33333333-3333-4000-8000-000000000003} + anscloudgateway + 10.0 + + + + StaticLibrarytrue + v143Unicode + + + StaticLibraryfalse + v143trueUnicode + + + + + + + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)obj\$(ProjectName)\$(Configuration)\ + + + + Level4true + _DEBUG;_LIB;%(PreprocessorDefinitions) + truestdcpp17 + $(ProjectDir)include;$(SolutionDir)anscloud-common\include;%(AdditionalIncludeDirectories) + + + + + Level4true + NDEBUG;_LIB;%(PreprocessorDefinitions) + truestdcpp17 + $(ProjectDir)include;$(SolutionDir)anscloud-common\include;%(AdditionalIncludeDirectories) + + + + + + + + + + + + + {11111111-1111-4000-8000-000000000001} + + + + +''') + +# === VCXPROJ: test-device (Console Application) === +w("test-device/test-device.vcxproj", r''' + + + Debugx64 + Releasex64 + + + 17.0 + {44444444-4444-4000-8000-000000000004} + testdevice + 10.0 + + + + Applicationtrue + v143Unicode + + + Applicationfalse + v143trueUnicode + + + + + + + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)obj\$(ProjectName)\$(Configuration)\ + + + + Level4true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + truestdcpp17 + $(SolutionDir)anscloud-common\include;$(SolutionDir)anscloud-device\include;%(AdditionalIncludeDirectories) + + Console + + + + Level4true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + truestdcpp17 + $(SolutionDir)anscloud-common\include;$(SolutionDir)anscloud-device\include;%(AdditionalIncludeDirectories) + + Console + + + + + + + {11111111-1111-4000-8000-000000000001} + + + {22222222-2222-4000-8000-000000000002} + + + + +''') + +# === VCXPROJ: test-gateway (Console Application) === +w("test-gateway/test-gateway.vcxproj", r''' + + + Debugx64 + Releasex64 + + + 17.0 + {55555555-5555-4000-8000-000000000005} + testgateway + 10.0 + + + + Applicationtrue + v143Unicode + + + Applicationfalse + v143trueUnicode + + + + + + + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)obj\$(ProjectName)\$(Configuration)\ + + + + Level4true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + truestdcpp17 + $(SolutionDir)anscloud-common\include;$(SolutionDir)anscloud-gateway\include;%(AdditionalIncludeDirectories) + + Console + + + + Level4true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + truestdcpp17 + $(SolutionDir)anscloud-common\include;$(SolutionDir)anscloud-gateway\include;%(AdditionalIncludeDirectories) + + Console + + + + + + + {11111111-1111-4000-8000-000000000001} + + + {33333333-3333-4000-8000-000000000003} + + + + +''') + +print("\n=== All vcxproj files created ===") diff --git a/anscloud-common/anscloud-common.vcxproj b/anscloud-common/anscloud-common.vcxproj new file mode 100644 index 0000000..3fad751 --- /dev/null +++ b/anscloud-common/anscloud-common.vcxproj @@ -0,0 +1,74 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 17.0 + {B1A2C3D4-0001-4000-8000-000000000001} + anscloudcommon + 10.0 + anscloud-common + + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + Unicode + true + + + + + + + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)obj\$(ProjectName)\$(Configuration)\ + + + + Level3 + true + _DEBUG;_LIB;ANSCLOUD_COMMON_STATIC;%(PreprocessorDefinitions) + true + stdcpp17 + $(ProjectDir)include;$(SolutionDir)packages\nlohmann-json\include;%(AdditionalIncludeDirectories) + + + + + Level3 + true + true + true + NDEBUG;_LIB;ANSCLOUD_COMMON_STATIC;%(PreprocessorDefinitions) + true + stdcpp17 + $(ProjectDir)include;$(SolutionDir)packages\nlohmann-json\include;%(AdditionalIncludeDirectories) + + + + + + + + + + + + + diff --git a/anscloud-common/include/anscloud/common/i_message_broker.h b/anscloud-common/include/anscloud/common/i_message_broker.h new file mode 100644 index 0000000..ed909a6 --- /dev/null +++ b/anscloud-common/include/anscloud/common/i_message_broker.h @@ -0,0 +1,125 @@ +#pragma once +//============================================================================= +// IMessageBroker - Abstract interface for message broker operations +// +// Your existing RabbitMQ C++ API implements this interface. +// The SDK (DeviceAgent / GatewayAgent) depends only on this abstraction, +// not on any concrete AMQP library. +//============================================================================= + +#ifndef ANSCLOUD_COMMON_I_MESSAGE_BROKER_H +#define ANSCLOUD_COMMON_I_MESSAGE_BROKER_H + +#include +#include +#include +#include + +namespace anscloud { + +// Forward declare +struct BrokerConfig; + +//----------------------------------------------------------------------------- +// Message received from broker +//----------------------------------------------------------------------------- +struct BrokerMessage { + std::string exchange; + std::string routing_key; + std::string body; // JSON payload + std::string correlation_id; // For RPC matching + std::string reply_to; // Reply queue/routing key + std::string message_id; + std::string content_type; // "application/json" + uint64_t delivery_tag = 0; + bool redelivered = false; +}; + +//----------------------------------------------------------------------------- +// Publish options +//----------------------------------------------------------------------------- +struct PublishOptions { + std::string correlation_id; + std::string reply_to; + std::string message_id; + std::string content_type = "application/json"; + bool persistent = true; + int expiration_ms = 0; // 0 = no expiry (TTL) +}; + +//----------------------------------------------------------------------------- +// Callback types +//----------------------------------------------------------------------------- +using MessageCallback = std::function; +using ConnectionCallback = std::function; +using ErrorCallback = std::function; + +//============================================================================= +// IMessageBroker - Pure virtual interface +// +// Your existing RabbitMQ C++ API creates a class that inherits from this: +// +// class MyRabbitMQBroker : public anscloud::IMessageBroker { +// // ... implement all pure virtual methods using your existing API +// }; +// +//============================================================================= +class IMessageBroker { +public: + virtual ~IMessageBroker() = default; + + //--- Connection lifecycle ------------------------------------------------ + virtual bool connect(const std::string& host, int port, + const std::string& username, const std::string& password, + const std::string& vhost) = 0; + virtual void disconnect() = 0; + virtual bool is_connected() const = 0; + + //--- Exchange & Queue declarations (idempotent) -------------------------- + virtual bool declare_exchange(const std::string& name, + const std::string& type = "topic", + bool durable = true) = 0; + + virtual bool declare_queue(const std::string& name, + bool durable = true, + bool exclusive = false, + bool auto_delete = false) = 0; + + virtual bool bind_queue(const std::string& queue, + const std::string& exchange, + const std::string& routing_key) = 0; + + //--- Publishing ---------------------------------------------------------- + virtual bool publish(const std::string& exchange, + const std::string& routing_key, + const std::string& body, + const PublishOptions& options = {}) = 0; + + //--- Consuming ----------------------------------------------------------- + // Start consuming from a queue. Messages delivered via callback. + // Returns a consumer tag (string) that can be used to cancel. + virtual std::string start_consuming(const std::string& queue, + MessageCallback callback, + bool auto_ack = true) = 0; + + // Cancel a consumer by tag + virtual void cancel_consumer(const std::string& consumer_tag) = 0; + + // Process pending messages (call from event loop). + // timeout_ms: max time to wait for messages. 0 = non-blocking poll. + // Returns number of messages processed. + virtual int process_messages(int timeout_ms = 100) = 0; + + //--- Callbacks ----------------------------------------------------------- + virtual void on_connected(ConnectionCallback cb) = 0; + virtual void on_error(ErrorCallback cb) = 0; +}; + +//============================================================================= +// Factory function type - your application provides this +//============================================================================= +using BrokerFactory = std::function()>; + +} // namespace anscloud + +#endif // ANSCLOUD_COMMON_I_MESSAGE_BROKER_H diff --git a/anscloud-common/include/anscloud/common/json_serializer.h b/anscloud-common/include/anscloud/common/json_serializer.h new file mode 100644 index 0000000..1900e10 --- /dev/null +++ b/anscloud-common/include/anscloud/common/json_serializer.h @@ -0,0 +1,47 @@ +#pragma once +#ifndef ANSCLOUD_COMMON_JSON_SERIALIZER_H +#define ANSCLOUD_COMMON_JSON_SERIALIZER_H + +#include "types.h" +#include +#include + +namespace anscloud { +namespace json { + + // Serialization (struct -> JSON string) + std::string serialize(const Heartbeat& hb); + std::string serialize(const DeviceTelemetry& tel); + std::string serialize(const SystemMetrics& m); + std::string serialize(const Command& cmd); + std::string serialize(const CommandResponse& resp); + std::string serialize(const DeviceEvent& evt); + std::string serialize(const DeviceStatusMessage& msg); + std::string serialize(const CameraInfo& cam); + std::string serialize(const std::vector& cams); + std::string serialize(const DeviceState& state); + std::string serialize(const std::vector& states); + std::string serialize(const CommandResult& result); + + // Deserialization (JSON string -> struct) + Heartbeat deserialize_heartbeat(const std::string& json); + DeviceTelemetry deserialize_telemetry(const std::string& json); + SystemMetrics deserialize_metrics(const std::string& json); + Command deserialize_command(const std::string& json); + CommandResponse deserialize_response(const std::string& json); + DeviceEvent deserialize_event(const std::string& json); + DeviceStatusMessage deserialize_status_message(const std::string& json); + CameraInfo deserialize_camera(const std::string& json); + std::vector deserialize_cameras(const std::string& json); + DeviceState deserialize_device_state(const std::string& json); + std::vector deserialize_device_states(const std::string& json); + CommandResult deserialize_command_result(const std::string& json); + + // Utility + std::string now_iso8601(); + std::string generate_uuid(); + +} // namespace json +} // namespace anscloud + +#endif // ANSCLOUD_COMMON_JSON_SERIALIZER_H diff --git a/anscloud-common/include/anscloud/common/types.h b/anscloud-common/include/anscloud/common/types.h new file mode 100644 index 0000000..5b6867f --- /dev/null +++ b/anscloud-common/include/anscloud/common/types.h @@ -0,0 +1,255 @@ +#pragma once +#ifndef ANSCLOUD_COMMON_TYPES_H +#define ANSCLOUD_COMMON_TYPES_H + +#include +#include +#include +#include +#include + +namespace anscloud { + +//============================================================================= +// Protocol Constants - Exchange & Queue names +//============================================================================= +namespace protocol { + // Exchanges + constexpr const char* EX_DEVICE_TELEMETRY = "ex.device.telemetry"; + constexpr const char* EX_DEVICE_STATUS = "ex.device.status"; + constexpr const char* EX_DEVICE_EVENTS = "ex.device.events"; + constexpr const char* EX_COMMAND = "ex.command"; + constexpr const char* EX_COMMAND_RESPONSE = "ex.command.response"; + constexpr const char* EX_BROADCAST = "ex.broadcast"; + + // Queue name patterns (format with device_id) + inline std::string queue_cmd(const std::string& device_id) { + return "q.cmd." + device_id; + } + inline std::string queue_broadcast(const std::string& device_id) { + return "q.broadcast." + device_id; + } + + // Gateway consumer queues + constexpr const char* Q_TELEMETRY_INGEST = "q.telemetry.ingest"; + constexpr const char* Q_EVENTS_PROCESSOR = "q.events.processor"; + constexpr const char* Q_STATUS_TRACKER = "q.status.tracker"; + constexpr const char* Q_COMMAND_RESPONSES = "q.command.responses"; +} // namespace protocol + +//============================================================================= +// Broker Configuration +//============================================================================= +struct BrokerConfig { + std::string host = "localhost"; + int port = 5672; + std::string username = "guest"; + std::string password = "guest"; + std::string vhost = "/"; + bool use_tls = false; + int heartbeat_sec = 60; + int reconnect_delay_ms = 1000; + int reconnect_max_delay_ms = 30000; + int connection_timeout_ms = 10000; +}; + +//============================================================================= +// Device Identity +//============================================================================= +struct DeviceCredentials { + std::string device_id; // e.g. "AIBOX-001" + std::string device_secret; // Authentication token + std::string tenant_id; // Multi-tenancy isolation +}; + +//============================================================================= +// Device Status +//============================================================================= +enum class DeviceStatus { + Online, + Offline, + Connecting, + Error +}; + +const char* to_string(DeviceStatus status); +DeviceStatus device_status_from_string(const std::string& str); + +struct DeviceStatusMessage { + std::string device_id; + DeviceStatus status = DeviceStatus::Offline; + std::string timestamp; // ISO 8601 + std::string firmware_version; + std::string ansvis_version; + std::string ip_address; +}; + +//============================================================================= +// Telemetry Types +//============================================================================= +struct SystemMetrics { + double cpu_usage_percent = 0.0; + double ram_usage_percent = 0.0; + double ram_used_mb = 0.0; + double ram_total_mb = 0.0; + double gpu_usage_percent = 0.0; + double gpu_memory_used_mb = 0.0; + double gpu_memory_total_mb = 0.0; + double gpu_temp_celsius = 0.0; + double disk_usage_percent = 0.0; + double disk_used_gb = 0.0; + double disk_total_gb = 0.0; + int process_count = 0; + double uptime_hours = 0.0; +}; + +struct CameraInfo { + std::string camera_id; + std::string name; + std::string rtsp_url; + std::string status; // "active", "error", "disabled" + int width = 0; + int height = 0; + double fps = 0.0; + std::string error_message; +}; + +struct InferenceMetrics { + int active_models = 0; + double total_fps = 0.0; + double avg_latency_ms = 0.0; + int detections_per_second = 0; +}; + +struct Heartbeat { + std::string device_id; + std::string timestamp; + uint64_t uptime_seconds = 0; + int active_cameras = 0; + int active_models = 0; +}; + +struct DeviceTelemetry { + std::string device_id; + std::string timestamp; + SystemMetrics system; + std::vector cameras; + InferenceMetrics inference; +}; + +//============================================================================= +// Command Types +//============================================================================= +enum class CommandType { + // System + GetSystemInfo, + GetConfig, + SetConfig, + RestartService, + RebootDevice, + UpdateFirmware, + RunDiagnostics, + // Camera + GetCameraList, + AddCamera, + RemoveCamera, + UpdateCamera, + GetCameraSnapshot, + // AI Model + GetModelList, + LoadModel, + UnloadModel, + // Custom + Custom +}; + +const char* to_string(CommandType type); +CommandType command_type_from_string(const std::string& str); + +enum class CommandStatus { + Pending, + Executing, + Success, + Failed, + Timeout, + Cancelled +}; + +const char* to_string(CommandStatus status); +CommandStatus command_status_from_string(const std::string& str); + +struct Command { + std::string command_id; // UUID + std::string device_id; // Target device + CommandType type = CommandType::Custom; + std::string custom_type; // When type == Custom + std::string params_json; // JSON parameters + std::string correlation_id; // For RPC matching + std::string timestamp; + int timeout_seconds = 30; +}; + +struct CommandResponse { + std::string command_id; + std::string device_id; + CommandStatus status = CommandStatus::Pending; + std::string result_json; // JSON result payload + std::string error_message; + std::string correlation_id; + std::string timestamp; + int execution_time_ms = 0; +}; + +//============================================================================= +// Event Types +//============================================================================= +enum class EventType { + Alert, + DetectionLPR, + DetectionFace, + DetectionObject, + CameraStatusChanged, + ModelStatusChanged, + SystemAlert, + Custom +}; + +const char* to_string(EventType type); +EventType event_type_from_string(const std::string& str); + +struct DeviceEvent { + std::string event_id; // UUID + std::string device_id; + EventType type = EventType::Custom; + std::string custom_type; + std::string data_json; // Event payload + std::string camera_id; // Optional: related camera + std::string timestamp; + std::string severity; // "info", "warning", "critical" +}; + +//============================================================================= +// Gateway-side types +//============================================================================= +struct CommandResult { + bool success = false; + CommandStatus status = CommandStatus::Pending; + std::string result_json; + std::string error_message; + int execution_time_ms = 0; +}; + +struct DeviceState { + std::string device_id; + DeviceStatus status = DeviceStatus::Offline; + std::string last_seen; // ISO 8601 + std::string ip_address; + std::string firmware_version; + std::string ansvis_version; + SystemMetrics last_metrics; + int camera_count = 0; +}; + +} // namespace anscloud + +#endif // ANSCLOUD_COMMON_TYPES_H diff --git a/anscloud-device/anscloud-device.vcxproj b/anscloud-device/anscloud-device.vcxproj new file mode 100644 index 0000000..b197837 --- /dev/null +++ b/anscloud-device/anscloud-device.vcxproj @@ -0,0 +1,86 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 17.0 + {B1A2C3D4-0001-4000-8000-000000000002} + ansclouddevice + 10.0 + anscloud-device + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + Unicode + true + + + + + + + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)obj\$(ProjectName)\$(Configuration)\ + + + + Level3 + true + _DEBUG;_WINDOWS;_USRDLL;ANSCLOUD_DEVICE_EXPORTS;%(PreprocessorDefinitions) + true + stdcpp17 + $(ProjectDir)include;$(SolutionDir)anscloud-common\include;$(SolutionDir)packages\nlohmann-json\include;%(AdditionalIncludeDirectories) + + + anscloud-common.lib;%(AdditionalDependencies) + $(SolutionDir)bin\$(Configuration)\;%(AdditionalLibraryDirectories) + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;_USRDLL;ANSCLOUD_DEVICE_EXPORTS;%(PreprocessorDefinitions) + true + stdcpp17 + $(ProjectDir)include;$(SolutionDir)anscloud-common\include;$(SolutionDir)packages\nlohmann-json\include;%(AdditionalIncludeDirectories) + + + anscloud-common.lib;%(AdditionalDependencies) + $(SolutionDir)bin\$(Configuration)\;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + {B1A2C3D4-0001-4000-8000-000000000001} + + + + diff --git a/anscloud-device/include/anscloud/device/device_agent.h b/anscloud-device/include/anscloud/device/device_agent.h new file mode 100644 index 0000000..e4ef4ce --- /dev/null +++ b/anscloud-device/include/anscloud/device/device_agent.h @@ -0,0 +1,123 @@ +#pragma once +#ifndef ANSCLOUD_DEVICE_AGENT_H +#define ANSCLOUD_DEVICE_AGENT_H + +#include +#include +#include +#include +#include +#include + +#ifdef ANSCLOUD_DEVICE_EXPORTS + #define ANSCLOUD_DEVICE_API __declspec(dllexport) +#else + #define ANSCLOUD_DEVICE_API __declspec(dllimport) +#endif + +namespace anscloud { + +//============================================================================= +// DeviceAgent Configuration +//============================================================================= +struct DeviceAgentConfig { + BrokerConfig broker; + DeviceCredentials credentials; + + // Telemetry intervals (seconds) + int heartbeat_interval_sec = 30; + int metrics_interval_sec = 60; + int snapshot_interval_sec = 0; // 0 = disabled + + // Device info + std::string firmware_version; + std::string ansvis_version; + std::string ip_address; + std::string local_api_endpoint; // e.g. "http://127.0.0.1:8080" +}; + +//============================================================================= +// Application callback types - your app implements these +//============================================================================= +using CommandHandler = std::function; +using MetricsProvider = std::function; +using CameraListProvider = std::function()>; +using InferenceProvider = std::function; +using SnapshotProvider = std::function; // returns base64 JPEG + +//============================================================================= +// Agent runtime status +//============================================================================= +struct AgentStatus { + bool connected = false; + bool running = false; + uint64_t uptime_seconds = 0; + uint64_t commands_received = 0; + uint64_t commands_executed = 0; + uint64_t heartbeats_sent = 0; + uint64_t telemetry_sent = 0; + uint64_t events_published = 0; + uint64_t reconnect_count = 0; + std::string last_error; +}; + +//============================================================================= +// DeviceAgent - AIBOX cloud agent +// +// Inject your existing RabbitMQ C++ API via BrokerFactory: +// +// DeviceAgent agent([](){ return std::make_unique(); }); +// agent.configure(config); +// agent.set_command_handler(...); +// agent.start(); // non-blocking +// +//============================================================================= +class ANSCLOUD_DEVICE_API DeviceAgent { +public: + // Construct with broker factory (your RabbitMQ API) + explicit DeviceAgent(BrokerFactory factory); + ~DeviceAgent(); + + // Non-copyable, movable + DeviceAgent(const DeviceAgent&) = delete; + DeviceAgent& operator=(const DeviceAgent&) = delete; + DeviceAgent(DeviceAgent&&) noexcept; + DeviceAgent& operator=(DeviceAgent&&) noexcept; + + //--- Configuration (call before start) ----------------------------------- + void configure(const DeviceAgentConfig& config); + + void set_command_handler(CommandHandler handler); + void set_metrics_provider(MetricsProvider provider); + void set_camera_list_provider(CameraListProvider provider); + void set_inference_provider(InferenceProvider provider); + void set_snapshot_provider(SnapshotProvider provider); + + //--- Lifecycle (non-blocking) -------------------------------------------- + bool start(); // Connects, declares queues, starts background threads + void stop(); // Publishes offline, disconnects, stops threads + + bool is_running() const; + bool is_connected() const; + + //--- Manual publishing --------------------------------------------------- + void publish_event(const DeviceEvent& event); + void publish_telemetry(const DeviceTelemetry& telemetry); + void send_heartbeat_now(); + void send_metrics_now(); + + //--- Status & callbacks -------------------------------------------------- + AgentStatus get_status() const; + + void on_connection_changed(ConnectionCallback cb); + void on_error(ErrorCallback cb); + void on_broadcast(std::function cb); + +private: + class Impl; + std::unique_ptr m_impl; +}; + +} // namespace anscloud + +#endif // ANSCLOUD_DEVICE_AGENT_H diff --git a/anscloud-device/include/anscloud/device/device_agent_c.h b/anscloud-device/include/anscloud/device/device_agent_c.h new file mode 100644 index 0000000..1061cf0 --- /dev/null +++ b/anscloud-device/include/anscloud/device/device_agent_c.h @@ -0,0 +1,114 @@ +#pragma once +#ifndef ANSCLOUD_DEVICE_AGENT_C_H +#define ANSCLOUD_DEVICE_AGENT_C_H + +#ifdef ANSCLOUD_DEVICE_EXPORTS + #define ANSCLOUD_DEVICE_CAPI __declspec(dllexport) +#else + #define ANSCLOUD_DEVICE_CAPI __declspec(dllimport) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +//============================================================================= +// Opaque handle +//============================================================================= +typedef struct AnsDeviceAgent* AnsDeviceAgentHandle; + +//============================================================================= +// C-compatible config structs +//============================================================================= +typedef struct { + const char* host; + int port; + const char* username; + const char* password; + const char* vhost; + int use_tls; +} AnsDeviceBrokerConfig; + +typedef struct { + const char* device_id; + const char* device_secret; + const char* tenant_id; +} AnsDeviceCredentials; + +typedef struct { + AnsDeviceBrokerConfig broker; + AnsDeviceCredentials credentials; + int heartbeat_interval_sec; + int metrics_interval_sec; + const char* firmware_version; + const char* ansvis_version; + const char* ip_address; +} AnsDeviceConfig; + +//============================================================================= +// Callback types +//============================================================================= +// command_json: JSON string of Command. Return: JSON string of CommandResponse. +// Caller must free returned string with ansdevice_free_string(). +typedef const char* (*AnsDeviceCommandHandlerFn)(const char* command_json, void* user_data); + +// metrics_json: caller must free returned JSON string +typedef const char* (*AnsDeviceMetricsProviderFn)(void* user_data); + +typedef void (*AnsDeviceConnectionCallbackFn)(int connected, void* user_data); +typedef void (*AnsDeviceErrorCallbackFn)(const char* error, void* user_data); +typedef void (*AnsDeviceBroadcastCallbackFn)(const char* message, void* user_data); + +//============================================================================= +// Lifecycle +//============================================================================= +// broker_factory_fn: function pointer that creates your IMessageBroker. +// For simplicity in C API, pass NULL to use a default (if you register one). +ANSCLOUD_DEVICE_CAPI AnsDeviceAgentHandle ansdevice_create(void); +ANSCLOUD_DEVICE_CAPI void ansdevice_destroy(AnsDeviceAgentHandle handle); + +ANSCLOUD_DEVICE_CAPI int ansdevice_configure(AnsDeviceAgentHandle handle, const AnsDeviceConfig* config); +ANSCLOUD_DEVICE_CAPI int ansdevice_start(AnsDeviceAgentHandle handle); +ANSCLOUD_DEVICE_CAPI void ansdevice_stop(AnsDeviceAgentHandle handle); +ANSCLOUD_DEVICE_CAPI int ansdevice_is_running(AnsDeviceAgentHandle handle); +ANSCLOUD_DEVICE_CAPI int ansdevice_is_connected(AnsDeviceAgentHandle handle); + +//============================================================================= +// Handlers & providers +//============================================================================= +ANSCLOUD_DEVICE_CAPI void ansdevice_set_command_handler(AnsDeviceAgentHandle handle, + AnsDeviceCommandHandlerFn fn, void* user_data); +ANSCLOUD_DEVICE_CAPI void ansdevice_set_metrics_provider(AnsDeviceAgentHandle handle, + AnsDeviceMetricsProviderFn fn, void* user_data); + +//============================================================================= +// Publishing +//============================================================================= +ANSCLOUD_DEVICE_CAPI int ansdevice_publish_event(AnsDeviceAgentHandle handle, const char* event_json); +ANSCLOUD_DEVICE_CAPI void ansdevice_send_heartbeat(AnsDeviceAgentHandle handle); +ANSCLOUD_DEVICE_CAPI void ansdevice_send_metrics(AnsDeviceAgentHandle handle); + +//============================================================================= +// Callbacks +//============================================================================= +ANSCLOUD_DEVICE_CAPI void ansdevice_on_connection_changed(AnsDeviceAgentHandle handle, + AnsDeviceConnectionCallbackFn fn, void* user_data); +ANSCLOUD_DEVICE_CAPI void ansdevice_on_error(AnsDeviceAgentHandle handle, + AnsDeviceErrorCallbackFn fn, void* user_data); +ANSCLOUD_DEVICE_CAPI void ansdevice_on_broadcast(AnsDeviceAgentHandle handle, + AnsDeviceBroadcastCallbackFn fn, void* user_data); + +//============================================================================= +// Status & memory +//============================================================================= +// Returns JSON string. Caller must free with ansdevice_free_string(). +ANSCLOUD_DEVICE_CAPI const char* ansdevice_get_status(AnsDeviceAgentHandle handle); + +ANSCLOUD_DEVICE_CAPI void ansdevice_free_string(const char* str); +ANSCLOUD_DEVICE_CAPI const char* ansdevice_version(void); + +#ifdef __cplusplus +} +#endif + +#endif // ANSCLOUD_DEVICE_AGENT_C_H diff --git a/anscloud-gateway/anscloud-gateway.vcxproj b/anscloud-gateway/anscloud-gateway.vcxproj new file mode 100644 index 0000000..3483548 --- /dev/null +++ b/anscloud-gateway/anscloud-gateway.vcxproj @@ -0,0 +1,86 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 17.0 + {B1A2C3D4-0001-4000-8000-000000000003} + anscloudgateway + 10.0 + anscloud-gateway + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + Unicode + true + + + + + + + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)obj\$(ProjectName)\$(Configuration)\ + + + + Level3 + true + _DEBUG;_WINDOWS;_USRDLL;ANSCLOUD_GATEWAY_EXPORTS;%(PreprocessorDefinitions) + true + stdcpp17 + $(ProjectDir)include;$(SolutionDir)anscloud-common\include;$(SolutionDir)packages\nlohmann-json\include;%(AdditionalIncludeDirectories) + + + anscloud-common.lib;%(AdditionalDependencies) + $(SolutionDir)bin\$(Configuration)\;%(AdditionalLibraryDirectories) + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;_USRDLL;ANSCLOUD_GATEWAY_EXPORTS;%(PreprocessorDefinitions) + true + stdcpp17 + $(ProjectDir)include;$(SolutionDir)anscloud-common\include;$(SolutionDir)packages\nlohmann-json\include;%(AdditionalIncludeDirectories) + + + anscloud-common.lib;%(AdditionalDependencies) + $(SolutionDir)bin\$(Configuration)\;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + {B1A2C3D4-0001-4000-8000-000000000001} + + + + diff --git a/anscloud-gateway/include/anscloud/gateway/gateway_agent.h b/anscloud-gateway/include/anscloud/gateway/gateway_agent.h new file mode 100644 index 0000000..d99bd0c --- /dev/null +++ b/anscloud-gateway/include/anscloud/gateway/gateway_agent.h @@ -0,0 +1,145 @@ +#pragma once +#ifndef ANSCLOUD_GATEWAY_AGENT_H +#define ANSCLOUD_GATEWAY_AGENT_H + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ANSCLOUD_GATEWAY_EXPORTS + #define ANSCLOUD_GATEWAY_API __declspec(dllexport) +#else + #define ANSCLOUD_GATEWAY_API __declspec(dllimport) +#endif + +namespace anscloud { + +//============================================================================= +// Gateway Configuration +//============================================================================= +struct GatewayCredentials { + std::string username; + std::string password; + std::string vhost = "/"; +}; + +struct GatewayAgentConfig { + BrokerConfig broker; + GatewayCredentials credentials; + int device_timeout_seconds = 90; + int default_command_timeout_seconds = 30; + int max_pending_commands = 1000; +}; + +struct GatewayStats { + uint64_t commands_sent = 0; + uint64_t commands_responded = 0; + uint64_t commands_timed_out = 0; + uint64_t telemetry_received = 0; + uint64_t events_received = 0; + uint64_t heartbeats_received = 0; + int devices_online = 0; + int devices_total = 0; + int pending_commands = 0; + uint64_t uptime_seconds = 0; +}; + +struct BatchResult { + std::vector> results; // device_id -> result + int succeeded = 0; + int failed = 0; + int timed_out = 0; +}; + +//============================================================================= +// GatewayAgent - CMS gateway for consuming device messages & sending commands +// +// GatewayAgent gw([](){ return std::make_unique(); }); +// gw.configure(config); +// gw.on_telemetry([](const DeviceTelemetry& t){ /* store */ }); +// gw.on_event([](const DeviceEvent& e){ /* process */ }); +// gw.start(); +// +// auto result = gw.send_command("AIBOX-001", CommandType::GetConfig); +// +//============================================================================= +class ANSCLOUD_GATEWAY_API GatewayAgent { +public: + explicit GatewayAgent(BrokerFactory factory); + ~GatewayAgent(); + + GatewayAgent(const GatewayAgent&) = delete; + GatewayAgent& operator=(const GatewayAgent&) = delete; + GatewayAgent(GatewayAgent&&) noexcept; + GatewayAgent& operator=(GatewayAgent&&) noexcept; + + //--- Configuration ------------------------------------------------------- + void configure(const GatewayAgentConfig& config); + + //--- Lifecycle ----------------------------------------------------------- + bool start(); + void stop(); + bool is_running() const; + bool is_connected() const; + + //--- Commands (synchronous RPC) ------------------------------------------ + CommandResult send_command(const std::string& device_id, + CommandType type, + const std::string& params_json = "{}", + int timeout_seconds = 0); // 0 = use default + + std::future send_command_async(const std::string& device_id, + CommandType type, + const std::string& params_json = "{}", + int timeout_seconds = 0); + + CommandResult send_custom_command(const std::string& device_id, + const std::string& custom_type, + const std::string& params_json = "{}", + int timeout_seconds = 0); + + void send_command_fire_and_forget(const std::string& device_id, + CommandType type, + const std::string& params_json = "{}"); + + BatchResult send_batch_command(const std::vector& device_ids, + CommandType type, + const std::string& params_json = "{}", + int timeout_seconds = 0); + + void send_broadcast(const std::string& message); + + //--- Device tracking ----------------------------------------------------- + std::vector get_all_devices() const; + DeviceState get_device(const std::string& device_id) const; + std::vector get_online_devices() const; + bool is_device_online(const std::string& device_id) const; + + //--- Callbacks ----------------------------------------------------------- + using TelemetryCallback = std::function; + using HeartbeatCallback = std::function; + using EventCallback = std::function; + using DeviceStatusCallback = std::function; + + void on_telemetry(TelemetryCallback cb); + void on_heartbeat(HeartbeatCallback cb); + void on_event(EventCallback cb); + void on_device_status_changed(DeviceStatusCallback cb); + void on_connection_changed(ConnectionCallback cb); + void on_error(ErrorCallback cb); + + //--- Stats --------------------------------------------------------------- + GatewayStats get_stats() const; + +private: + class Impl; + std::unique_ptr m_impl; +}; + +} // namespace anscloud + +#endif // ANSCLOUD_GATEWAY_AGENT_H diff --git a/anscloud-gateway/include/anscloud/gateway/gateway_agent_c.h b/anscloud-gateway/include/anscloud/gateway/gateway_agent_c.h new file mode 100644 index 0000000..60e5395 --- /dev/null +++ b/anscloud-gateway/include/anscloud/gateway/gateway_agent_c.h @@ -0,0 +1,57 @@ +#pragma once +#ifndef ANSCLOUD_GATEWAY_AGENT_C_H +#define ANSCLOUD_GATEWAY_AGENT_C_H + +#ifdef ANSCLOUD_GATEWAY_EXPORTS + #define ANSCLOUD_GATEWAY_CAPI __declspec(dllexport) +#else + #define ANSCLOUD_GATEWAY_CAPI __declspec(dllimport) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct AnsGatewayAgent* AnsGatewayAgentHandle; + +typedef struct { + const char* host; + int port; + const char* username; + const char* password; + const char* vhost; + int use_tls; +} AnsGatewayBrokerConfig; + +typedef struct { + const char* username; + const char* password; + const char* vhost; +} AnsGatewayCredentials; + +typedef struct { + AnsGatewayBrokerConfig broker; + AnsGatewayCredentials credentials; + int device_timeout_seconds; + int default_command_timeout_seconds; + int max_pending_commands; +} AnsGatewayConfig; + +// Callbacks - all receive JSON strings +typedef void (*AnsGatewayTelemetryCallbackFn)(const char* telemetry_json, void* user_data); +typedef void (*AnsGatewayHeartbeatCallbackFn)(const char* heartbeat_json, void* user_data); +typedef void (*AnsGatewayEventCallbackFn)(const char* event_json, void* user_data); +typedef void (*AnsGatewayDeviceStatusCallbackFn)(const char* device_id, const char* status, void* user_data); +typedef void (*AnsGatewayConnectionCallbackFn)(int connected, void* user_data); +typedef void (*AnsGatewayErrorCallbackFn)(const char* error, void* user_data); + +//============================================================================= +// Lifecycle +//============================================================================= +ANSCLOUD_GATEWAY_CAPI AnsGatewayAgentHandle ansgateway_create(void); +ANSCLOUD_GATEWAY_CAPI void ansgateway_destroy(AnsGatewayAgentHandle handle); +ANSCLOUD_GATEWAY_CAPI int ansgateway_configure(AnsGatewayAgentHandle handle, const AnsGatewayConfig* config); +ANSCLOUD_GATEWAY_CAPI int ansgateway_start(AnsGatewayAgentHandle handle); +ANSCLOUD_GATEWAY_CAPI void ansgateway_stop(AnsGatewayAgentHandle handle); +ANSCLOUD_GATEWAY_CAPI int ansgateway_is_running(AnsGatewayAgentHandle handle); +ANSCLOUD_GATEWAY_CAPI int ansgateway_is_connected(AnsGatewayAgentHandle handle); diff --git a/architecture-diagram.html b/architecture-diagram.html new file mode 100644 index 0000000..3b00777 --- /dev/null +++ b/architecture-diagram.html @@ -0,0 +1,636 @@ + + + + + +ANSCloud Integration Architecture + + + + +
+

ANSCloud — Partner Integration Architecture

+
3-Tier Integration Strategy: REST API → AMQP Protocol → C++ FFI Library
+
+ + + + +
+
+
+ TIER 1 + REST / WebSocket API Gateway + ★ Recommended for most partners +
+
+
+
🌐
+
Partner CMS
+
Any language
+
+ Node.js + Python + Go + Java + C# + PHP + Ruby +
+
+
+
──────►
+
HTTPS / WSS
+
+
+
🛡️
+
API Gateway
+
Cloud B
Auth + Rate Limit
+
+
+
──────►
+
AMQP internal
+
+
+
📨
+
RabbitMQ
+
Cloud B
Exchanges + Queues
+
+
+
──────►
+
AMQP TLS
+
+
+
📷
+
AIBOX
+
anscloud-device
C++ + Your RabbitMQ API
+
+
+
+ + + + +
+
+ TIER 2 + Native AMQP Protocol (Direct Broker Access) + Advanced integrators, on-prem +
+
+
+
+
Partner CMS
+
Native AMQP lib
+
+ amqplib + pika + amqp091-go + Spring AMQP +
+
+
+
════════════════════════►
+
AMQP 0-9-1 (direct, same protocol)
+
+
+
📨
+
RabbitMQ
+
Cloud B
Partner vhost
+
+
+
──────►
+
AMQP TLS
+
+
+
📷
+
AIBOX
+
anscloud-device
C++ + Your RabbitMQ API
+
+
+
+ + + + +
+
+ TIER 3 + C++ Gateway Library via FFI + Your CMS, embedded, C/C++ partners +
+
+
+
🔧
+
Partner CMS
+
Links .dll / .so
+
+ cgo + ctypes + P/Invoke + ffi-napi + JNI +
+
+
+
──────►
+
C API (FFI)
+
+
+
📦
+
anscloud-gateway
+
C++ lib (CMSCore)
gateway_agent_c.h
+
+
+
──────►
+
Your RabbitMQ API
+
+
+
📨
+
RabbitMQ
+
Cloud B
+
+
+
──────►
+
AMQP TLS
+
+
+
📷
+
AIBOX
+
anscloud-device
C++ + Your RabbitMQ API
+
+
+
+
+ + + + +
+

☁️ Cloud B — Messaging Platform (You Own)

+
+
+

API Gateway Layer

+
REST endpoints for commands, status, telemetry
+
WebSocket for real-time event streaming
+
Webhook push to partner URLs
+
API key auth + JWT per tenant
+
Rate limiting per partner tier
+
Serves Tier 1 partners
+
+
+

RabbitMQ Broker

+
ex.command → direct → device queues
+
ex.device.telemetry → topic → ingest
+
ex.device.events → topic → processor
+
ex.device.status → topic → tracker
+
ex.broadcast → fanout → all devices
+
vhost isolation per tenant
+
+
+

AIBOX Devices

+
anscloud-device (C++ static lib)
+
Uses YOUR existing RabbitMQ C++ API
+
IMessageBroker adapter pattern
+
Auto boot → connect → heartbeat
+
Publishes telemetry, events, status
+
Receives & executes commands
+
+
+
+ + + + +
+

Command RPC Flow (Tier 1 Example)

+
+ Partner POST /api/v1/devices/AIBOX-001/commands {"command":"get_config"}
+
+ API GW generates cmd_id + correlation_idpublish to ex.command routing_key=AIBOX-001
+
+ RabbitMQ routesq.cmd.AIBOX-001
+
+ AIBOX consumesexecutes locallypublishes responseex.command.response
+
+ API GW matches correlation_idresolves pending request
+
+ Partner receives HTTP 200 {"status":"success","result":{"version":"3.2.0","cameras":3}}
+
+ ── Partner sees a simple REST call. RabbitMQ is completely hidden. ── +
+
+ + + + +
+

Tier Comparison

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Language SupportSetup EffortLatencyPartner NeedsYou ControlBest For
Tier 1 — REST APIAny Minutes ~50ms addedAPI key + docsAuth, rate limits, versioning SaaS partners, web/mobile
Tier 2 — AMQP DirectHas AMQP libHoursLowest Protocol guide + credentialsvhost isolationLarge integrators, on-prem
Tier 3 — C++ FFIHas FFI supportDaysLowDLL/so + C headersFull protocol encapsulatedYour CMS, embedded, C++ devs
+
+ + + diff --git a/test-device/test-device.vcxproj b/test-device/test-device.vcxproj new file mode 100644 index 0000000..3d2de5a --- /dev/null +++ b/test-device/test-device.vcxproj @@ -0,0 +1,86 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 17.0 + {B1A2C3D4-0001-4000-8000-000000000004} + testdevice + 10.0 + test-device + + + + Application + true + v143 + Unicode + + + Application + false + v143 + Unicode + true + + + + + + + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)obj\$(ProjectName)\$(Configuration)\ + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + $(SolutionDir)anscloud-common\include;$(SolutionDir)anscloud-device\include;$(SolutionDir)packages\nlohmann-json\include;%(AdditionalIncludeDirectories) + + + Console + anscloud-common.lib;anscloud-device.lib;%(AdditionalDependencies) + $(SolutionDir)bin\$(Configuration)\;%(AdditionalLibraryDirectories) + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + $(SolutionDir)anscloud-common\include;$(SolutionDir)anscloud-device\include;$(SolutionDir)packages\nlohmann-json\include;%(AdditionalIncludeDirectories) + + + Console + anscloud-common.lib;anscloud-device.lib;%(AdditionalDependencies) + $(SolutionDir)bin\$(Configuration)\;%(AdditionalLibraryDirectories) + + + + + + + + {B1A2C3D4-0001-4000-8000-000000000001} + + + {B1A2C3D4-0001-4000-8000-000000000002} + + + + diff --git a/test-gateway/test-gateway.vcxproj b/test-gateway/test-gateway.vcxproj new file mode 100644 index 0000000..ad37958 --- /dev/null +++ b/test-gateway/test-gateway.vcxproj @@ -0,0 +1,86 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 17.0 + {B1A2C3D4-0001-4000-8000-000000000005} + testgateway + 10.0 + test-gateway + + + + Application + true + v143 + Unicode + + + Application + false + v143 + Unicode + true + + + + + + + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)obj\$(ProjectName)\$(Configuration)\ + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + $(SolutionDir)anscloud-common\include;$(SolutionDir)anscloud-gateway\include;$(SolutionDir)packages\nlohmann-json\include;%(AdditionalIncludeDirectories) + + + Console + anscloud-common.lib;anscloud-gateway.lib;%(AdditionalDependencies) + $(SolutionDir)bin\$(Configuration)\;%(AdditionalLibraryDirectories) + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + $(SolutionDir)anscloud-common\include;$(SolutionDir)anscloud-gateway\include;$(SolutionDir)packages\nlohmann-json\include;%(AdditionalIncludeDirectories) + + + Console + anscloud-common.lib;anscloud-gateway.lib;%(AdditionalDependencies) + $(SolutionDir)bin\$(Configuration)\;%(AdditionalLibraryDirectories) + + + + + + + + {B1A2C3D4-0001-4000-8000-000000000001} + + + {B1A2C3D4-0001-4000-8000-000000000003} + + + +