feat: mqtt receive.
This commit is contained in:
@@ -8,3 +8,13 @@ ai:
|
||||
base_url: https://api.lkeap.cloud.tencent.com/v1
|
||||
api_key: ""
|
||||
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
|
||||
|
||||
@@ -26,9 +26,22 @@ type AIConfig struct {
|
||||
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 {
|
||||
DB DBConfig `mapstructure:"database" yaml:"database"`
|
||||
AI AIConfig `mapstructure:"ai" yaml:"ai"`
|
||||
MQTT MQTTConfig `mapstructure:"mqtt" yaml:"mqtt"`
|
||||
}
|
||||
|
||||
func InitConfig() {
|
||||
|
||||
@@ -19,6 +19,7 @@ require (
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // 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/fumiama/imgsz v0.0.2 // 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-viper/mapstructure/v2 v2.2.1 // 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/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||
|
||||
@@ -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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
|
||||
@@ -3,7 +3,9 @@ package main
|
||||
import (
|
||||
"hr_receiver/config"
|
||||
"hr_receiver/models"
|
||||
"hr_receiver/mqtt"
|
||||
"hr_receiver/routes"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -25,8 +27,15 @@ func main() {
|
||||
&models.StepStrideFreq{},
|
||||
&models.RegressionResult{},
|
||||
&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.Run(":8081")
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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", ®ion); err == nil {
|
||||
return region
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user