Compare commits

...

4 Commits

Author SHA1 Message Date
0f6590c720 float fix 4
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2026-03-06 21:49:51 +01:00
42ff8b51ed float fix 3
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2026-03-06 21:35:32 +01:00
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
5 changed files with 296 additions and 291 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"
} }

View File

@@ -34,20 +34,20 @@ type CarHandler struct {
*/ */
type CarValue struct { type CarValue struct {
Status string `unit:"" json:"status"` Status string `unit:"" json:"status"`
Timestamp string `unit:"" json:"timestamp"` Timestamp string `unit:"" json:"timestamp"`
VoltageL1 float32 `unit:"V" json:"voltageL1"` VoltageL1 float32 `unit:"V" json:"voltageL1"`
VoltageL2 float32 `unit:"V" json:"voltageL2"` VoltageL2 float32 `unit:"V" json:"voltageL2"`
VoltageL3 float32 `unit:"V" json:"voltageL3"` VoltageL3 float32 `unit:"V" json:"voltageL3"`
CurrentL1 float32 `unit:"A" json:"currentL1"` CurrentL1 float32 `unit:"A" json:"currentL1"`
CurrentL2 float32 `unit:"A" json:"currentL2"` CurrentL2 float32 `unit:"A" json:"currentL2"`
CurrentL3 float32 `unit:"A" json:"currentL3"` CurrentL3 float32 `unit:"A" json:"currentL3"`
PowerL1 float32 `unit:"W" json:"powerL1"` PowerL1 float32 `unit:"W" json:"powerL1"`
PowerL2 float32 `unit:"W" json:"powerL2"` PowerL2 float32 `unit:"W" json:"powerL2"`
PowerL3 float32 `unit:"W" json:"powerL3"` PowerL3 float32 `unit:"W" json:"powerL3"`
TotalImportEnergy float32 `unit:"Wh" json:"totalImportEnergy"` TotalImportEnergy float32 `unit:"Wh" json:"totalImportEnergy"`
TotalExportEnergy float32 `unit:"Wh" json:"totalExportEnergy"` TotalExportEnergy float32 `unit:"Wh" json:"totalExportEnergy"`
Cnt int `unit:"" json:"cnt"` Cnt int `unit:"" json:"cnt"`
} }
func New(id string, config config.HandlerConfigT) handler.Handler { func New(id string, config config.HandlerConfigT) handler.Handler {

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

@@ -1,79 +1,74 @@
package prepared package prepared
import ( import (
"time" "encoding/json"
"log" "log"
"encoding/json" "time"
"udi/config" "udi/config"
"udi/handlers/handler" "udi/database"
"udi/database" "udi/handlers/handler"
) )
type PreparedHandler struct { type PreparedHandler struct {
handler.CommonHandler handler.CommonHandler
dbh *database.DatabaseHandle dbh *database.DatabaseHandle
} }
type endpoint_t struct { type endpoint_t struct {
Label string `json:"label"` Label string `json:"label"`
Variable string `json:"variable"` Variable string `json:"variable"`
Value string `json:"value"` Value interface{} `json:"value"`
Unit string `json:"unit"` Unit string `json:"unit"`
Status string `json:"status"` Status string `json:"status"`
} }
type observation_t struct { type observation_t struct {
Device string `json:"device"` Device string `json:"device"`
Label string `json:"label"` Label string `json:"label"`
Variables map[string]endpoint_t `json:"variables"` Variables map[string]endpoint_t `json:"variables"`
} }
func New(id string, config config.HandlerConfigT) handler.Handler { func New(id string, config config.HandlerConfigT) handler.Handler {
t := &PreparedHandler { t := &PreparedHandler{}
} t.Id = id
t.Id = id t.dbh = database.NewDatabaseHandle()
t.dbh = database.NewDatabaseHandle() log.Printf("Handler Prepared %d initialized", id)
log.Printf("Handler Prepared %d initialized", id) return t
return t
} }
func (self *PreparedHandler) Handle(message handler.MessageT) { func (self *PreparedHandler) Handle(message handler.MessageT) {
log.Printf("Handler Prepared %d processing %s -> %s", self.Id, message.Topic, message.Payload) log.Printf("Handler Prepared %d processing %s -> %s", self.Id, message.Topic, message.Payload)
var observation observation_t var observation observation_t
err := json.Unmarshal([]byte(message.Payload), &observation) err := json.Unmarshal([]byte(message.Payload), &observation)
if err != nil { if err != nil {
self.Lost("Unable to parse payload into Observation struct", err, message) self.Lost("Unable to parse payload into Observation struct", err, message)
return return
} }
var measurement database.Measurement var measurement database.Measurement
measurement.Time = time.Now() measurement.Time = time.Now()
measurement.Application = self.Id measurement.Application = self.Id
measurement.Device = observation.Device measurement.Device = observation.Device
measurement.Attributes = map[string]interface{} { measurement.Attributes = map[string]interface{}{
"Label": observation.Label, "Label": observation.Label,
} }
measurement.Values = make(map[string]database.VariableType) measurement.Values = make(map[string]database.VariableType)
for k, v := range observation.Variables { for k, v := range observation.Variables {
measurement.Values[k] = database.VariableType { measurement.Values[k] = database.VariableType{
Label: v.Label, Label: v.Label,
Variable: v.Variable, Variable: v.Variable,
Unit: v.Unit, Unit: v.Unit,
Value: v.Value, Value: v.Value,
Status: v.Status, Status: v.Status,
} }
} }
log.Printf("Prepared measurement item: %s", measurement) log.Printf("Prepared measurement item: %s", measurement)
self.dbh.StoreMeasurement(&measurement) self.dbh.StoreMeasurement(&measurement)
self.S() self.S()
} }

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()
} }