feat: mqtt receive.

This commit is contained in:
2026-04-28 15:29:16 +08:00
parent 51871c352a
commit 2464617599
9 changed files with 2820 additions and 2 deletions
+10
View File
@@ -8,3 +8,13 @@ ai:
base_url: https://api.lkeap.cloud.tencent.com/v1 base_url: https://api.lkeap.cloud.tencent.com/v1
api_key: "" api_key: ""
model: deepseek-v3.2 model: deepseek-v3.2
mqtt:
enabled: true
host: mqtt.weihua-iot.cn
port: 10237
username: public_client
password: uXC3M4ObO9KpdU
client_id_prefix: hr-receiver
region: "+"
use_tls: true
qos: 0
+13
View File
@@ -26,9 +26,22 @@ type AIConfig struct {
Model string `mapstructure:"model" yaml:"model"` Model string `mapstructure:"model" yaml:"model"`
} }
type MQTTConfig struct {
Enabled bool `mapstructure:"enabled" yaml:"enabled"`
Host string `mapstructure:"host" yaml:"host"`
Port int `mapstructure:"port" yaml:"port"`
Username string `mapstructure:"username" yaml:"username"`
Password string `mapstructure:"password" yaml:"password"`
ClientIDPrefix string `mapstructure:"client_id_prefix" yaml:"client_id_prefix"`
Region string `mapstructure:"region" yaml:"region"`
UseTLS bool `mapstructure:"use_tls" yaml:"use_tls"`
QoS int `mapstructure:"qos" yaml:"qos"`
}
type AppConfig struct { type AppConfig struct {
DB DBConfig `mapstructure:"database" yaml:"database"` DB DBConfig `mapstructure:"database" yaml:"database"`
AI AIConfig `mapstructure:"ai" yaml:"ai"` AI AIConfig `mapstructure:"ai" yaml:"ai"`
MQTT MQTTConfig `mapstructure:"mqtt" yaml:"mqtt"`
} }
func InitConfig() { func InitConfig() {
+2
View File
@@ -19,6 +19,7 @@ require (
github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect github.com/cloudwego/iasm v0.2.0 // indirect
github.com/eclipse/paho.mqtt.golang v1.5.0 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/fumiama/imgsz v0.0.2 // indirect github.com/fumiama/imgsz v0.0.2 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
@@ -28,6 +29,7 @@ require (
github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect github.com/jackc/pgx/v5 v5.5.5 // indirect
+4
View File
@@ -9,6 +9,8 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o=
github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
@@ -40,6 +42,8 @@ github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
+9
View File
@@ -3,7 +3,9 @@ package main
import ( import (
"hr_receiver/config" "hr_receiver/config"
"hr_receiver/models" "hr_receiver/models"
"hr_receiver/mqtt"
"hr_receiver/routes" "hr_receiver/routes"
"log"
) )
func main() { func main() {
@@ -25,8 +27,15 @@ func main() {
&models.StepStrideFreq{}, &models.StepStrideFreq{},
&models.RegressionResult{}, &models.RegressionResult{},
&models.User{}, &models.User{},
&models.MqttHeartRateRecord{},
&models.MqttStepCountRecord{},
&models.MqttGatewayStatusRecord{},
) )
if err := mqtt.Start(config.DB, config.App.MQTT); err != nil {
log.Printf("mqtt listener start failed: %v", err)
}
// 启动服务 // 启动服务
r := routes.SetupRouter() r := routes.SetupRouter()
r.Run(":8081") r.Run(":8081")
+57
View File
@@ -0,0 +1,57 @@
package models
import "gorm.io/gorm"
type MqttHeartRateRecord struct {
gorm.Model
Identifier string `gorm:"uniqueIndex;size:255" json:"identifier"`
Topic string `gorm:"size:255;index" json:"topic"`
RegionID uint32 `gorm:"index" json:"regionId"`
GatewayMAC string `gorm:"size:32;index" json:"gatewayMac"`
BandID uint32 `gorm:"index" json:"bandId"`
BeltAddr string `gorm:"size:64;index" json:"beltAddr"`
PacketNum uint32 `gorm:"index" json:"packetNum"`
HeartRate int `gorm:"type:int" json:"heartRate"`
HrConfidence int `gorm:"type:int" json:"hrConfidence"`
IsActive bool `json:"isActive"`
IsOnSkin bool `json:"isOnSkin"`
Battery uint32 `json:"battery"`
SignalRSSINeg float64 `gorm:"type:double precision" json:"signalRssiNeg"`
SNR float64 `gorm:"type:double precision" json:"snr"`
HubBusID uint32 `json:"hubBusId"`
HubSubDevID uint32 `json:"hubSubDevId"`
ReceivedAt int64 `gorm:"type:bigint;index" json:"receivedAt"`
}
type MqttStepCountRecord struct {
gorm.Model
Identifier string `gorm:"uniqueIndex;size:255" json:"identifier"`
Topic string `gorm:"size:255;index" json:"topic"`
RegionID uint32 `gorm:"index" json:"regionId"`
GatewayMAC string `gorm:"size:32;index" json:"gatewayMac"`
BandID uint32 `gorm:"index" json:"bandId"`
BeltAddr string `gorm:"size:64;index" json:"beltAddr"`
PacketNum uint32 `gorm:"index" json:"packetNum"`
StepCount uint32 `json:"stepCount"`
SignalRSSINeg float64 `gorm:"type:double precision" json:"signalRssiNeg"`
SNR float64 `gorm:"type:double precision" json:"snr"`
HubBusID uint32 `json:"hubBusId"`
HubSubDevID uint32 `json:"hubSubDevId"`
ReceivedAt int64 `gorm:"type:bigint;index" json:"receivedAt"`
}
type MqttGatewayStatusRecord struct {
gorm.Model
Identifier string `gorm:"uniqueIndex;size:255" json:"identifier"`
Topic string `gorm:"size:255;index" json:"topic"`
RegionID uint32 `gorm:"index" json:"regionId"`
GatewayMAC string `gorm:"size:32;index" json:"gatewayMac"`
BootCount uint32 `json:"bootCount"`
UptimeMs uint32 `json:"uptimeMs"`
DurationMsSinceLastPacket uint32 `json:"durationMsSinceLastPacket"`
RxCount uint32 `json:"rxCount"`
BatteryVoltageMV uint32 `json:"batteryVoltageMv"`
BatterySOCPercentage uint32 `json:"batterySocPercentage"`
ChargingRatePercentage int32 `json:"chargingRatePercentage"`
ReceivedAt int64 `gorm:"type:bigint;index" json:"receivedAt"`
}
+300
View File
@@ -0,0 +1,300 @@
package mqtt
import (
"crypto/tls"
"fmt"
"hr_receiver/config"
"hr_receiver/models"
whgw_hrpb "hr_receiver/proto"
"log"
"strings"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
"google.golang.org/protobuf/proto"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
const (
defaultQueueSize = 2048
defaultWorkers = 4
)
type Listener struct {
db *gorm.DB
cfg config.MQTTConfig
client mqtt.Client
writeCh chan interface{}
}
func Start(db *gorm.DB, cfg config.MQTTConfig) error {
if !cfg.Enabled {
log.Println("mqtt listener disabled")
return nil
}
if err := validateConfig(cfg); err != nil {
return err
}
listener := &Listener{
db: db,
cfg: cfg,
writeCh: make(chan interface{}, defaultQueueSize),
}
for i := 0; i < defaultWorkers; i++ {
go listener.writeWorker()
}
if err := listener.connect(); err != nil {
return err
}
return nil
}
func validateConfig(cfg config.MQTTConfig) error {
if cfg.Host == "" {
return fmt.Errorf("missing config: mqtt.host")
}
if cfg.Port == 0 {
return fmt.Errorf("missing config: mqtt.port")
}
if cfg.Region == "" {
return fmt.Errorf("missing config: mqtt.region")
}
if cfg.ClientIDPrefix == "" {
return fmt.Errorf("missing config: mqtt.client_id_prefix")
}
return nil
}
func (l *Listener) connect() error {
opts := mqtt.NewClientOptions()
scheme := "tcp"
if l.cfg.UseTLS {
scheme = "ssl"
opts.SetTLSConfig(&tls.Config{MinVersion: tls.VersionTLS12})
}
broker := fmt.Sprintf("%s://%s:%d", scheme, l.cfg.Host, l.cfg.Port)
opts.AddBroker(broker)
opts.SetClientID(fmt.Sprintf("%s-%d", l.cfg.ClientIDPrefix, time.Now().UnixNano()))
opts.SetUsername(l.cfg.Username)
opts.SetPassword(l.cfg.Password)
opts.SetKeepAlive(60 * time.Second)
opts.SetAutoReconnect(true)
opts.SetConnectRetry(true)
opts.SetConnectRetryInterval(5 * time.Second)
opts.SetOnConnectHandler(func(client mqtt.Client) {
if err := l.subscribe(client); err != nil {
log.Printf("mqtt subscribe failed: %v", err)
return
}
log.Printf("mqtt connected to %s", broker)
})
opts.SetConnectionLostHandler(func(client mqtt.Client, err error) {
log.Printf("mqtt connection lost: %v", err)
})
opts.SetDefaultPublishHandler(l.handleMessage)
l.client = mqtt.NewClient(opts)
token := l.client.Connect()
if !token.WaitTimeout(15 * time.Second) {
return fmt.Errorf("mqtt connect timeout")
}
return token.Error()
}
func (l *Listener) subscribe(client mqtt.Client) error {
topics := []string{
fmt.Sprintf("/whgw/v2/region/%s/measurement/band/+/hr", l.cfg.Region),
fmt.Sprintf("/whgw/v2/region/%s/measurement/band/+/step", l.cfg.Region),
fmt.Sprintf("/whgw/v2/region/%s/gateway/+/status", l.cfg.Region),
}
for _, topic := range topics {
token := client.Subscribe(topic, byte(l.cfg.QoS), l.handleMessage)
if !token.WaitTimeout(10 * time.Second) {
return fmt.Errorf("mqtt subscribe timeout for topic %s", topic)
}
if err := token.Error(); err != nil {
return fmt.Errorf("mqtt subscribe topic %s: %w", topic, err)
}
log.Printf("mqtt subscribed: %s", topic)
}
return nil
}
func (l *Listener) handleMessage(_ mqtt.Client, msg mqtt.Message) {
defer func() {
if r := recover(); r != nil {
log.Printf("mqtt message handling panic, topic=%s err=%v", msg.Topic(), r)
}
}()
if len(msg.Payload()) == 0 {
return
}
var packet whgw_hrpb.GatewaySlaveOutCloudMasterInMsg
if err := proto.Unmarshal(msg.Payload(), &packet); err != nil {
log.Printf("mqtt payload parse failed, topic=%s err=%v", msg.Topic(), err)
return
}
now := time.Now().UnixMilli()
switch payload := packet.Choice.(type) {
case *whgw_hrpb.GatewaySlaveOutCloudMasterInMsg_NtfHrMeasurement:
record := buildHeartRateRecord(payload.NtfHrMeasurement, msg.Topic(), now)
l.enqueue(&record)
case *whgw_hrpb.GatewaySlaveOutCloudMasterInMsg_NtfStepCountMeasurement:
record := buildStepCountRecord(payload.NtfStepCountMeasurement, msg.Topic(), now)
l.enqueue(&record)
case *whgw_hrpb.GatewaySlaveOutCloudMasterInMsg_NtfGatewayStatus:
record := buildGatewayStatusRecord(payload.NtfGatewayStatus, msg.Topic(), now)
l.enqueue(&record)
default:
log.Printf("mqtt payload ignored, unsupported type on topic=%s", msg.Topic())
}
}
func (l *Listener) enqueue(record interface{}) {
select {
case l.writeCh <- record:
default:
log.Printf("mqtt write queue full, dropping record of type %T", record)
}
}
func (l *Listener) writeWorker() {
for record := range l.writeCh {
func() {
defer func() {
if r := recover(); r != nil {
log.Printf("mqtt record persist panic, type=%T err=%v", record, r)
}
}()
if err := l.db.Clauses(clause.OnConflict{DoNothing: true}).Create(record).Error; err != nil {
log.Printf("mqtt record persist failed, type=%T err=%v", record, err)
}
}()
}
}
func buildHeartRateRecord(measurement *whgw_hrpb.HrMeasurement, topic string, now int64) models.MqttHeartRateRecord {
regionID := measurement.GetGatewayInfo().GetRegionId()
if regionID == 0 {
regionID = parseRegionFromTopic(topic)
}
gatewayMAC := formatMAC(measurement.GetGatewayInfo().GetGatewayMac())
packet := measurement.GetHrPacket()
rssi, snr := parsePacketStatus(measurement.GetPacketStatus())
beltAddr := fmt.Sprintf("%d-%d", regionID, packet.GetId())
return models.MqttHeartRateRecord{
Identifier: fmt.Sprintf("hr:%d:%s:%d:%d", regionID, gatewayMAC, packet.GetId(), packet.GetPacketNum()),
Topic: topic,
RegionID: regionID,
GatewayMAC: gatewayMAC,
BandID: packet.GetId(),
BeltAddr: beltAddr,
PacketNum: packet.GetPacketNum(),
HeartRate: int(packet.GetHr()),
HrConfidence: int(packet.GetStatus().GetHrConfidence().Number()),
IsActive: packet.GetStatus().GetIsActive(),
IsOnSkin: packet.GetStatus().GetIsOnSkin(),
Battery: packet.GetStatus().GetBattery(),
SignalRSSINeg: rssi,
SNR: snr,
HubBusID: measurement.GetHubInfo().GetBusId(),
HubSubDevID: measurement.GetHubInfo().GetSubDevId(),
ReceivedAt: now,
}
}
func buildStepCountRecord(measurement *whgw_hrpb.StepCountMeasurement, topic string, now int64) models.MqttStepCountRecord {
regionID := measurement.GetGatewayInfo().GetRegionId()
if regionID == 0 {
regionID = parseRegionFromTopic(topic)
}
gatewayMAC := formatMAC(measurement.GetGatewayInfo().GetGatewayMac())
packet := measurement.GetStepCountPacket()
rssi, snr := parsePacketStatus(measurement.GetPacketStatus())
beltAddr := fmt.Sprintf("%d-%d", regionID, packet.GetId())
return models.MqttStepCountRecord{
Identifier: fmt.Sprintf("step:%d:%s:%d:%d", regionID, gatewayMAC, packet.GetId(), packet.GetPacketNum()),
Topic: topic,
RegionID: regionID,
GatewayMAC: gatewayMAC,
BandID: packet.GetId(),
BeltAddr: beltAddr,
PacketNum: packet.GetPacketNum(),
StepCount: packet.GetStepCount(),
SignalRSSINeg: rssi,
SNR: snr,
HubBusID: measurement.GetHubInfo().GetBusId(),
HubSubDevID: measurement.GetHubInfo().GetSubDevId(),
ReceivedAt: now,
}
}
func buildGatewayStatusRecord(status *whgw_hrpb.GatewayStatus, topic string, now int64) models.MqttGatewayStatusRecord {
regionID := status.GetInfo().GetRegionId()
if regionID == 0 {
regionID = parseRegionFromTopic(topic)
}
gatewayMAC := formatMAC(status.GetInfo().GetGatewayMac())
return models.MqttGatewayStatusRecord{
Identifier: fmt.Sprintf("gateway:%d:%s:%d:%d:%d", regionID, gatewayMAC, status.GetStat().GetBootCount(), status.GetStat().GetUptimeMs(), status.GetStat().GetRxCount()),
Topic: topic,
RegionID: regionID,
GatewayMAC: gatewayMAC,
BootCount: status.GetStat().GetBootCount(),
UptimeMs: status.GetStat().GetUptimeMs(),
DurationMsSinceLastPacket: status.GetStat().GetDurationMsSinceLastPacket(),
RxCount: status.GetStat().GetRxCount(),
BatteryVoltageMV: status.GetStat().GetBatteryInfo().GetVoltageMv(),
BatterySOCPercentage: status.GetStat().GetBatteryInfo().GetSocPercentage(),
ChargingRatePercentage: status.GetStat().GetBatteryInfo().GetChargingRatePercentage(),
ReceivedAt: now,
}
}
func parsePacketStatus(status *whgw_hrpb.IPacketStatus) (float64, float64) {
if status == nil {
return 0, 0
}
if parsed := status.GetParsed(); parsed != nil {
return float64(parsed.GetSignalRssiNeg()), float64(parsed.GetSnrPkt())
}
if raw := status.GetRaw(); raw != nil {
return -float64(raw.GetSignalRssiX2Neg()) / 2, float64(raw.GetSnrPktX4()) / 4
}
return 0, 0
}
func formatMAC(data []byte) string {
if len(data) == 0 {
return ""
}
parts := make([]string, 0, len(data))
for _, b := range data {
parts = append(parts, fmt.Sprintf("%02x", b))
}
return strings.Join(parts, ":")
}
func parseRegionFromTopic(topic string) uint32 {
parts := strings.Split(topic, "/")
for i := 0; i < len(parts)-1; i++ {
if parts[i] == "region" {
var region uint32
if _, err := fmt.Sscanf(parts[i+1], "%d", &region); err == nil {
return region
}
}
}
return 0
}
File diff suppressed because it is too large Load Diff
+190
View File
@@ -0,0 +1,190 @@
syntax = "proto3";
package whgw_hr;
option go_package = "hr_receiver/proto;whgw_hrpb";
enum HrConfidence {
ZERO = 0;
LOW = 1;
MEDIUM = 2;
HIGH = 3;
}
enum LoRaBW {
BW_NONE = 0;
BW_10_4 = 0x08;
BW_15_6 = 0x01;
BW_20_8 = 0x09;
BW_31_25 = 0x02;
BW_41_7 = 0x0A;
BW_62_5 = 0x03;
BW_125_0 = 0x04;
BW_250_0 = 0x05;
BW_500_0 = 0x06;
}
message LoRaParameters {
LoRaBW bw = 1;
uint32 sf = 2;
float frequency_mhz = 3;
}
message StatusFlag {
HrConfidence hr_confidence = 1;
bool is_active = 2;
bool is_on_skin = 3;
uint32 battery = 4;
}
message HrPacket {
StatusFlag status = 1;
uint32 id = 2;
uint32 packet_num = 3;
uint32 hr = 4;
}
message StepCountPacket {
uint32 id = 1;
uint32 packet_num = 2;
uint32 step_count = 3;
}
message RawPacketStatus {
uint32 signal_rssi_x2_neg = 1;
int32 snr_pkt_x4 = 2;
}
message PacketStatus {
float signal_rssi_neg = 1;
float snr_pkt = 2;
}
message HubInfo {
uint32 bus_id = 1;
uint32 sub_dev_id = 2;
LoRaParameters radio_parameters = 3;
}
message RadioData {
HubInfo hub_info = 1;
RawPacketStatus raw_packet_status = 2;
bytes data = 3;
}
enum PacketKind {
NTF = 0;
SIG = 1;
INF = 2;
CNF = 3;
REQ = 4;
RSP = 5;
}
message GatewayStateReport {
bool network_has_connection = 1;
bool network_has_ip = 2;
bool network_has_mqtt_connection = 3;
bool bluetooth_has_connect = 4;
uint32 error_code = 5;
}
message HubSlaveOutGatewayMasterInMsg {
oneof choice {
RadioData ntf_radio_data = 1;
BatteryInfo ntf_battery_info = 2;
}
}
message GatewayMasterOutHubSlaveInMsg {
oneof choice {
GatewayStateReport sig_gateway_state_report = 1;
}
}
message GatewaySlaveOutCloudMasterInMsg {
oneof choice {
HrMeasurement ntf_hr_measurement = 1;
GatewayStatus ntf_gateway_status = 2;
StepCountMeasurement ntf_step_count_measurement = 3;
}
}
message BatteryInfo {
uint32 voltage_mv = 1;
uint32 soc_percentage = 2;
sint32 charging_rate_percentage = 3;
}
message GatewayInfo {
uint32 region_id = 1;
bytes gateway_mac = 2;
}
message IPacketStatus {
oneof choice {
RawPacketStatus raw = 1;
PacketStatus parsed = 2;
}
}
message HrMeasurement {
HrPacket hr_packet = 1;
IPacketStatus packet_status = 2;
GatewayInfo gateway_info = 3;
HubInfo hub_info = 4;
}
message StepCountMeasurement {
StepCountPacket step_count_packet = 1;
IPacketStatus packet_status = 2;
GatewayInfo gateway_info = 3;
HubInfo hub_info = 4;
}
message GatewayStatistic {
uint32 boot_count = 1;
uint32 uptime_ms = 2;
uint32 duration_ms_since_last_packet = 3;
uint32 rx_count = 4;
BatteryInfo battery_info = 5;
}
message GatewayStatus {
GatewayInfo info = 1;
GatewayStatistic stat = 2;
}
message Status {
int32 code = 1;
string message = 2;
}
enum GatewayConfigEntryKind {
REGION_ID = 0;
WIFI_SSID = 1;
WIFI_PASSWORD = 2;
}
message GatewayConfigEntry {
oneof choice {
uint32 region_id = 1;
string wifi_ssid = 2;
string wifi_password = 3;
}
}
message GatewayConfigMasterOutSlaveInMsg {
oneof choice {
GatewayConfigEntry cnf_get_entry = 1;
Status cnf_get_failure = 2;
Status cnf_set_entry = 3;
}
}
message GatewayConfigMasterInSlaveOutMsg {
oneof choice {
GatewayConfigEntryKind inf_get_entry = 1;
GatewayConfigEntry inf_set_entry = 2;
}
}