网关基座最多支持19个BLE外设,稳定可靠,睡眠养老解决方案, BLE网关使用说明书

网关基座最多支持19个BLE外设,稳定可靠,睡眠养老解决方案, BLE网关使用说明书

网关基座最多支持19个BLE外设,稳定可靠,广泛应用睡眠养老解决方案、养老院、幼儿园、康养中心。

BLE网关使用说明书 – 深圳加一健康科技网关基座,支持19个外设

WIFI4G 网关使用说明书

配置 App 连接 · 功能配置 · 云端数据监测

适用网关:以 GW 开头的 BLE 网关设备 | 文档版本:中文版 HTML

下载配置APK: gw-config-app-release.apk

下载云测试工具: gateway_test_server.exe

目录

  • 1. 连接网关
  • 2. 功能配置
    • 2.1 设备信息
    • 2.2 WiFi 配置
    • 2.3 云端配置
    • 2.4 MQTT 配置
    • 2.5 白名单管理
    • 2.6 LED / 蜂鸣器控制
    • 2.7 系统设置
  • 3. 云端监测数据
  • 4. 设备序列号位置

1. 连接网关

网关上电,进入网关配置app,先设置app权限,好了之后开始扫描,连接我们的网关GW开头的设备,连接成功后会自动进入网关配置页面,该页面由设备预览和功能配置两部分组成,而功能配置由7个板块组成.

  1. 网关上电。
  2. 打开网关配置 App,并设置 App 权限。
  3. 开始扫描,连接名称以 GW 开头的设备。
  4. 连接成功后,系统会自动进入网关配置页面。

图 1-1 网关配置 App 首页

图 1-2 权限与语言选择界面
图 1-3 扫描到 GW 设备
图 1-4 连接成功并进入配置页

2. 功能配置

我们主要使用功能配置部分,接下来会一一说明.

配置页面由设备预览功能配置两部分组成,功能配置由 7 个板块组成,下文逐一说明。

2.1 设备信息

点击设备信息,通过一键读取可以获取网关的信息,也可以通过底部按钮将手机时间同步到网关.

提示:点击“一键读取”可获取网关信息;也可点击底部按钮,将手机时间同步到网关。

图 2-1 设备信息模块
图 2-2 一键读取网关信息
图 2-3 手机时间同步到网关

2.2 WiFi 配置

点击WiFi配置进入WiFi配置页面,可以设置WiFi SSID和密码(输入完成后要点击设置按钮,防止配置丢失),可以在该页底部查询网络连接状态.

  1. 进入 WiFi 配置页面。
  2. 设置 WiFi SSID 和密码。
  3. 输入完成后点击“设置”按钮,防止配置丢失。
  4. 在页面底部查询网络连接状态。
图 2-4 WiFi 配置入口
图 2-5 设置 WiFi SSID 与密码
图 2-6 WiFi 设置结果
图 2-7 网络连接状态查询

2.3 云端配置

点击云端配置,可以在该页设置不同协议下的服务器地址和输入端口(监听器上的端口),输入完成后要保存配置,保存完毕后可以查询已保存的配置,可以通过协议开关来打开或关闭相应的协议.

  1. 进入云端配置页面。
  2. 设置不同协议下的服务器地址和输入端口。
  3. 输入完成后保存配置。
  4. 查询已保存的配置,并通过协议开关打开或关闭对应协议。
图 2-8 云端配置页面
图 2-9 协议服务器与端口设置

2.4 MQTT 配置

点击MQTT配置,可以设置MQTT用户名,密码及发布主题.

提示:MQTT 配置用于设置 MQTT 用户名、密码及发布主题。

图 2-10 MQTT 配置页面

2.5 白名单管理

点击白名单管理,可以在该页管理BLE设备,添加设备可以通过扫描设备和输入设备序列号两种方式添加(设备序列号一般在设备背部)

  1. 进入白名单管理页面。
  2. 通过“扫描设备”或“输入设备序列号”添加设备。
  3. 添加设备之后先保存白名单。
  4. 若设备仍为未连接状态,点击右上角“刷新”,直到设备连接成功。
  5. 需要删除设备时,点击设备右侧红叉,删除后再次保存。
