Compare commits

..

4 Commits

Author SHA1 Message Date
f63c22912a float fix 2
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2026-03-06 20:57:32 +01:00
c37420a993 float fix 1
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2026-03-06 20:51:50 +01:00
ac0c417a48 small j for float 3
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2026-03-06 20:34:21 +01:00
dc965aeba6 small j for float 2
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2026-03-06 20:27:28 +01:00
4 changed files with 244 additions and 238 deletions

View File

@@ -105,6 +105,7 @@
"devicePart": "3", "devicePart": "3",
"valueFrom": "payload", "valueFrom": "payload",
"valuePart": "1", "valuePart": "1",
"valueType": "float",
"unitFrom": "payload", "unitFrom": "payload",
"unitPart": "3" "unitPart": "3"
} }
@@ -185,7 +186,7 @@
"attributes": { "attributes": {
"application": "Shellies Sensor Temperature", "application": "Shellies Sensor Temperature",
"deviceSelector": "T:2", "deviceSelector": "T:2",
"valueSelector": "j:$.tC", "valueSelector": "J:$.tC",
"unitSelector": "C:°C" "unitSelector": "C:°C"
} }
} }
@@ -199,7 +200,7 @@
"attributes": { "attributes": {
"application": "Shellies Sensor Humidity", "application": "Shellies Sensor Humidity",
"deviceSelector": "T:2", "deviceSelector": "T:2",
"valueSelector": "j:$.rh", "valueSelector": "J:$.rh",
"unitSelector": "C:%" "unitSelector": "C:%"
} }
} }
@@ -213,7 +214,7 @@
"attributes": { "attributes": {
"application": "Shellies Sensor Power", "application": "Shellies Sensor Power",
"deviceSelector": "T:2", "deviceSelector": "T:2",
"valueSelector": "j:$.battery.percent", "valueSelector": "J:$.battery.percent",
"unitSelector": "C:%" "unitSelector": "C:%"
} }
} }

View File

@@ -1,81 +1,75 @@
package dt1t package dt1t
import ( import (
"log" "log"
"fmt" "strconv"
"time" "time"
"strconv" "udi/config"
"udi/handlers/handler" "udi/database"
"udi/database" "udi/handlers/handler"
"udi/config"
) )
type Dt1tHandler struct { type Dt1tHandler struct {
handler.CommonHandler handler.CommonHandler
ready bool ready bool
label string label string
dbh *database.DatabaseHandle dbh *database.DatabaseHandle
application string application string
device string device string
} }
func New(id string, config config.HandlerConfigT) handler.Handler { func New(id string, config config.HandlerConfigT) handler.Handler {
t := &Dt1tHandler { t := &Dt1tHandler{}
}
if config.Attributes["Application"] == "" { if config.Attributes["Application"] == "" {
log.Println("Error: application not configured") log.Println("Error: application not configured")
return t return t
} }
t.application = config.Attributes["Application"] t.application = config.Attributes["Application"]
if config.Attributes["Device"] == "" { if config.Attributes["Device"] == "" {
log.Println("Error: device not configured") log.Println("Error: device not configured")
return t return t
} }
t.device = config.Attributes["Device"] t.device = config.Attributes["Device"]
t.Id = id t.Id = id
t.dbh = database.NewDatabaseHandle() t.dbh = database.NewDatabaseHandle()
log.Printf("Handler DT1T %d initialized", id) log.Printf("Handler DT1T %d initialized", id)
t.ready = true t.ready = true
return t return t
} }
func (self *Dt1tHandler) Handle(message handler.MessageT) { func (self *Dt1tHandler) Handle(message handler.MessageT) {
if ! self.ready { if !self.ready {
self.Lost("Handler is not marked as ready", nil, message) self.Lost("Handler is not marked as ready", nil, message)
return return
} }
// log.Printf("Handler DT1T %d processing %s -> %s", self.id, message.Topic, message.Payload) // log.Printf("Handler DT1T %d processing %s -> %s", self.id, message.Topic, message.Payload)
temperature, err := strconv.Atoi(message.Payload) temperature, err := strconv.Atoi(message.Payload)
if err != nil { if err != nil {
self.Lost("Invalid raw value", err, message) self.Lost("Invalid raw value", err, message)
return return
} }
if temperature & 0x8000 != 0{ if temperature&0x8000 != 0 {
temperature = ((temperature - 1) ^ 0xffff) * -1 temperature = ((temperature - 1) ^ 0xffff) * -1
} }
temperatureF := float32(temperature) / 10.0 temperatureF := float32(temperature) / 10.0
var measurement database.Measurement var measurement database.Measurement
measurement.Time = time.Now() measurement.Time = time.Now()
measurement.Application = self.application measurement.Application = self.application
measurement.Device = self.device measurement.Device = self.device
var variable database.VariableType
variable.Label = "Temperature"
variable.Variable = ""
variable.Unit = "°C"
variable.Value = fmt.Sprintf("%f", temperatureF)
measurement.Values = make(map[string]database.VariableType)
measurement.Values["Value"] = variable
// log.Printf("Prepared measurement item: %s", measurement) var variable database.VariableType
self.dbh.StoreMeasurement(&measurement) variable.Label = "Temperature"
self.S() variable.Variable = ""
variable.Unit = "°C"
variable.Value = temperatureF
measurement.Values = make(map[string]database.VariableType)
measurement.Values["Value"] = variable
// log.Printf("Prepared measurement item: %s", measurement)
self.dbh.StoreMeasurement(&measurement)
self.S()
} }

