changes for influxdb
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
import "gorm.io/gorm"
|
|
||||||
|
|
||||||
|
|
||||||
type VariableType struct {
|
type VariableType struct {
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
@@ -13,35 +11,28 @@ type VariableType struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Measurement struct {
|
type Measurement struct {
|
||||||
Time time.Time `gorm:"not null;primary_key"`
|
Time time.Time
|
||||||
Application string `gorm:"not null"`
|
Application string
|
||||||
Device string
|
Device string
|
||||||
Attributes map[string]interface{} `gorm:"serializer:json;type:jsonb"`
|
Attributes map[string]interface{}
|
||||||
Values map[string]VariableType `gorm:"serializer:json;type:jsonb"`
|
Values map[string]VariableType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simplified structures for backward compatibility
|
||||||
|
type DeviceType struct {
|
||||||
|
Label string
|
||||||
|
ModelIdentifier string
|
||||||
|
Attributes map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
gorm.Model
|
Label string
|
||||||
Label string `gorm:"not null"`
|
Attributes map[string]interface{}
|
||||||
Attributes map[string]interface{} `gorm:"serializer:json;type:jsonb"`
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type DeviceType struct {
|
|
||||||
gorm.Model
|
|
||||||
Label string `gorm:"not null"`
|
|
||||||
ModelIdentifier string
|
|
||||||
Attributes map[string]interface{} `gorm:"serializer:json;type:jsonb"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
gorm.Model
|
Label string
|
||||||
Label string `gorm:"not null;uniqueIndex:idx_label_application_id"`
|
|
||||||
ApplicationID int `gorm:"not null;uniqueIndex:idx_label_application_id"`
|
|
||||||
Application Application
|
Application Application
|
||||||
DeviceTypeID int `gorm:"not null"`
|
|
||||||
DeviceType DeviceType
|
DeviceType DeviceType
|
||||||
Attributes map[string]interface{} `gorm:"serializer:json;type:jsonb"`
|
Attributes map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +1,146 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
//"time"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
"udi/counter"
|
"udi/counter"
|
||||||
"gorm.io/driver/postgres"
|
|
||||||
"gorm.io/gorm"
|
influxdb "github.com/influxdata/influxdb1-client/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DatabaseHandle struct {
|
type DatabaseHandle struct {
|
||||||
initialized bool
|
initialized bool
|
||||||
dbh *gorm.DB
|
client influxdb.Client
|
||||||
|
database string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDatabaseHandle() *DatabaseHandle {
|
func NewDatabaseHandle() *DatabaseHandle {
|
||||||
var db DatabaseHandle
|
var db DatabaseHandle
|
||||||
// inject the whole database configuration via the well-known PG* env variables
|
|
||||||
conn, err := gorm.Open(postgres.Open(""))
|
// Read configuration from environment variables
|
||||||
if err != nil {
|
influxURL := os.Getenv("INFLUXDB_URL")
|
||||||
log.Printf("Unable to open database connection: %s", err)
|
if influxURL == "" {
|
||||||
db.initialized = false
|
influxURL = "http://localhost:8086"
|
||||||
} else {
|
|
||||||
db.dbh = conn
|
|
||||||
db.initialized = true
|
|
||||||
log.Println("Database connection opened")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
influxDB := os.Getenv("INFLUXDB_DATABASE")
|
||||||
|
if influxDB == "" {
|
||||||
|
influxDB = "udi"
|
||||||
|
}
|
||||||
|
|
||||||
|
username := os.Getenv("INFLUXDB_USER")
|
||||||
|
password := os.Getenv("INFLUXDB_PASSWORD")
|
||||||
|
|
||||||
|
// Create InfluxDB client
|
||||||
|
client, err := influxdb.NewHTTPClient(influxdb.HTTPConfig{
|
||||||
|
Addr: influxURL,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to create InfluxDB client: %s", err)
|
||||||
|
db.initialized = false
|
||||||
|
return &db
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test connection
|
||||||
|
_, _, err = client.Ping(0)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to ping InfluxDB: %s", err)
|
||||||
|
db.initialized = false
|
||||||
|
client.Close()
|
||||||
|
return &db
|
||||||
|
}
|
||||||
|
|
||||||
|
db.client = client
|
||||||
|
db.database = influxDB
|
||||||
|
db.initialized = true
|
||||||
|
log.Printf("InfluxDB connection opened (URL: %s, Database: %s)", influxURL, influxDB)
|
||||||
|
|
||||||
return &db
|
return &db
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *DatabaseHandle) StoreMeasurement(measurement *Measurement) {
|
func (self *DatabaseHandle) StoreMeasurement(measurement *Measurement) {
|
||||||
if !self.initialized {
|
if !self.initialized {
|
||||||
log.Printf("Database connection not initialized, can not store, measurement %s lost", measurement)
|
log.Printf("Database connection not initialized, can not store, measurement %v lost", measurement)
|
||||||
counter.F("Stored")
|
counter.F("Stored")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result := self.dbh.Create(measurement)
|
// Create batch points
|
||||||
if result.Error != nil {
|
bp, err := influxdb.NewBatchPoints(influxdb.BatchPointsConfig{
|
||||||
log.Printf("Unable to insert, measurement %s lost, error: %s", measurement, result.Error)
|
Database: self.database,
|
||||||
|
Precision: "s",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to create batch points: %s", err)
|
||||||
|
counter.F("Stored")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build tags
|
||||||
|
tags := map[string]string{
|
||||||
|
"application": measurement.Application,
|
||||||
|
}
|
||||||
|
if measurement.Device != "" {
|
||||||
|
tags["device"] = measurement.Device
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add attributes as tags
|
||||||
|
for key, value := range measurement.Attributes {
|
||||||
|
if strValue, ok := value.(string); ok {
|
||||||
|
tags[key] = strValue
|
||||||
|
} else {
|
||||||
|
tags[key] = fmt.Sprintf("%v", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build fields from Values
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
for key, varType := range measurement.Values {
|
||||||
|
// Store the value with the variable name as field key
|
||||||
|
fields[key] = varType.Value
|
||||||
|
|
||||||
|
// Optionally store metadata as separate fields
|
||||||
|
if varType.Unit != "" {
|
||||||
|
fields[key+"_unit"] = varType.Unit
|
||||||
|
}
|
||||||
|
if varType.Variable != "" {
|
||||||
|
fields[key+"_variable"] = varType.Variable
|
||||||
|
}
|
||||||
|
if varType.Status != "" {
|
||||||
|
fields[key+"_status"] = varType.Status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have at least one field
|
||||||
|
if len(fields) == 0 {
|
||||||
|
log.Printf("No fields to store in measurement, skipping")
|
||||||
|
counter.F("Stored")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create point
|
||||||
|
pt, err := influxdb.NewPoint(
|
||||||
|
"measurement",
|
||||||
|
tags,
|
||||||
|
fields,
|
||||||
|
measurement.Time,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to create point: %s", err)
|
||||||
|
counter.F("Stored")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bp.AddPoint(pt)
|
||||||
|
|
||||||
|
// Write batch
|
||||||
|
err = self.client.Write(bp)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to write to InfluxDB, measurement lost, error: %s", err)
|
||||||
counter.F("Stored")
|
counter.F("Stored")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -48,49 +149,9 @@ func (self *DatabaseHandle) StoreMeasurement(measurement *Measurement) {
|
|||||||
counter.S("Stored")
|
counter.S("Stored")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *DatabaseHandle) GetDeviceByLabelAndApplication(applicationLabel string, deviceLabel string) (*Device, error) {
|
func (self *DatabaseHandle) Close() {
|
||||||
if ! self.initialized {
|
if self.initialized && self.client != nil {
|
||||||
err := fmt.Errorf("Database connection not initialized")
|
self.client.Close()
|
||||||
return nil, err
|
log.Println("InfluxDB connection closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
var device Device
|
|
||||||
result := self.dbh.
|
|
||||||
Preload("Application").
|
|
||||||
Preload("DeviceType").
|
|
||||||
Joins("JOIN applications ON devices.application_id = applications.id").
|
|
||||||
Where("devices.label = ? AND applications.label = ?", deviceLabel, applicationLabel).
|
|
||||||
First(&device)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
err := fmt.Errorf("Query failed: %s", result.Error)
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &device, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *DatabaseHandle) GetDeviceByLabel(deviceLabel string) (*Device, error) {
|
|
||||||
if ! self.initialized {
|
|
||||||
err := fmt.Errorf("Database connection not initialized")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var device Device
|
|
||||||
result := self.dbh.
|
|
||||||
Preload("Application").
|
|
||||||
Preload("DeviceType").
|
|
||||||
Where("devices.label = ?", deviceLabel).
|
|
||||||
First(&device)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
err := fmt.Errorf("Query failed: %s", result.Error)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &device, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
//"time"
|
|
||||||
"gorm.io/driver/postgres"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Migrate() {
|
|
||||||
dsn := ""
|
|
||||||
db, err := gorm.Open(postgres.Open(dsn))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Unable to open database connection: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
db.AutoMigrate(&Application{})
|
|
||||||
log.Println("Application created")
|
|
||||||
|
|
||||||
db.AutoMigrate(&DeviceType{})
|
|
||||||
log.Println("DeviceType created")
|
|
||||||
|
|
||||||
db.AutoMigrate(&Device{})
|
|
||||||
log.Println("Device created")
|
|
||||||
|
|
||||||
db.AutoMigrate(&Measurement{})
|
|
||||||
log.Println("Measurement created")
|
|
||||||
|
|
||||||
log.Println("Remember to call create_hypertable on measurements, sowhat I can't do that for you.")
|
|
||||||
|
|
||||||
/*
|
|
||||||
m := Measurement {
|
|
||||||
Time: time.Now(),
|
|
||||||
Application: "app",
|
|
||||||
Attributes: nil,
|
|
||||||
Values: []SensorType {
|
|
||||||
{ Variable: "Temperature", Unit: "Degree Celsius", Value: 1.0 },
|
|
||||||
{ Variable: "Temperature", Unit: "Degree Celsius", Value: 3.0 },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
db.Create(&m)
|
|
||||||
|
|
||||||
m = Measurement {
|
|
||||||
Time: time.Now(),
|
|
||||||
Application: "app",
|
|
||||||
Attributes: nil,
|
|
||||||
Values: []SensorType {
|
|
||||||
{ Variable: "Temperature", Unit: "Degree Celsius", Value: 10.0 },
|
|
||||||
{ Variable: "Temperature", Unit: "Degree Celsius", Value: 30.0 },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
db.Create(&m)
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -5,21 +5,12 @@ go 1.22.3
|
|||||||
require (
|
require (
|
||||||
github.com/eclipse/paho.mqtt.golang v1.5.0
|
github.com/eclipse/paho.mqtt.golang v1.5.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c
|
||||||
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852
|
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852
|
||||||
gorm.io/driver/postgres v1.5.11
|
|
||||||
gorm.io/gorm v1.25.12
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
|
||||||
github.com/jackc/pgx/v5 v5.7.2 // indirect
|
|
||||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
|
||||||
golang.org/x/crypto v0.32.0 // indirect
|
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "log"
|
|
||||||
import "udi/database"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.SetPrefix("UDI Migrate Schema: ")
|
|
||||||
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
|
||||||
|
|
||||||
log.Println("Starting")
|
|
||||||
|
|
||||||
database.Migrate()
|
|
||||||
|
|
||||||
log.Println("Done")
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user