Mqtt: improve publishing structs (#18171)
This commit is contained in:
parent
9ae0f1a47b
commit
36f00052ee
3 changed files with 108 additions and 31 deletions
|
|
@ -3,7 +3,10 @@ package server
|
|||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// pass converts a simple api without return value to api with nil error return value
|
||||
|
|
@ -22,3 +25,10 @@ func parseFloat(payload string) (float64, error) {
|
|||
}
|
||||
return f, err
|
||||
}
|
||||
|
||||
// omitEmpty returns true if struct field is omitempty
|
||||
func omitEmpty(f reflect.StructField) bool {
|
||||
tag := f.Tag.Get("json")
|
||||
values := strings.Split(tag, ",")
|
||||
return slices.Contains(values, "omitempty")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,8 +101,13 @@ func (m *MQTT) publishComplex(topic string, retained bool, payload interface{})
|
|||
// loop struct
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
if f := typ.Field(i); f.IsExported() {
|
||||
n := f.Name
|
||||
m.publishComplex(fmt.Sprintf("%s/%s", topic, strings.ToLower(n[:1])+n[1:]), retained, val.Field(i).Interface())
|
||||
topic := fmt.Sprintf("%s/%s", topic, strings.ToLower(f.Name[:1])+f.Name[1:])
|
||||
|
||||
if val.Field(i).IsZero() && omitEmpty(f) {
|
||||
m.publishSingleValue(topic, retained, nil)
|
||||
} else {
|
||||
m.publishComplex(topic, retained, val.Field(i).Interface())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ package server
|
|||
|
||||
import (
|
||||
"math"
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
func TestMqttNaNInf(t *testing.T) {
|
||||
|
|
@ -16,51 +18,111 @@ func TestMqttNaNInf(t *testing.T) {
|
|||
assert.Equal(t, "+Inf", m.encode(math.Inf(0)), "Inf not encoded as string")
|
||||
}
|
||||
|
||||
type measurement struct {
|
||||
Power float64 `json:"power"`
|
||||
Energy float64 `json:"energy,omitempty"`
|
||||
Currents []float64 `json:"currents,omitempty"`
|
||||
Controllable *bool `json:"controllable,omitempty"`
|
||||
}
|
||||
|
||||
func TestPublishTypes(t *testing.T) {
|
||||
var topics, payloads []string
|
||||
suite.Run(t, new(mqttSuite))
|
||||
}
|
||||
|
||||
reset := func() {
|
||||
topics = topics[:0]
|
||||
payloads = payloads[:0]
|
||||
type mqttSuite struct {
|
||||
suite.Suite
|
||||
*MQTT
|
||||
topics, payloads []string
|
||||
}
|
||||
|
||||
func (suite *mqttSuite) publish(topic string, retained bool, payload interface{}) {
|
||||
suite.MQTT.publish(topic, retained, payload)
|
||||
}
|
||||
|
||||
func (suite *mqttSuite) publisher(topic string, retained bool, payload string) {
|
||||
if i := slices.Index(suite.topics, topic); i >= 0 {
|
||||
suite.topics[i] = topic
|
||||
suite.payloads[i] = payload
|
||||
} else {
|
||||
suite.topics = append(suite.topics, topic)
|
||||
suite.payloads = append(suite.payloads, payload)
|
||||
}
|
||||
}
|
||||
|
||||
m := &MQTT{
|
||||
publisher: func(topic string, retained bool, payload string) {
|
||||
topics = append(topics, topic)
|
||||
payloads = append(payloads, payload)
|
||||
},
|
||||
func (suite *mqttSuite) SetupSuite() {
|
||||
suite.MQTT = &MQTT{
|
||||
publisher: suite.publisher,
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *mqttSuite) SetupTest() {
|
||||
suite.topics = suite.topics[:0]
|
||||
suite.payloads = suite.payloads[:0]
|
||||
}
|
||||
|
||||
func (suite *mqttSuite) TestTime() {
|
||||
now := time.Now()
|
||||
m.publish("test", false, now)
|
||||
require.Len(t, topics, 1)
|
||||
assert.Equal(t, strconv.FormatInt(now.Unix(), 10), payloads[0], "time not encoded as unix timestamp")
|
||||
reset()
|
||||
suite.publish("test", false, now)
|
||||
suite.Require().Len(suite.topics, 1)
|
||||
suite.Equal(strconv.FormatInt(now.Unix(), 10), suite.payloads[0], "time not encoded as unix timestamp")
|
||||
}
|
||||
|
||||
m.publish("test", false, struct {
|
||||
func (suite *mqttSuite) TestBool() {
|
||||
suite.publish("test", false, false)
|
||||
suite.Require().Len(suite.topics, 1)
|
||||
suite.Equal("false", suite.payloads[0])
|
||||
}
|
||||
|
||||
func (suite *mqttSuite) TestStruct() {
|
||||
suite.publish("test", false, struct {
|
||||
Foo string
|
||||
}{
|
||||
Foo: "bar",
|
||||
})
|
||||
assert.Equal(t, []string{"test/foo"}, topics, "struct mismatch")
|
||||
assert.Equal(t, []string{"bar"}, payloads, "struct mismatch")
|
||||
reset()
|
||||
suite.Equal([]string{"test/foo"}, suite.topics, "topics")
|
||||
suite.Equal([]string{"bar"}, suite.payloads, "payloads")
|
||||
}
|
||||
|
||||
func (suite *mqttSuite) TestStructPointer() {
|
||||
i := 1
|
||||
m.publish("test", false, struct {
|
||||
suite.publish("test", false, struct {
|
||||
Foo, Bar *int
|
||||
}{
|
||||
Foo: &i,
|
||||
Bar: nil,
|
||||
})
|
||||
assert.Equal(t, []string{"test/foo", "test/bar"}, topics, "pointer mismatch")
|
||||
assert.Equal(t, []string{"1", ""}, payloads, "pointer mismatch")
|
||||
reset()
|
||||
|
||||
slice := []int{10, 20}
|
||||
m.publish("test", false, slice)
|
||||
require.Len(t, topics, 3)
|
||||
assert.Equal(t, []string{"test", "test/1", "test/2"}, topics, "slice mismatch")
|
||||
assert.Equal(t, []string{"2", "10", "20"}, payloads, "slice mismatch")
|
||||
reset()
|
||||
suite.Equal([]string{"test/foo", "test/bar"}, suite.topics, "topics")
|
||||
suite.Equal([]string{"1", ""}, suite.payloads, "payloads")
|
||||
}
|
||||
|
||||
func (suite *mqttSuite) TestSlice() {
|
||||
slice := []int{10, 20}
|
||||
suite.publish("test", false, slice)
|
||||
suite.Require().Len(suite.topics, 3)
|
||||
suite.Equal([]string{"test", "test/1", "test/2"}, suite.topics, "topics")
|
||||
suite.Equal([]string{"2", "10", "20"}, suite.payloads, "payloads")
|
||||
}
|
||||
|
||||
func (suite *mqttSuite) TestGrid() {
|
||||
topics := []string{"test/power", "test/energy", "test/currents", "test/controllable"}
|
||||
|
||||
suite.publish("test", false, measurement{})
|
||||
suite.Require().Len(suite.topics, 4)
|
||||
suite.Equal(topics, suite.topics, "topics")
|
||||
suite.Equal([]string{"0", "", "", ""}, suite.payloads, "payloads")
|
||||
|
||||
suite.publish("test", false, measurement{Energy: 1})
|
||||
suite.Require().Len(suite.topics, 4)
|
||||
suite.Equal(topics, suite.topics, "topics")
|
||||
suite.Equal([]string{"0", "1", "", ""}, suite.payloads, "payloads")
|
||||
|
||||
suite.publish("test", false, measurement{Controllable: lo.ToPtr(false)})
|
||||
suite.Require().Len(suite.topics, 4)
|
||||
suite.Equal(topics, suite.topics, "topics")
|
||||
suite.Equal([]string{"0", "", "", "false"}, suite.payloads, "payloads")
|
||||
|
||||
suite.publish("test", false, measurement{Currents: []float64{1, 2, 3}})
|
||||
suite.Require().Len(suite.topics, 7)
|
||||
suite.Equal([]string{"test/power", "test/energy", "test/currents", "test/controllable", "test/currents/1", "test/currents/2", "test/currents/3"}, suite.topics, "topics")
|
||||
suite.Equal([]string{"0", "", "3", "", "1", "2", "3"}, suite.payloads, "payloads")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue