Files
tecamino-dbm/models/datapoints.go
2025-04-23 21:53:01 +02:00

278 lines
6.5 KiB
Go

package models
import (
"fmt"
"regexp"
"strings"
"time"
"github.com/zuadi/tecamino-dbm/utils"
)
type Datapoint struct {
Datapoints map[string]*Datapoint `json:"-"`
Path string `json:"path"`
Value any `json:"value,omitempty"`
CreateDateTime int64 `json:"createDateTime,omitempty"`
UpdateDateTime int64 `json:"updateDateTime,omitempty"`
Type Type `json:"type"`
ReadWrite Rights `json:"readWrite"`
Drivers map[string]*Driver `json:"-"`
Subscribtions Subscribtions `json:"-"`
}
func (d *Datapoint) CreateDatapoint(typ Type, value any, rights Rights, path string) error {
parts := regexp.MustCompile(`[:]+`).Split(path, -1)
current := d
for i, part := range parts {
if current.Datapoints == nil {
current.Datapoints = make(map[string]*Datapoint)
}
if i == len(parts)-1 {
// Leaf node: create or update datapoint
if existing, ok := current.Datapoints[part]; ok {
// Update existing
existing.Type = typ
existing.ReadWrite = rights.GetRights()
existing.Value = typ.ConvertValue(value)
existing.UpdateDateTime = time.Now().UnixMilli()
} else {
// Create new
current.Datapoints[part] = &Datapoint{
Path: strings.Join(parts, ":"),
Type: typ,
Value: typ.ConvertValue(value),
ReadWrite: rights.GetRights(),
CreateDateTime: time.Now().UnixMilli(),
UpdateDateTime: time.Now().UnixMilli(),
Subscribtions: InitSubscribtion(),
}
}
return nil
}
// Traverse or create intermediate datapoints
if next, ok := current.Datapoints[part]; ok {
current = next
} else {
newDp := &Datapoint{
Path: strings.Join(parts[:i+1], ":"),
Type: NONE,
ReadWrite: rights.GetRights(),
CreateDateTime: time.Now().UnixMilli(),
UpdateDateTime: time.Now().UnixMilli(),
Subscribtions: InitSubscribtion(),
}
current.Datapoints[part] = newDp
current = newDp
}
}
return nil
}
func (d *Datapoint) ImportDatapoint(dp *Datapoint, path string) error {
parts := regexp.MustCompile(`[:]+`).Split(path, -1)
current := d
for i, part := range parts {
if current.Datapoints == nil {
current.Datapoints = make(map[string]*Datapoint)
}
if i == len(parts)-1 {
// Leaf node: import the datapoint
if existing, ok := current.Datapoints[part]; ok {
existing.Type = dp.Type
existing.Value = current.Type.ConvertValue(dp.Value)
existing.ReadWrite = dp.ReadWrite.GetRights()
existing.UpdateDateTime = time.Now().UnixMilli()
} else {
dp.Path = strings.Join(parts, ":")
dp.ReadWrite = dp.ReadWrite.GetRights()
dp.UpdateDateTime = time.Now().UnixMilli()
dp.Subscribtions = InitSubscribtion()
current.Datapoints[part] = dp
}
return nil
}
// Traverse or create intermediate nodes
if next, ok := current.Datapoints[part]; ok {
current = next
} else {
newDp := &Datapoint{
Path: strings.Join(parts[:i+1], ":"),
Type: NONE,
ReadWrite: dp.ReadWrite.GetRights(),
UpdateDateTime: time.Now().UnixMilli(),
}
newDp.ReadWrite = newDp.ReadWrite.GetRights()
current.Datapoints[part] = newDp
current = newDp
}
}
return nil
}
func (d *Datapoint) UpdateDatapointValue(value any, path string) error {
paths := regexp.MustCompile(`[:]+`).Split(path, -1)
current := d
for i, part := range paths {
dp, ok := current.Datapoints[part]
if !ok {
return fmt.Errorf("datapoint path not found: %s (at %s)", path, part)
}
if i == len(paths)-1 {
dp.Value = dp.Type.ConvertValue(value)
dp.UpdateDateTime = time.Now().UnixMilli()
return nil
}
current = dp
}
return nil
}
func (d *Datapoint) RemoveDatapoint(path string) error {
parts := regexp.MustCompile(`[:]+`).Split(path, -1)
if len(parts) < 1 {
return fmt.Errorf("invalid path: '%s'", path)
}
current := d
for i := range len(parts) - 1 {
next, ok := current.Datapoints[parts[i]]
if !ok {
return fmt.Errorf("path not found: '%s'", strings.Join(parts[:i+1], ":"))
}
current = next
}
toDelete := parts[len(parts)-1]
if _, ok := current.Datapoints[toDelete]; ok {
delete(current.Datapoints, toDelete)
fmt.Println("Removed datapoint:", path)
return nil
}
return fmt.Errorf("datapoint '%s' not found", path)
}
func (d *Datapoint) GetAllDatapoints(depth int) (dps []*Datapoint) {
var dfs func(dp *Datapoint, currentDepth int)
dfs = func(dp *Datapoint, currentDepth int) {
if currentDepth == 0 {
dps = append(dps, dp)
}
if depth == 1 {
return
} else if depth == 0 {
// Return all descendants
for _, child := range dp.Datapoints {
dps = append(dps, child)
dfs(child, currentDepth+1)
}
return
}
if currentDepth == depth-1 {
return
}
for _, child := range dp.Datapoints {
dfs(child, currentDepth+1)
}
}
dfs(d, 0)
return
}
func (d *Datapoint) QueryDatapoints(depth int, path string) (dps []*Datapoint) {
parts := strings.Split(path, ":")
var dfs func(current *Datapoint, index int)
dfs = func(current *Datapoint, index int) {
if index == len(parts) {
dps = append(dps, current.GetAllDatapoints(depth)...)
return
}
pattern := "^" + parts[index] + "$"
re, err := regexp.Compile(pattern)
if err != nil {
return
}
for name, dp := range current.Datapoints {
if re.MatchString(name) {
dfs(dp, index+1)
}
}
}
dfs(d, 0)
return
}
func (d *Datapoint) AddSubscribtion(id string, sub *Subscribe) {
if d.Subscribtions == nil {
return
}
if s, ok := d.Subscribtions[id]; ok {
s.OnCreate = sub.OnCreate
s.OnChange = sub.OnChange
s.OnDelete = sub.OnDelete
} else {
d.Subscribtions[id] = &Subscribtion{
OnCreate: sub.OnCreate,
OnChange: sub.OnChange,
OnDelete: sub.OnDelete,
}
}
}
func (d *Datapoint) RemoveSubscribtion(id string) {
if _, ok := d.Subscribtions[id]; !ok {
return
}
delete(d.Subscribtions, id)
}
func (d *Datapoint) AddDriver(driver, bus string, adr int) {
if d.Drivers == nil {
d.Drivers = make(map[string]*Driver)
}
d.Drivers[driver] = &Driver{
Bus: bus,
Address: adr,
}
}
func (d *Datapoint) AddDriverSubscribtion(id string, sub *Subscribe) {
if s, ok := d.Subscribtions[id]; ok {
s.OnCreate = sub.OnCreate
s.OnChange = sub.OnChange
s.OnDelete = sub.OnDelete
} else {
d.Subscribtions[id] = &Subscribtion{
OnCreate: sub.OnCreate,
OnChange: sub.OnChange,
OnDelete: sub.OnDelete,
}
}
}
func (d *Datapoint) GetValueUint64() uint64 {
return utils.Uint64From(d.Value)
}