Skip to content

Commit

Permalink
Allow for sending value with preserved type
Browse files Browse the repository at this point in the history
Closes #2
  • Loading branch information
pakerfeldt committed Feb 9, 2025
1 parent d062809 commit b39e08d
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 19 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Version 1.1
Added configuration parameter emitValueAsString. When set to `false`, values will preserve its type when sent as `json`. If set to `true`, behaviour is compatible with previous version.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ The app supports several payload formats for MQTT messages:

| Type | Description | Requires KNX XML |
| -------- | ------- | ------- |
| `value` | String representation of the value (e.g., `24.52`) | Yes |
| `value` | the value (e.g., `24.42`) | Yes |
| `value-with-unit` | String representation including the unit (e.g., `24.42 °C`) | Yes |
| `json` | JSON representation of the message | Yes |
| `bytes` | Raw bytes as specified by KNX | No |
Expand All @@ -54,7 +54,7 @@ When sending JSON messages, you can choose to include/exclude the following fiel
| -------- | ------- |
| `bytes` | Raw bytes encoded in Base64 |
| `name` | Name of the group address |
| `value` | String representation of the value |
| `value` | Value with preserved type or as string representation if emitValueAsString is true |
| `unit` | Associated unit of the value |

## To KNX
Expand Down
6 changes: 4 additions & 2 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ outgoingMqttMessage:

# The message type to publish over MQTT when receiving events from KNX
# One of:
# value - a string representation of the value excluding the unit
# value - the value with preserved type or as string representation if emitValueAsString is true
# value-with-unit - a string representation of the value including the unit (if any)
# bytes - the raw bytes as received from KNX
# json - a json object containing the fields specified in includedJsonFields.
Expand All @@ -16,14 +16,16 @@ outgoingMqttMessage:
emitUsingAddress: true
# Emit values using human readable group address names
emitUsingName: true
# Emit the value payload as string, if false, the type will be preserved.
emitValueAsString: false

# When sending MQTT messages as JSON, these values determines what should be included.
includedJsonFields:
# Include the field `bytes`, with a base64 representation of the raw bytes
bytes: true
# Include the field `name`, containing the human readable name of the address
name: true
# Include the field `string`, containing a string representation of the value
# Include the field `value`, containing the raw value or as string representation if emitValueAsString is true
value: true
# Include the field `unit`, containing the unit of the datatype (if any)
unit: true
Expand Down
1 change: 1 addition & 0 deletions models/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type OutgoingMqttMessage struct {
Type string `yaml:"type"`
EmitUsingAddress bool `yaml:"emitUsingAddress"`
EmitUsingName bool `yaml:"emitUsingName"`
EmitValueAsString bool `yaml:"emitValueAsString"`
IncludedJsonFields IncludedJsonFields `yaml:"includedJsonFields"`
}

Expand Down
8 changes: 4 additions & 4 deletions models/mqtt.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package models

type OutgoingMqttJson struct {
Bytes *string `json:"bytes,omitempty"`
Name *string `json:"name,omitempty"`
Value *string `json:"value,omitempty"`
Unit *string `json:"unit,omitempty"`
Bytes *string `json:"bytes,omitempty"`
Name *string `json:"name,omitempty"`
Value interface{} `json:"value,omitempty"`
Unit *string `json:"unit,omitempty"`
}
26 changes: 15 additions & 11 deletions protocols/knx_receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"strings"

mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/pakerfeldt/knx-mqtt/models"
Expand Down Expand Up @@ -45,12 +44,12 @@ func incomingKnxEventHandler(event knx.GroupEvent, mqttClient mqtt.Client, knxIt
return
}
groupAddress := knxItems.GroupAddresses[index]
dpt, ok := dpt.Produce(groupAddress.Datapoint)
datapoint, ok := dpt.Produce(groupAddress.Datapoint)
if ok {
dpt.Unpack(event.Data)
log.Debug().Str("protocol", "knx").Str("address", event.Destination.String()).Str("name", groupAddress.Name).Str("value", dpt.String()).Msg("Incoming")
datapoint.Unpack(event.Data)
log.Debug().Str("protocol", "knx").Str("address", event.Destination.String()).Str("name", groupAddress.Name).Str("value", datapoint.String()).Msg("Incoming")

payload, err := constructPayload(dpt, mqttMessageCfg.Type, &mqttMessageCfg.IncludedJsonFields, &groupAddress.Name)
payload, err := constructPayload(datapoint, groupAddress.Datapoint, mqttMessageCfg.EmitValueAsString, mqttMessageCfg.Type, &mqttMessageCfg.IncludedJsonFields, &groupAddress.Name)
if err != nil {
return
}
Expand All @@ -66,11 +65,9 @@ func incomingKnxEventHandler(event knx.GroupEvent, mqttClient mqtt.Client, knxIt
}
}

