bcms 0.1.12

Bluetooth Client Manager Service for IoT device management
Documentation
#!/usr/bin/env python3
"""
Mock BCMS Backend Server

Emulates the backend API for testing BCMS data submissions.
Accepts any Bearer token for authentication.
"""

from http.server import HTTPServer, BaseHTTPRequestHandler
import json
from datetime import datetime, timezone
import uuid

# In-memory storage
devices = {}  # hardware_id -> device_info
last_submissions = {}  # device_id -> timestamp


def now_iso():
    return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")


class MockBackendHandler(BaseHTTPRequestHandler):
    def log_request_info(self, body=None):
        print(f"\n{'='*60}")
        print(f"[{datetime.now().isoformat()}] {self.command} {self.path}")
        auth = self.headers.get("Authorization", "None")
        print(f"Auth: {auth[:50]}..." if len(auth) > 50 else f"Auth: {auth}")
        if body:
            print(f"Body:\n{json.dumps(body, indent=2)}")

    def send_json(self, status, data):
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(data).encode())

    def read_json(self):
        length = int(self.headers.get("Content-Length", 0))
        if length:
            return json.loads(self.rfile.read(length))
        return {}

    def do_GET(self):
        # GET /api/iot-devices/{id}/last-data-submission
        if self.path.startswith("/api/iot-devices/") and self.path.endswith("/last-data-submission"):
            device_id = self.path.split("/")[3]
            self.log_request_info()
            ts = last_submissions.get(device_id)
            print(f"-> Last submission for {device_id}: {ts}")
            self.send_json(200, {
                "timestamp": ts,
                "createdAt": now_iso() if ts else None
            })
        else:
            self.send_json(404, {"error": "Not found"})

    def do_POST(self):
        body = self.read_json()
        self.log_request_info(body)

        # POST /api/iot-devices/data/submit
        if self.path == "/api/iot-devices/data/submit":
            data_entries = body.get("data", [])
            total_points = 0
            results = []
            for entry in data_entries:
                device_id = entry.get("iotDeviceId")
                data_type = entry.get("dataType")
                data_points = entry.get("data", [])
                total_points += len(data_points)
                print(f"  -> Device {device_id[:8]}...: {len(data_points)} {data_type} points")
                for dp in data_points:
                    ts = datetime.fromtimestamp(dp["timestamp"]).isoformat()
                    print(f"     [{ts}] {dp['data']}")
                    results.append({
                        "id": str(uuid.uuid4()),
                        "timestamp": dp["timestamp"],
                        "dataType": data_type,
                        "data": dp["data"],
                        "createdAt": now_iso()
                    })
                # Update last submission timestamp
                if data_points and device_id:
                    last_submissions[device_id] = max(dp["timestamp"] for dp in data_points)
            print(f"-> Total: {total_points} data points received")
            self.send_json(200, results)

        # POST /api/iot-devices/exists
        elif self.path == "/api/iot-devices/exists":
            hw_id = body.get("hardwareIdentifier", "")
            exists = hw_id in devices
            device_info = devices.get(hw_id, {})
            print(f"-> Device {hw_id} exists: {exists}")

            response = {"exists": exists}
            if exists:
                response.update({
                    "rememberDevice": True,
                    "id": device_info.get("id"),
                    "model": device_info.get("model"),
                    "trustLevel": "known",
                    "connectionType": device_info.get("connectionType"),
                    "supportedDataTypes": device_info.get("supportedDataTypes", [])
                })
            self.send_json(200, response)

        # POST /api/iot-devices (register)
        elif self.path == "/api/iot-devices":
            hw_id = body.get("hardwareIdentifier", "")
            now = now_iso()

            if hw_id not in devices:
                device_id = str(uuid.uuid4())
                devices[hw_id] = {
                    "id": device_id,
                    "createdAt": now,
                    "modifiedAt": now,
                    "revision": 1,
                    "hardwareIdentifier": hw_id,
                    "model": body.get("model", "generic"),
                    "trustLevel": "unknown",
                    "connectionType": body.get("connectionType", "unknown"),
                    "connectionMeta": body.get("connectionMeta", {}),
                    "connectionSecret": None,
                    "supportedDataTypes": body.get("supportedDataTypes", []),
                    "allowMultipleDeviceRelationships": False,
                    "devices": [],
                    "assignedUser": None,
                    "createdByDevice": None
                }
                print(f"-> Registered new device: {device_id}")
            else:
                print(f"-> Device already exists: {devices[hw_id]['id']}")

            self.send_json(200, devices[hw_id])

        else:
            self.send_json(404, {"error": "Not found"})


if __name__ == "__main__":
    port = 8080
    server = HTTPServer(("0.0.0.0", port), MockBackendHandler)
    print(f"Mock BCMS Backend running on http://0.0.0.0:{port}")
    print("Endpoints:")
    print("  POST /api/iot-devices/data/submit  - Submit IoT data")
    print("  POST /api/iot-devices/exists       - Check device existence")
    print("  POST /api/iot-devices              - Register device")
    print("  GET  /api/iot-devices/{id}/last-data-submission")
    print("\nWaiting for requests...")
    server.serve_forever()