项目 / 滨江智慧工地 · 一期
正在连接实时服务管
CONSTRUCTION INTELLIGENCE

设备接入指南

从第一条连接到第一条数据:按步骤将设备接入工地监控平台。

开放接入 · 协议 v1

使用 TCP 上报,让设备出现在监控后台

下载 C# 客户端源码
01 连接→02 注册→03 心跳→04 上报→05 查看结果
TCP 端口:9000 · 监听地址:0.0.0.0 · UTF-8 无 BOM · JSON + LF · 单帧上限 65536 字节
网页的 HTTP 地址仅用于打开后台;设备应连接服务器 IP 与 TCP 端口,不发送 HTTP 请求。

01 / 连接与注册

本机使用 127.0.0.1:9000;其他电脑使用服务器局域网 IP,例如 192.168.1.10:9000。0.0.0.0 是监听配置,不是客户端连接地址。确保系统防火墙允许此 TCP 端口。

建立长连接后先发送注册报文,收到 ok: true 后再上报。新编号会自动创建设备;建议使用 EXAMPLE-001,避开模拟器占用的 TC-001 至 TC-006。

同编号新连接会替换旧连接;每条连接只允许绑定一个设备。断开连接或 30 秒内无有效报文时判定离线。默认每 10 秒发送心跳,每 3 秒上报数据;有效上报同样刷新在线时间。

02 / 可复制报文

下列报文均为单行 JSON。复制后在末尾追加实际换行 LF(0x0A),而不是文本字符 \n。支持 CRLF;JSON 不应跨行美化后发送。

注册

{"type":"register","deviceId":"EXAMPLE-001"}

心跳

{"type":"heartbeat","deviceId":"EXAMPLE-001"}

遥测上报

{"type":"telemetry","deviceId":"EXAMPLE-001","messageId":"sample-001","collectedAt":"2026-10-14T17:10:24.5760255+00:00","motorTemperature":55.2,"ambientTemperature":26.5,"load":3.8,"ratedLoad":8,"hookHeight":38.5,"radius":24,"rotation":120,"windSpeed":4.2,"workState":"running"}

每条新采集记录使用新的 messageId;网络重试可复用原编号,服务端按设备与报文编号去重。示例采集时间为页面生成时刻,实际设备请使用带 Z 或时区偏移的 ISO 8601 时间。

成功应答(上报)

{"type":"ack","ok":true,"code":"ok","messageId":"sample-001","message":"数据已保存"}

失败应答(未注册)

{"type":"ack","ok":false,"code":"not_registered","messageId":"sample-001","message":"请先注册,设备编号必须与连接一致"}

注册和心跳成功返回 code: ok、message: 接收成功;重复数据返回 ok: true, code: duplicate,不重复入库。其他错误码:invalid_json、invalid_utf8、validation、frame_too_large。超限报文应答后关闭连接,其余错误可修正后重发;非法报文不会刷新心跳。

03 / 字段与单位

字段适用报文类型 / 单位要求
type全部string必填:register / heartbeat / telemetry
deviceId全部string必填:1–64 位字母、数字、下划线、连字符
messageIdtelemetrystring必填:1–128 字符,设备内唯一
collectedAttelemetryISO 8601必填:带 Z 或时区偏移;入库存储 UTC,界面北京时间
motorTemperaturetelemetrynumber / °C必填:有限数字;电机温度,超过 80°C 告警
ambientTemperaturetelemetrynumber / °C必填:有限数字;环境温度
loadtelemetrynumber / t必填:有限数字;实际载荷 ≥ 0,超过 ratedLoad 告警
ratedLoadtelemetrynumber / t必填:有限数字;额定载荷 > 0
hookHeighttelemetrynumber / m必填:有限数字;吊钩高度 ≥ 0
radiustelemetrynumber / m必填:有限数字;工作幅度 ≥ 0
rotationtelemetrynumber / °必填:有限数字;回转角度 0–360
windSpeedtelemetrynumber / m/s必填:有限数字;风速 ≥ 0,超过 12m/s 告警
workStatetelemetrystring必填:idle(待机)、running(运行)、fault(故障并告警)

04 / 可运行的 C# 示例

安装 .NET 10 SDK,下载源码 ZIP 并解压,在 CraneClient 目录运行以下命令。参数依次为主机、端口、设备编号、上报间隔、心跳间隔;Ctrl+C 停止。连接中断后客户端自动重试。

启动命令

dotnet run -- 127.0.0.1 9000 EXAMPLE-001 3 10

Program.cs(与下载文件一致)

using System.Net.Sockets;
using System.Text;
using System.Text.Json;

