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.
77 lines
2.5 KiB
Go
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
|
|
}
|
|
}
|