View File

@@ -46,7 +46,7 @@ func New(id string, config config.HandlerConfigT) handler.Handler {
t.application = config.Attributes["application"] t.application = config.Attributes["application"]
t.deviceSelector = config.Attributes["deviceSelector"] t.deviceSelector = config.Attributes["deviceSelector"]
if t.deviceSelector[:2] == "J:" || t.deviceSelector[:2] == "j:" { if t.deviceSelector[:2] == "J:" {
jp, err := jsonpath.Compile(t.deviceSelector[2:]) jp, err := jsonpath.Compile(t.deviceSelector[2:])
if err != nil { if err != nil {
log.Printf("Unable to compile deviceJsonpath: %s, %s", t.deviceSelector[2:], err) log.Printf("Unable to compile deviceJsonpath: %s, %s", t.deviceSelector[2:], err)
@@ -55,7 +55,7 @@ func New(id string, config config.HandlerConfigT) handler.Handler {
t.deviceJsonpath = jp t.deviceJsonpath = jp
} }
t.valueSelector = config.Attributes["valueSelector"] t.valueSelector = config.Attributes["valueSelector"]
if t.valueSelector[:2] == "J:" || t.valueSelector[:2] == "j:" { if t.valueSelector[:2] == "J:" {
jp, err := jsonpath.Compile(t.valueSelector[2:]) jp, err := jsonpath.Compile(t.valueSelector[2:])
if err != nil { if err != nil {
log.Printf("Unable to compile valueJsonpath: %s, %s", t.valueSelector[2:], err) log.Printf("Unable to compile valueJsonpath: %s, %s", t.valueSelector[2:], err)
@@ -64,7 +64,7 @@ func New(id string, config config.HandlerConfigT) handler.Handler {
t.valueJsonpath = jp t.valueJsonpath = jp
} }
t.unitSelector = config.Attributes["unitSelector"] t.unitSelector = config.Attributes["unitSelector"]
if t.unitSelector[:2] == "J:" || t.unitSelector[:2] == "j:" { if t.unitSelector[:2] == "J:" {
jp, err := jsonpath.Compile(t.unitSelector[2:]) jp, err := jsonpath.Compile(t.unitSelector[2:])
if err != nil { if err != nil {
log.Printf("Unable to compile unitJsonpath: %s, %s", t.unitSelector[2:], err) log.Printf("Unable to compile unitJsonpath: %s, %s", t.unitSelector[2:], err)
@@ -83,17 +83,13 @@ func New(id string, config config.HandlerConfigT) handler.Handler {
func (self *SingleValueExtractorJsonpathHandler) ExtractionHelper(subTopics []string, jPayload interface{}, selector string, jp *jsonpath.Compiled) (interface{}, error) { func (self *SingleValueExtractorJsonpathHandler) ExtractionHelper(subTopics []string, jPayload interface{}, selector string, jp *jsonpath.Compiled) (interface{}, error) {
var res interface{} var res interface{}
switch selector[:2] { switch selector[:2] {
case "J:", "j:": case "J:":
// extract using jsonpath from payload // extract using jsonpath from payload
r, e := jp.Lookup(jPayload) r, e := jp.Lookup(jPayload)
if e != nil { if e != nil {
return "", fmt.Errorf("jp.Lookup failed with %s", e) return "", fmt.Errorf("jp.Lookup failed with %s", e)
} }
if selector[:2] == "j:" { res = r
res = r.(float64)
} else {
res = fmt.Sprint(r)
}
case "T:": case "T:":
// T: extract from topic // T: extract from topic
i, e := strconv.Atoi(selector[2:]) i, e := strconv.Atoi(selector[2:])
@@ -152,12 +148,12 @@ func (self *SingleValueExtractorJsonpathHandler) Handle(message handler.MessageT
return return
} }
measurement.Device = device measurement.Device = device.(string)
var variable database.VariableType var variable database.VariableType
variable.Label = "" variable.Label = ""
variable.Variable = "" variable.Variable = ""
variable.Unit = unit variable.Unit = unit.(string)
variable.Value = value variable.Value = value
measurement.Values = make(map[string]database.VariableType) measurement.Values = make(map[string]database.VariableType)
measurement.Values["Value"] = variable measurement.Values["Value"] = variable

View File

@@ -1,23 +1,22 @@
package sver package sver
import ( import (
"time" "log"
"strconv" "regexp"
"strings" "strconv"
"regexp" "strings"
"log" "time"
"udi/config" "udi/config"
"udi/handlers/handler" "udi/database"
"udi/database" "udi/handlers/handler"
) )
type SingleValueExtractorRegexHandler struct { type SingleValueExtractorRegexHandler struct {
handler.CommonHandler handler.CommonHandler
ready bool ready bool
config localConfig config localConfig
payloadRegex *regexp.Regexp payloadRegex *regexp.Regexp
dbh *database.DatabaseHandle dbh *database.DatabaseHandle
} }
const TOPIC_SEL = "topic" const TOPIC_SEL = "topic"
@@ -26,175 +25,191 @@ const PAYLOAD_FULL_SEL = "payload-full"
const CONSTANT_SEL = "constant" const CONSTANT_SEL = "constant"
type localConfig struct { type localConfig struct {
application string application string
deviceFrom string deviceFrom string
devicePart int devicePart int
device string device string
valueFrom string valueFrom string
valuePart int valuePart int
unitFrom string valueType string
unitPart int unitFrom string
unit string unitPart int
unit string
} }
func New(id string, config config.HandlerConfigT) handler.Handler { func New(id string, config config.HandlerConfigT) handler.Handler {
t := &SingleValueExtractorRegexHandler { t := &SingleValueExtractorRegexHandler{
ready: false, ready: false,
} }
var localConfig localConfig var localConfig localConfig
if config.Attributes["application"] == "" { if config.Attributes["application"] == "" {
log.Println("Error: application not configured") log.Println("Error: application not configured")
return t return t
} }
localConfig.application = config.Attributes["application"] localConfig.application = config.Attributes["application"]
payloadRegex := config.Attributes["payloadRegex"]
if payloadRegex != "" {
t.payloadRegex = regexp.MustCompile(payloadRegex)
} else {
t.payloadRegex = nil
}
if config.Attributes["deviceFrom"] != TOPIC_SEL && config.Attributes["deviceFrom"] != PAYLOAD_SEL && config.Attributes["deviceFrom"] != CONSTANT_SEL { payloadRegex := config.Attributes["payloadRegex"]
log.Printf("Error: invalid value %s for deviceFrom", config.Attributes["deviceFrom"]) if payloadRegex != "" {
return t t.payloadRegex = regexp.MustCompile(payloadRegex)
} } else {
localConfig.deviceFrom = config.Attributes["deviceFrom"] t.payloadRegex = nil
}
devicePart, err1 := strconv.Atoi(config.Attributes["devicePart"]) if config.Attributes["deviceFrom"] != TOPIC_SEL && config.Attributes["deviceFrom"] != PAYLOAD_SEL && config.Attributes["deviceFrom"] != CONSTANT_SEL {
if err1 != nil { log.Printf("Error: invalid value %s for deviceFrom", config.Attributes["deviceFrom"])
log.Printf("Error: unable to convert devicePart to number: %s", err1) return t
return t }
} localConfig.deviceFrom = config.Attributes["deviceFrom"]
localConfig.devicePart = devicePart
// empty device is valid devicePart, err1 := strconv.Atoi(config.Attributes["devicePart"])
localConfig.device = config.Attributes["device"] if err1 != nil {
log.Printf("Error: unable to convert devicePart to number: %s", err1)
return t
}
localConfig.devicePart = devicePart
if config.Attributes["valueFrom"] != PAYLOAD_SEL && config.Attributes["valueFrom"] != PAYLOAD_FULL_SEL { // empty device is valid
log.Printf("Error: invalid value %s for valueFrom", config.Attributes["valueFrom"]) localConfig.device = config.Attributes["device"]
return t
}
localConfig.valueFrom = config.Attributes["valueFrom"]
if config.Attributes["valueFrom"] == PAYLOAD_SEL { if config.Attributes["valueFrom"] != PAYLOAD_SEL && config.Attributes["valueFrom"] != PAYLOAD_FULL_SEL {
valuePart, err2 := strconv.Atoi(config.Attributes["valuePart"]) log.Printf("Error: invalid value %s for valueFrom", config.Attributes["valueFrom"])
if err2 != nil { return t
log.Printf("Error: unable to convert valuePart to number: %s", err2) }
return t localConfig.valueFrom = config.Attributes["valueFrom"]
}
localConfig.valuePart = valuePart
}
if config.Attributes["unitFrom"] != PAYLOAD_SEL && config.Attributes["unitFrom"] != CONSTANT_SEL { if config.Attributes["valueFrom"] == PAYLOAD_SEL {
log.Printf("Error: invalid value %s for unitFrom", config.Attributes["unitFrom"]) valuePart, err2 := strconv.Atoi(config.Attributes["valuePart"])
return t if err2 != nil {
} log.Printf("Error: unable to convert valuePart to number: %s", err2)
localConfig.unitFrom = config.Attributes["unitFrom"] return t
}
localConfig.valuePart = valuePart
}
if config.Attributes["unitFrom"] == PAYLOAD_SEL { if config.Attributes["valueType"] != "float" && config.Attributes["valueType"] != "string" {
unitPart, err3 := strconv.Atoi(config.Attributes["unitPart"]) log.Printf("Error: invalid value %s for valueType", config.Attributes["valueType"])
if err3 != nil { return t
log.Printf("Error: unable to convert unitPart to number: %s", err3) }
return t localConfig.valueType = config.Attributes["valueType"]
}
localConfig.unitPart = unitPart
}
// empty unit is valid if config.Attributes["unitFrom"] != PAYLOAD_SEL && config.Attributes["unitFrom"] != CONSTANT_SEL {
localConfig.unit = config.Attributes["unit"] log.Printf("Error: invalid value %s for unitFrom", config.Attributes["unitFrom"])
return t
}
localConfig.unitFrom = config.Attributes["unitFrom"]
t.config = localConfig if config.Attributes["unitFrom"] == PAYLOAD_SEL {
unitPart, err3 := strconv.Atoi(config.Attributes["unitPart"])
if err3 != nil {
log.Printf("Error: unable to convert unitPart to number: %s", err3)
return t
}
localConfig.unitPart = unitPart
}
t.Id = id // empty unit is valid
t.ready = true localConfig.unit = config.Attributes["unit"]
t.dbh = database.NewDatabaseHandle()
log.Printf("Handler SVER %d initialized", id) t.config = localConfig
return t
t.Id = id
t.ready = true
t.dbh = database.NewDatabaseHandle()
log.Printf("Handler SVER %d initialized", id)
return t
} }
func (self *SingleValueExtractorRegexHandler) Handle(message handler.MessageT) { func (self *SingleValueExtractorRegexHandler) Handle(message handler.MessageT) {
if ! self.ready { if !self.ready {
self.Lost("Handler is not marked as ready", nil, message) self.Lost("Handler is not marked as ready", nil, message)
return return
} }
//log.Printf("Handler SingleValueExtractor %d processing %s -> %s", self.id, message.Topic, message.Payload) //log.Printf("Handler SingleValueExtractor %d processing %s -> %s", self.id, message.Topic, message.Payload)
var measurement database.Measurement var measurement database.Measurement
measurement.Time = time.Now() measurement.Time = time.Now()
measurement.Application = self.config.application measurement.Application = self.config.application
subTopics := strings.Split(message.Topic, "/")
//log.Printf("Subtopics: %s", strings.Join(subTopics, ", "))
var payloadMatches []string subTopics := strings.Split(message.Topic, "/")
if self.payloadRegex != nil { //log.Printf("Subtopics: %s", strings.Join(subTopics, ", "))
payloadMatches = self.payloadRegex.FindStringSubmatch(message.Payload)
//log.Printf("Matches: %s", strings.Join(payloadMatches, ", "))
}
switch self.config.deviceFrom { var payloadMatches []string
case TOPIC_SEL: if self.payloadRegex != nil {
if self.config.devicePart >= len(subTopics) { payloadMatches = self.payloadRegex.FindStringSubmatch(message.Payload)
self.Lost("devicePart out of range", nil, message) //log.Printf("Matches: %s", strings.Join(payloadMatches, ", "))
return }
}
measurement.Device = subTopics[self.config.devicePart]
case PAYLOAD_SEL:
if self.payloadRegex == nil {
self.Lost("no payloadRegex defined, devicePart can't be used", nil, message)
return
}
if self.config.devicePart >= len(payloadMatches) {
self.Lost("devicePart out of range", nil, message)
return
}
measurement.Device = payloadMatches[self.config.devicePart]
case CONSTANT_SEL:
measurement.Device = self.config.device
}
measurement.Values = make(map[string]database.VariableType) switch self.config.deviceFrom {
var variable database.VariableType case TOPIC_SEL:
variable.Label = "" if self.config.devicePart >= len(subTopics) {
variable.Variable = "" self.Lost("devicePart out of range", nil, message)
return
}
measurement.Device = subTopics[self.config.devicePart]
case PAYLOAD_SEL:
if self.payloadRegex == nil {
self.Lost("no payloadRegex defined, devicePart can't be used", nil, message)
return
}
if self.config.devicePart >= len(payloadMatches) {
self.Lost("devicePart out of range", nil, message)
return
}
measurement.Device = payloadMatches[self.config.devicePart]
case CONSTANT_SEL:
measurement.Device = self.config.device
}
switch self.config.valueFrom { measurement.Values = make(map[string]database.VariableType)
case PAYLOAD_SEL: var variable database.VariableType
if self.payloadRegex == nil { variable.Label = ""
self.Lost("no payloadRegex defined, valuePart can't be used", nil, message) variable.Variable = ""
return
}
if self.config.valuePart >= len(payloadMatches) {
self.Lost("valuePart out of range", nil, message)
return
}
variable.Value = payloadMatches[self.config.valuePart]
case PAYLOAD_FULL_SEL:
variable.Value = message.Payload
}
switch self.config.unitFrom { var value string
case PAYLOAD_SEL: switch self.config.valueFrom {
if self.payloadRegex == nil { case PAYLOAD_SEL:
self.Lost("no payloadRegex defined, unitPart can't be used", nil, message) if self.payloadRegex == nil {
return self.Lost("no payloadRegex defined, valuePart can't be used", nil, message)
} return
if self.config.unitPart >= len(payloadMatches) { }
self.Lost("unitPart out of range", nil, message) if self.config.valuePart >= len(payloadMatches) {
return self.Lost("valuePart out of range", nil, message)
} return
variable.Unit = payloadMatches[self.config.unitPart] }
case CONSTANT_SEL: value = payloadMatches[self.config.valuePart]
variable.Unit = self.config.unit case PAYLOAD_FULL_SEL:
} value = message.Payload
}
if self.config.valueType == "float" {
fValue, err := strconv.ParseFloat(value, 64)
if err != nil {
self.Lost("Unable to convert value to float", err, message)
return
}
variable.Value = fValue
} else {
variable.Value = value
}
measurement.Values["Value"] = variable switch self.config.unitFrom {
case PAYLOAD_SEL:
if self.payloadRegex == nil {
self.Lost("no payloadRegex defined, unitPart can't be used", nil, message)
return
}
if self.config.unitPart >= len(payloadMatches) {
self.Lost("unitPart out of range", nil, message)
return
}
variable.Unit = payloadMatches[self.config.unitPart]
case CONSTANT_SEL:
variable.Unit = self.config.unit
}
//log.Printf("Prepared measurement item: %s", measurement) measurement.Values["Value"] = variable
self.dbh.StoreMeasurement(&measurement)
self.S() //log.Printf("Prepared measurement item: %s", measurement)
self.dbh.StoreMeasurement(&measurement)
self.S()
} }