Files
mesh-catalog/modules/power/cmd/power/publish.go
T
jochen 76b63744e6 Carry the power draw to the bar over the bus, not through a file (hq ADR 0260)
Modules talk over the bus. The power module's bundle puts each fresh reading of its own
sampler in its state draw, keyed by the machine; the bar's block shows that state, which
the bar's bundle watches under the read the mesh grants it. A new catalogue check refuses
a module naming another module's path under /run or /var/lib.
2026-10-08 16:12:37 +02:00

77 lines
2.5 KiB
Go

package main
import (
"encoding/json"
"fmt"
"os"
"strings"
"time"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// The draw on the bus (novox/hq ADR 0260). The module's bundle runs as the operator's account and so
// cannot read the energy counter; its root process can and cannot reach the bus. So the process leaves
// each reading in the module's own file and the bundle puts it in the state `draw`, keyed by this
// machine: the current value, which a machine reading later sees at once. Not an event: a reading is
// replaced every five seconds and nobody needs the ones before, and an event would keep each in a stream.
// Nothing measured is not put; a value older than its lifetime (`ttl-seconds` in the manifest) is gone.
// DrawState is the state the draw is kept in, and DrawStale how old a reading may be and still be put.
const (
DrawState = "draw"
DrawStale = 30 * time.Second
)
// Putter puts one key of a state; the SDK's in the bundle, a recorder in a test.
type Putter func(key string, value any) error
// shareDraw reads the sampler's reading and puts it for this machine, if it is fresh and measured
// something. It answers what it did, for the log.
func (m *Machine) shareDraw(machine string, now time.Time, put Putter) (string, error) {
raw, err := os.ReadFile(m.path(DrawFile))
if err != nil {
return "no reading yet", nil
}
var r Reading
if err := json.Unmarshal(raw, &r); err != nil {
return "", fmt.Errorf("the sampler's reading is not one: %w", err)
}
at, err := time.Parse(time.RFC3339, r.At)
if err != nil || now.Sub(at) > DrawStale {
return "the sampler's reading is stale", nil
}
if r.Measures == "" {
return "nothing measured", nil
}
if err := put(machine, r); err != nil {
return "", err
}
return "put " + r.Text, nil
}
// ShareDraw puts the draw every SampleEvery for as long as the bundle runs. A machine with no name
// from the runtime puts nothing, and says so once.
func ShareDraw(m *Machine) {
machine := os.Getenv("MESH_NODE")
if machine == "" {
fmt.Fprintln(os.Stderr, "power: the runtime gave no machine name (MESH_NODE); the draw is not shared")
return
}
put := func(key string, value any) error {
_, err := stdio.State(DrawState).Put(key, value)
return err
}
last := ""
for range time.Tick(SampleEvery) {
said, err := m.shareDraw(machine, time.Now(), put)
if err != nil {
said = err.Error()
}
if said != last && (err != nil || !strings.HasPrefix(said, "put ")) {
fmt.Fprintln(os.Stderr, "power: draw:", said)
}
last = said
}
}