func constructPayload(dpt dpt.Datapoint, messageType string, jsonFields *models.IncludedJsonFields, addressName *string) (interface{}, error) {
func constructPayload(dpt dpt.Datapoint, dptType string, emitValueAsString bool, messageType string, jsonFields *models.IncludedJsonFields, addressName *string) (interface{}, error) {
var payload interface{}
if messageType == models.JsonType {
stringWithoutSuffix := utils.StringWithoutSuffix(dpt)

outgoingJson := models.OutgoingMqttJson{}
if jsonFields.IncludeBytes {
base64 := base64.StdEncoding.EncodeToString(dpt.Pack())
Expand All @@ -80,21 +77,28 @@ func constructPayload(dpt dpt.Datapoint, messageType string, jsonFields *models.
outgoingJson.Name = addressName
}
if jsonFields.IncludeValue {
outgoingJson.Value = &stringWithoutSuffix
if emitValueAsString {
outgoingJson.Value = utils.StringWithoutSuffix(dpt)
} else {
outgoingJson.Value = utils.ExtractDatapointValue(dpt, dptType)
}
}
if jsonFields.IncludeUnit {
unit := dpt.Unit()
outgoingJson.Unit = &unit
}

jsonBytes, err := json.Marshal(outgoingJson)
if err != nil {
log.Error().Str("error", fmt.Sprintf("%+v", err)).Msg("Failed to create outgoing JSON message")
return nil, err
}
payload = string(jsonBytes)
} else if messageType == models.ValueType {
payload = strings.Trim(strings.TrimSuffix(dpt.String(), dpt.Unit()), " ")
if emitValueAsString {
payload = utils.StringWithoutSuffix(dpt)
} else {
payload = fmt.Sprintf("%v", utils.ExtractDatapointValue(dpt, dptType))
}
} else if messageType == models.ValueWithUnitType {
payload = dpt.String()
} else if messageType == models.BytesType {
Expand Down
57 changes: 57 additions & 0 deletions utils/knx_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package utils

import (
"fmt"
"reflect"
"regexp"
"strconv"
"strings"

"github.com/vapourismo/knx-go/knx/dpt"
)
Expand Down Expand Up @@ -456,3 +458,58 @@ var stringPackFunctions = map[string]func(string) []byte{
return datapoint.Pack()
},
}

func ExtractDatapointValue(dp dpt.Datapoint, dptType string) interface{} {
mainType := strings.SplitN(dptType, ".", 2)[0]
rv := reflect.ValueOf(dp)
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
switch mainType {
case "1":
if rv.Kind() == reflect.Bool {
return rv.Bool()
}
case "6":
if rv.Kind() == reflect.Int8 {
return int8(rv.Int())
}
case "7":
if rv.Kind() == reflect.Uint16 {
return uint16(rv.Uint())
}
case "9", "14":
if rv.Kind() == reflect.Float32 {
return float32(rv.Float())
}
case "12":
if rv.Kind() == reflect.Uint32 {
return uint32(rv.Uint())
}
case "13":
if rv.Kind() == reflect.Int32 {
return int32(rv.Int())
}
case "17", "18", "20":
if rv.Kind() == reflect.Uint8 {
return uint8(rv.Uint())
}
}

switch dptType {
case "5.001", "5.003", "8.003", "8.004", "8.010":
if rv.Kind() == reflect.Float32 {
return float32(rv.Float())
}
case "5.004", "5.005":
if rv.Kind() == reflect.Uint8 {
return uint8(rv.Uint())
}
case "8.001", "8.002", "8.005", "8.006", "8.007", "8.011":
if rv.Kind() == reflect.Int16 {
return int16(rv.Int())
}
}

return StringWithoutSuffix(dp)
}

0 comments on commit b39e08d

Please sign in to comment.