图 2-11 白名单管理页面
图 2-12 扫描添加设备
图 2-13 输入设备序列号添加
图 2-14 白名单设备列表
图 2-15 保存白名单配置
图 2-16 刷新连接状态 / 删除设备

注意:设备序列号一般位于设备背部,详见第 4 章。

2.6 LED / 蜂鸣器控制

点击LED/蜂鸣器的控制模块,可以通过该模块控制LED显示的灯光,动画效果等,也可以控制蜂鸣器进行报警/停止操作.

功能:控制 LED 显示的灯光与动画效果,也可控制蜂鸣器进行报警 / 停止操作。

图 2-17 LED / 蜂鸣器控制模块

2.7 系统设置

点击系统设置模块,可以在该模块控制网关WiFi模组的电源,同步手机时间和恢复出厂默认设置等操作.

功能:控制网关 WiFi 模组电源、同步手机时间、恢复出厂默认设置等。

图 2-18 系统设置模块

3. 云端监测数据

配置好网关后我们可以用网关云端来监测获取数据,在网关配置app上连接好WiFi并配好云端配置之后,点击启动服务,网关会自动连接云端,连接成功后日志会显示客户端已接入,之后就可以正常获取网关上传的数据了.

  1. 在网关配置 App 上连接好 WiFi,并配置好云端配置。
  2. 点击“启动服务”,网关自动连接云端。
  3. 连接成功后,日志显示“客户端已接入”。
  4. 之后即可正常获取网关上传的数据。
图 3-1 启动云服务
图 3-2 云服务连接与客户端接入
图 3-3 云端数据监测界面

数据上传途中,可以通过停止服务按钮来暂停数据上传,想继续点击启动服务按钮即可;同时也可以通过清空日志和清空数据按钮来单独清空日志窗口或数据窗口内容.

提示:数据上传途中可点击“停止服务”暂停上传,点击“启动服务”继续;也可使用“清空日志”和“清空数据”按钮单独清空日志窗口或数据窗口。

图 3-4 数据窗口
图 3-5 日志窗口

4. 设备序列号位置

设备序列号一般在设备背部,如图:

图 4-1 设备序列号位置(设备背部)

本文档由《深圳加一健康科技 BLE网关使用说明文档.docx》中文原版转换生成。

深圳加一健康多协议BLE物联网关 — 云端数据协议与集成使用说明

目录

  • 1. 产品概述
  • 2. 系统架构
  • 3. 云端数据上行协议
  • 4. 网关心跳帧
  • 5. 校验和算法
  • 6. 云端数据下行协议
  • 7. MQTT 配置说明
  • 8. 服务端解析示例
  • 9. 集成使用指南

1. 产品概述

WIFI4GGateWayBLE 是一款多协议物联网关,通过 BLE 连接最多 19 个 传感器外设(SP、ECG、P1H、PO 等),将采集数据经 WiFi / 4G 网络上传至云端服务器。

核心能力

  • 多协议上行:支持 UDP / TCP / MQTT 三种云端协议,可独立开关
  • 数据汇聚:传感器数据在网关内汇聚缓存,支持定时批量上传或即时发送
  • 网关心跳:每 3 秒发送心跳帧到云端,携带网关自身序列号
  • 下行透传:云端可通过 UDP/TCP 向网关下发指令,经 BLE 透传至传感器

2. 系统架构

加一健康科技BLE网关

数据流向

方向路径协议
上行BLE 传感器 → 网关(汇聚打包)→ UDP/TCP/MQTT → 云端UDP / TCP / MQTT Publish
下行云端 → UDP/TCP → 网关 → BLE → 传感器UDP / TCP
心跳网关(自主)→ UDP/TCP → 云端UDP / TCP

3. 云端数据上行协议

UDP 和 TCP 两种协议的数据帧格式完全相同,MQTT 发布时数据载荷也使用相同格式。仅传输层不同。

3.1 汇聚帧格式总览

0xAA| GW_ID[2]| Frame_TS[8]| PktCnt[2]| 逐包数据… (可多个)| Checksum[2]