// dotnet run -- 127.0.0.1 9000 EXAMPLE-001 [reportSeconds] [heartbeatSeconds]
var host = args.ElementAtOrDefault(0) ?? "127.0.0.1";
var port = int.Parse(args.ElementAtOrDefault(1) ?? "9000");
var deviceId = args.ElementAtOrDefault(2) ?? "EXAMPLE-001";
var reportSeconds = double.Parse(args.ElementAtOrDefault(3) ?? "3", System.Globalization.CultureInfo.InvariantCulture);
var heartbeatSeconds = double.Parse(args.ElementAtOrDefault(4) ?? "10", System.Globalization.CultureInfo.InvariantCulture);
if (reportSeconds <= 0 || heartbeatSeconds <= 0 || port is < 1 or > 65535) throw new ArgumentException("Invalid interval or port");
using var quit = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) => { e.Cancel = true; quit.Cancel(); };
var token = quit.Token;
while (!token.IsCancellationRequested)
{
    try
    {
        using var client = new TcpClient();
        await client.ConnectAsync(host, port, token);
        Console.WriteLine($"Connected to TCP {host}:{port}, device {deviceId}");
        using var writer = new StreamWriter(client.GetStream(), new UTF8Encoding(false), leaveOpen: true) { AutoFlush = true, NewLine = "\n" };
        using var reader = new StreamReader(client.GetStream(), Encoding.UTF8, leaveOpen: true);
        // A single send path avoids heartbeat and telemetry interleaving bytes.
        async Task Send(object packet)
        {
            await writer.WriteLineAsync(JsonSerializer.Serialize(packet).AsMemory(), token);
            using var ackTimeout = CancellationTokenSource.CreateLinkedTokenSource(token);
            ackTimeout.CancelAfter(TimeSpan.FromSeconds(10));
            var line = await reader.ReadLineAsync(ackTimeout.Token) ?? throw new IOException("Server disconnected");
            Console.WriteLine("ACK " + line);
            using var ack = JsonDocument.Parse(line);
            if (!ack.RootElement.GetProperty("ok").GetBoolean()) throw new IOException("Server rejected packet: " + line);
        }
        await Send(new { type = "register", deviceId });
        var nextReport = DateTime.UtcNow;
        var nextHeartbeat = DateTime.UtcNow.AddSeconds(heartbeatSeconds);
        while (!token.IsCancellationRequested)
        {
            var now = DateTime.UtcNow;
            if (now >= nextHeartbeat)
            {
                await Send(new { type = "heartbeat", deviceId });
                nextHeartbeat = DateTime.UtcNow.AddSeconds(heartbeatSeconds);
            }
            if (now >= nextReport)
            {
                await Send(new { type = "telemetry", deviceId, messageId = Guid.NewGuid().ToString("N"), collectedAt = DateTimeOffset.UtcNow, motorTemperature = 55.2, ambientTemperature = 26.5, load = 3.8, ratedLoad = 8, hookHeight = 38.5, radius = 24, rotation = 120, windSpeed = 4.2, workState = "running" });
                nextReport = DateTime.UtcNow.AddSeconds(reportSeconds);
            }
            await Task.Delay(100, token);
        }
    }
    catch (OperationCanceledException) when (token.IsCancellationRequested) { break; }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message + "; reconnecting in 2 seconds");
        try { await Task.Delay(2000, token); } catch (OperationCanceledException) { break; }
    }
}

05 / TCP 调试与结果验证

  1. 打开支持 TCP 客户端模式的网络调试工具,填写服务器 IP 与端口 9000。
  2. 选择 UTF-8 文本发送,关闭 HEX 模式;启用末尾追加 LF 或 CRLF。
  3. 发送注册报文,确认服务端返回 ok: true。
  4. 发送心跳与完整上报报文;每次新上报更换 messageId 和采集时间。
  5. 打开设备管理查找 EXAMPLE-001,进入详情查看指标和趋势。
  6. 在历史数据筛选该设备;将电机温度改为 85 后上报,在告警中心验证高温告警。
  7. 恢复正常温度并用新 messageId 上报,查看恢复时间;人工确认不会自动恢复异常。

常见问题

端口不可达或连接被拒绝

确认服务端已启动、端口为 9000、IP 正确,且防火墙允许 TCP。远程客户端不能使用 127.0.0.1 或 0.0.0.0。端口被其他进程占用会使服务端启动失败,查看日志。

已发送报文,却没有应答

检查末尾是否含实际 LF 字节。TCP 不保留消息边界,未发送换行时服务端会等待完整报文,超时后关闭连接。

JSON 格式错误或指标校验失败

使用双引号和有限数字,不要带注释或尾逗号。telemetry 所有字段均必填,按上方字段表检查。使用 UTF-8 无 BOM 编码。

提示未注册即上报

每次连接或重连都需要注册。心跳和上报的 deviceId 必须与该连接注册的编号一致。

设备编号重复或连接突然断开

同编号最新连接会替换旧连接。模拟器使用 TC-001 至 TC-006,外部示例请使用独立设备编号。

设备心跳超时

默认 30 秒内需要至少一条有效注册、心跳或遥测报文。建议心跳间隔 10 秒;错误报文不会延长在线时间。