帧总长度可变,取决于包含的 BLE 数据包数量和每个包的数据大小,上限 1024 字节。

3.2 帧头字段

偏移长度字段字节序说明
01Frame Header固定值 0xAA,帧起始标识
12Gateway ID大端网关设备 ID,当前固定 0x0001
38Frame Timestamp大端帧级 UTC 毫秒时间戳(uint64)
112Packet Count大端本帧包含的 BLE 数据包数量

3.3 逐包数据格式 (Per-Packet)

SN[15]| RSSI[1]| Packet_TS[8]| DataLen[2]| Data[N]

偏移(相对)长度字段字节序说明
015Serial Number原始传感器设备序列号(ASCII 字符串,不足右补 \0)
151RSSI接收信号强度,有符号 int8(单位 dBm)
168Packet Timestamp大端该数据包接收时刻的 UTC 毫秒时间戳
242Data Length大端载荷数据长度(0 ~ 512)
26NData原始BLE 传感器上报的原始字节流

注意:SN 字段固定 15 字节,实际序列号不足 15 字符时,右侧用 0x00 填充。例如 SN=”SP001″(4 字符)实际存储为 "SP001\0\0\0\0\0\0\0\0\0\0\0".

3.4 帧长计算

帧总长度 = 13 (帧头) + Σ(26 + DataLen_i) (每个包) + 2 (Checksum)

3.5 完整帧示例

假设网关 GW_ID=0x0001 在时间戳 1700000000000 收到 2 个 BLE 数据包:

  • 包 #1:SN=SP0000000001,RSSI=-45,TS=1700000000000,Data=0x48 0x65 0x6C(”Hel”)
  • 包 #2:SN=ECG000000002,RSSI=-60,TS=1700000000100,Data=0x6C 0x6F(”lo”)
偏移  内容                              说明
───   ────                              ────
00    AA                                帧头
01    00 01                             GW_ID = 0x0001 (大端)
03    00 00 01 8B C1 E4 BD 00           Frame TS = 1700000000000 (大端)
0B    00 02                             PktCnt = 2 (大端)

── 包 #1 ──
0D    53 50 30 30 30 30 30 30 30 30 31  SN = "SP0000000001"
      00 00 00 00                          (15 bytes)
1C    D3                                RSSI = -45 (0xD3)
1D    00 00 01 8B C1 E4 BD 00           Packet TS = 1700000000000
25    00 03                             DataLen = 3 (大端)
27    48 65 6C                          "Hel"

── 包 #2 ──
2A    45 43 47 30 30 30 30 30 30 30 32  SN = "ECG000000002"
      00 00 00 00                          (15 bytes)
39    C4                                RSSI = -60 (0xC4)
3A    00 00 01 8B C1 E4 BD 64           Packet TS = 1700000000100
42    00 02                             DataLen = 2 (大端)
44    6C 6F                             "lo"

── Checksum ──
46    XX XX                             Checksum(0x00..0x45)

帧全长 = 13 + (26+3) + (26+2) + 2 = 72 字节

3.6 发送机制

触发类型条件说明
定时刷新缓存超时网关汇聚缓存超时后自动打包上传(默认 5000ms)
立即发送缓存超时配置为 0不做缓存,每包立即发送
心跳每 3 秒网关主动发送,PktCnt=0(见第 4 节)

3.7 协议路由

同一汇聚帧会根据网关内启用的协议开关,同时发送到已启用的 UDP / TCP / MQTT 链路。因此云端可能从多条链路收到相同数据的副本,需按 (GW_ID, Frame_TS) 去重(见第 9.2 节)。

网关使用 net_mode 选择实际联网通道:

net_mode含义
0NONE(不发送)
1WIFI 通道
24G(China Mobile ML307R)
34G(Quectel EC800)

4. 网关心跳帧

网关每隔 3 秒 自动向云端发送心跳帧,用于网关存活性检测在线状态监控。心跳帧复用汇聚帧格式,通过 PktCnt = 0 与数据帧区分。

4.1 帧格式

0xAA| GW_ID[2]| Timestamp[8]| 0x0000| SN[15]| Checksum[2]

偏移长度字段说明
010xAA帧头
12GW_ID网关 ID,大端序
38Timestamp当前 UTC 毫秒时间戳,大端序
112PktCnt固定 0x0000,标识为心跳帧
1315Gateway SN网关自身序列号(ASCII 字符串,不足右补 \0)
282Checksum16 位累加和(第 5 节算法)

心跳帧固定 30 字节.

4.2 云端识别逻辑

PktCnt == 0  →  心跳帧 (Heartbeat)
PktCnt >= 1  →  数据汇聚帧 (Aggregated Data Frame)

建议:云端如果超过 10 秒(3 个心跳周期)未收到心跳,应判定网关离线,触发告警。

5. 校验和算法

使用简单的 16 位累加求校验和算法。校验范围:从帧头 0xAA 到 Checksum 字段前一个字节(不含 Checksum 字段自身)。服务端验证示例(Java):

public static int calcChecksum(byte[] data, int offset, int len) {
    int sum = 0;
    for (int i = 0; i < len; i++) {
        sum += (data[offset + i] & 0xFF);
    }
    return sum & 0xFFFF;
}

// 验证:data 末尾 2 字节为收到的 checksum
int expected = ((data[data.length - 2] & 0xFF) << 8)
             | (data[data.length - 1] & 0xFF);
int actual = calcChecksum(data, 0, data.length - 2);
boolean valid = (expected == actual);

6. 云端数据下行协议

云端通过 UDP 或 TCP 向网关下发数据(指令 / 配置 / 透传数据等),网关收到后解析并路由到对应处理逻辑,经 BLE 透传至目标传感器。

6.1 下行数据帧格式(云端 → 网关)

当前下行数据帧格式与上行相同(共用帧头和校验和),帧内包含 BLE MAC 地址、网关 ID、命令数据等字段。

注意:下行数据根据具体业务需求扩展。目前支持 CMD_WIFI_FIRST_TIME (0x16) — 云端要求网关上发设备信息。下行协议的完整命令集在后续版本中补充。

7. MQTT 配置说明

7.1 MQTT 参数

参数默认值最大长度说明
Broker 地址32由网关侧配置
Broker 端口由网关侧配置
用户名16可选,由网关侧配置
密码16可选,由网关侧配置
Client ID16
发布主题SeenNext-GW15网关上行数据发布到该主题
订阅主题网关订阅同主题或特定 cmd 主题(可配置)

7.2 发布数据格式

网关 MQTT 发布时,会先将 0xAA 二进制帧(与 UDP/TCP 上行帧完全一致)转换为 大写十六进制字符串,再作为 MQTT 消息的 Payload 发布到发布主题。

因此云端订阅到 MQTT 消息后,必须先做 HEX → 二进制还原,再按第 3 节格式解析。示例:

# 原始二进制帧(UDP/TCP 收到的即是此形态)
AA 00 01 00 00 01 8B C1 E4 BD 00 00 02 ...

# MQTT Payload(网关发布的实际内容,大写十六进制、无分隔符)
AA00010000018BC1E4BD000002...

# 云端还原:
payload = msg.payload                     # bytes, e.g. b"AA00010000018BC1E4BD000002..."
raw = bytes.fromhex(payload.decode("ascii"))   # 还原为二进制帧
frame = parse_gateway_frame(raw)          # 按第 3 节格式解析

注意:MQTT 载荷是 HEX 字符串(长度约为二进制帧的 2 倍),而 UDP/TCP 网络包是 原始二进制。云端对三条链路的处理必须区分:UDP/TCP 直接按帧解析,MQTT 需先 hex 解码再解析。

8. 服务端解析示例

MQTT 载荷需先还原:以下解析函数接收的都是 二进制帧(UDP/TCP 直接可用)。若数据来自 MQTT,msg.payload 是 HEX 字符串,需先执行 bytes.fromhex(payload.decode("ascii"))(Java 用 HexFormat.of().parseHex())还原为二进制后再调用解析函数。

8.1 Java 服务端解析

/**
 * 解析网关上行汇聚帧
 */
public class GatewayFrameParser {

    public static class SensorPacket {
        public String serialNumber;   // 15 字节 ASCII
        public int    rssi;            // dBm (signed)
        public long   timestamp;       // UTC 毫秒
        public byte[] data;            // 原始传感器数据
    }

    public static class AggregatedFrame {
        public int           gatewayId;
        public long          frameTimestamp;
        public List<SensorPacket> packets = new ArrayList<>();
        public boolean       isHeartbeat;
    }

    public static AggregatedFrame parse(byte[] raw) {
        if (raw == null || raw.length < 15) return null;
        if ((raw[0] & 0xFF) != 0xAA) return null;

        // 校验和
        int expectedCs = ((raw[raw.length - 2] & 0xFF) << 8)
                       | (raw[raw.length - 1] & 0xFF);
        int actualCs = calcChecksum(raw, 0, raw.length - 2);
        if (expectedCs != actualCs) return null;  // checksum error

        AggregatedFrame frame = new AggregatedFrame();
        frame.gatewayId = ((raw[1] & 0xFF) << 8) | (raw[2] & 0xFF);   // 大端
        frame.frameTimestamp = readBigEndianUint64(raw, 3);

        int pktCnt = ((raw[11] & 0xFF) << 8) | (raw[12] & 0xFF);       // 大端
        frame.isHeartbeat = (pktCnt == 0);

        int offset = 13;
        for (int i = 0; i < pktCnt; i++) {
            SensorPacket pkt = new SensorPacket();
            pkt.serialNumber = new String(raw, offset, 15,
                                 java.nio.charset.StandardCharsets.US_ASCII).trim();
            pkt.rssi    = raw[offset + 15];  // signed byte
            pkt.timestamp = readBigEndianUint64(raw, offset + 16);
            int dataLen   = ((raw[offset + 24] & 0xFF) << 8)
                          | (raw[offset + 25] & 0xFF);  // 大端
            pkt.data = Arrays.copyOfRange(raw, offset + 26, offset + 26 + dataLen);
            frame.packets.add(pkt);
            offset += 26 + dataLen;
        }

        return frame;
    }

    private static long readBigEndianUint64(byte[] buf, int off) {
        long v = 0;
        for (int i = 0; i < 8; i++)
            v = (v << 8) | (buf[off + i] & 0xFF);
        return v;
    }

    private static int calcChecksum(byte[] data, int off, int len) {
        int sum = 0;
        for (int i = 0; i < len; i++)
            sum += (data[off + i] & 0xFF);
        return sum & 0xFFFF;
    }
}

10.2 Python 服务端解析

import struct

def parse_gateway_frame(raw: bytes):
    """解析网关上行汇聚帧,返回 dict 或 None"""
    if len(raw) < 15 or raw[0] != 0xAA:
        return None

    # 校验和
    expected_cs = (raw[-2] << 8) | raw[-1]
    actual_cs = sum(raw[:-2]) & 0xFFFF
    if expected_cs != actual_cs:
        return None

    gw_id = (raw[1] << 8) | raw[2]
    frame_ts = struct.unpack('>Q', raw[3:11])[0]  # 大端 uint64
    pkt_cnt = (raw[11] << 8) | raw[12]

    result = {
        'gw_id': gw_id,
        'frame_ts': frame_ts,
        'pkt_cnt': pkt_cnt,
        'is_heartbeat': pkt_cnt == 0,
        'packets': [],
    }

    offset = 13
    for _ in range(pkt_cnt):
        sn = raw[offset:offset+15].decode('ascii').rstrip('\x00')
        rssi = raw[offset + 15] if raw[offset + 15] < 128 \
               else raw[offset + 15] - 256  # 有符号
        pkt_ts = struct.unpack('>Q', raw[offset+16:offset+24])[0]
        data_len = (raw[offset+24] << 8) | raw[offset+25]
        data = raw[offset+26:offset+26+data_len]

        result['packets'].append({
            'sn': sn,
            'rssi': rssi,
            'timestamp': pkt_ts,
            'data': data,
        })
        offset += 26 + data_len

    return result

8.3 C 服务端解析

#include <stdint.h>
#include <string.h>

typedef struct {
    char     sn[16];
    int8_t   rssi;
    uint64_t timestamp;
    uint16_t data_len;
    uint8_t  data[512];
} sensor_packet_t;

typedef struct {
    uint16_t       gw_id;
    uint64_t       frame_ts;
    uint16_t       pkt_cnt;
    int            is_heartbeat;
    sensor_packet_t packets[64];
} aggregated_frame_t;

int parse_gateway_frame(const uint8_t *buf, uint16_t len,
                         aggregated_frame_t *out) {
    if (buf == NULL || len < 15 || buf[0] != 0xAA) return -1;

    /* 校验和 */
    uint16_t expected = ((uint16_t)buf[len - 2] << 8) | buf[len - 1];
    uint16_t actual = 0;
    for (uint16_t i = 0; i < len - 2; i++) actual += buf[i];
    if (expected != actual) return -2;

    out->gw_id    = ((uint16_t)buf[1] << 8) | buf[2];
    out->frame_ts = ((uint64_t)buf[3]  << 56) | ((uint64_t)buf[4]  << 48)
                  | ((uint64_t)buf[5]  << 40) | ((uint64_t)buf[6]  << 32)
                  | ((uint64_t)buf[7]  << 24) | ((uint64_t)buf[8]  << 16)
                  | ((uint64_t)buf[9]  << 8)  | buf[10];
    out->pkt_cnt    = ((uint16_t)buf[11] << 8) | buf[12];
    out->is_heartbeat = (out->pkt_cnt == 0);

    uint16_t offset = 13;
    for (uint16_t i = 0; i < out->pkt_cnt; i++) {
        memcpy(out->packets[i].sn, &buf[offset], 15);
        out->packets[i].sn[15] = '\0';
        out->packets[i].rssi = (int8_t)buf[offset + 15];
        out->packets[i].timestamp =
            ((uint64_t)buf[offset+16] << 56) | ((uint64_t)buf[offset+17] << 48)
          | ((uint64_t)buf[offset+18] << 40) | ((uint64_t)buf[offset+19] << 32)
          | ((uint64_t)buf[offset+20] << 24) | ((uint64_t)buf[offset+21] << 16)
          | ((uint64_t)buf[offset+22] << 8)  |  buf[offset+23];
        out->packets[i].data_len =
            ((uint16_t)buf[offset+24] << 8) | buf[offset+25];
        memcpy(out->packets[i].data, &buf[offset+26],
               out->packets[i].data_len);
        offset += 26 + out->packets[i].data_len;
    }

    return 0;
}

9. 集成使用指南

9.1 云端服务端集成

  1. UDP 监听:启动 UDP 服务监听配置的端口,接收二进制帧
  2. TCP 监听:启动 TCP 服务,接受网关连接,接收二进制帧
  3. MQTT 订阅:连接 Broker,订阅网关发布的 Topic(默认 SeenNext-GW),收到的是 HEX 字符串载荷,先 hex → 二进制还原再解析
  4. 解析帧:按第 8 节的解析代码提取网关 ID、时间戳、传感器数据
  5. 心跳监控:超 10 秒未收到心跳帧 → 标记网关离线
  6. 下行指令:通过 UDP/TCP 向网关 IP 发送指令帧

重要:UDP 和 TCP 发送的是相同格式的二进制帧。如果同时启用 UDP 和 TCP,网关会将同一帧同时通过两个协议各发一份,云端可选择其一处理、另一份丢弃(根据 GW_ID+Frame_TS 去重)。

9.2 帧去重建议

由于 UDP 和 TCP 可能发送相同数据的副本,建议云端通过 (GW_ID, Frame_TS) 组合进行去重:

// Java 去重
Set<String> seenFrames = new HashSet<>();
String dedupKey = frame.gatewayId + "_" + frame.frameTimestamp;

if (seenFrames.contains(dedupKey)) {
    // 重复帧,丢弃
    return;
}
seenFrames.add(dedupKey);

// 定期清理过期 Key(如超过 60 秒的条目)
en_USEnglish