Implemented history writer

This commit is contained in:
Martino Ferrari
2026-06-14 14:09:51 +02:00
parent f25bd7f08e
commit dd7dd22cb0
27 changed files with 1537 additions and 104 deletions
+73 -54
View File
@@ -2,19 +2,18 @@
* streamhub_demo.cfg — MARTe2 app for the StreamHub demo.
*
* Three real-time threads, each feeding a UDPStreamer instance.
* No DebugService — monitoring is done via StreamHub + WebSocket clients.
*
* Thread1 1 kHz — scalar signals → UDPStreamer port 44500
* Counter, Time, Sine1 (1 Hz, 5 V), Sine2 (0.3 Hz, 3 V)
* Accumulate mode, multicast group 239.0.0.1 data port 44503
* Thread1 1 kHz — 2 scalar sines @ 1 ksps (Accumulate mode)
* Sine1 (1 Hz, 5 V, phase 0)
* Sine2 (0.3 Hz, 3 V, phase 60 deg)
*
* Thread2 5 kHz — packed arrays → UDPStreamer port 44501
* Ch1 float32[1000] @ 1 kHz (TimeMode = FirstSample)
* Ch2 float32[1000] @ 10 kHz (TimeMode = LastSample)
* Thread2 1 kHz — 2 array sines @ 1 Msps (1000 elem × 1 kHz)
* Ch1 (1 kHz, 1 V, phase 0)
* Ch2 (5 kHz, 0.5 V, phase 90 deg)
*
* Thread3 5 kHz — FullArray + per-sample timestamps → UDPStreamer port 44502
* Ch3 float32[1000] @ 3 kHz (TimeMode = FullArray)
* Ch4 float32[1000] @ 500 Hz (TimeMode = FullArray)
* Thread3 5 kHz — 2 array sines @ 5 Msps (1000 elem × 5 kHz)
* Ch3 (10 kHz, 2 V, phase 0)
* Ch4 (50 kHz, 1.5 V, phase 45 deg)
*/
$App = {
@@ -23,7 +22,7 @@ $App = {
+Functions = {
Class = ReferenceContainer
// ── Thread1: 1 kHz scalar signals ────────────────────────────────────
// ── Thread1: 1 kHz scalar sines (1 ksps) ────────────────────────────
+TimerGAM = {
Class = IOGAM
@@ -84,15 +83,15 @@ $App = {
}
}
// ── Thread2: 5 kHz packed arrays (FirstSample / LastSample) ──────────
// ── Thread2: 1 kHz, 1000-element arrays → 1 Msps ───────────────────
+FastTimerGAM = {
+MedTimerGAM = {
Class = IOGAM
InputSignals = {
Time = {
DataSource = FastTimer
DataSource = MedTimer
Type = uint32
Frequency = 5000
Frequency = 1000
}
}
OutputSignals = {
@@ -106,7 +105,7 @@ $App = {
Amplitude = 1.0
Phase = 0.0
Offset = 0.0
SamplingRate = 5000000.0
SamplingRate = 1000000.0
OutputSignals = {
Ch1 = {
DataSource = DDB2
@@ -119,11 +118,11 @@ $App = {
+SineGAM4 = {
Class = SineArrayGAM
Frequency = 10000.0
Frequency = 5000.0
Amplitude = 0.5
Phase = 1.5708
Offset = 0.0
SamplingRate = 5000000.0
SamplingRate = 1000000.0
OutputSignals = {
Ch2 = {
DataSource = DDB2
@@ -134,27 +133,44 @@ $App = {
}
}
+StreamerGAM2 = {
Class = IOGAM
+TimeArrayGAM1 = {
Class = TimeArrayGAM
SamplingRate = 1000000.0
Anchor = FirstSample
InputSignals = {
Time = { DataSource = DDB2 Type = uint32 }
Ch1 = { DataSource = DDB2 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
Ch2 = { DataSource = DDB2 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
Time = { DataSource = DDB2 Type = uint32 }
}
OutputSignals = {
Time = { DataSource = Streamer2 Type = uint32 }
Ch1 = { DataSource = Streamer2 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
Ch2 = { DataSource = Streamer2 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
TimeArray1 = {
DataSource = DDB2
Type = uint64
NumberOfDimensions = 1
NumberOfElements = 1000
}
}
}
// ── Thread3: 5 kHz FullArray with per-sample timestamps ──────────────
+StreamerGAM2 = {
Class = IOGAM
InputSignals = {
TimeArray1 = { DataSource = DDB2 Type = uint64 NumberOfDimensions = 1 NumberOfElements = 1000 }
Ch1 = { DataSource = DDB2 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
Ch2 = { DataSource = DDB2 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
}
OutputSignals = {
TimeArray1 = { DataSource = Streamer2 Type = uint64 NumberOfDimensions = 1 NumberOfElements = 1000 }
Ch1 = { DataSource = Streamer2 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
Ch2 = { DataSource = Streamer2 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
}
}
+FullArrTimerGAM = {
// ── Thread3: 5 kHz, 1000-element arrays → 5 Msps ───────────────────
+FastTimerGAM = {
Class = IOGAM
InputSignals = {
Time = {
DataSource = FullArrTimer
DataSource = FastTimer
Type = uint32
Frequency = 5000
}
@@ -166,7 +182,7 @@ $App = {
+SineGAM5 = {
Class = SineArrayGAM
Frequency = 3000.0
Frequency = 10000.0
Amplitude = 2.0
Phase = 0.0
Offset = 0.0
@@ -183,8 +199,8 @@ $App = {
+SineGAM6 = {
Class = SineArrayGAM
Frequency = 500.0
Amplitude = 3.0
Frequency = 50000.0
Amplitude = 1.5
Phase = 0.7854
Offset = 0.0
SamplingRate = 5000000.0
@@ -198,7 +214,7 @@ $App = {
}
}
+TimeArrayGAM1 = {
+TimeArrayGAM2 = {
Class = TimeArrayGAM
SamplingRate = 5000000.0
Anchor = FirstSample
@@ -206,7 +222,7 @@ $App = {
Time = { DataSource = DDB3 Type = uint32 }
}
OutputSignals = {
TimeArray = {
TimeArray2 = {
DataSource = DDB3
Type = uint64
NumberOfDimensions = 1
@@ -218,14 +234,14 @@ $App = {
+StreamerGAM3 = {
Class = IOGAM
InputSignals = {
TimeArray = { DataSource = DDB3 Type = uint64 NumberOfDimensions = 1 NumberOfElements = 1000 }
Ch3 = { DataSource = DDB3 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
Ch4 = { DataSource = DDB3 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
TimeArray2 = { DataSource = DDB3 Type = uint64 NumberOfDimensions = 1 NumberOfElements = 1000 }
Ch3 = { DataSource = DDB3 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
Ch4 = { DataSource = DDB3 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
}
OutputSignals = {
TimeArray = { DataSource = Streamer3 Type = uint64 NumberOfDimensions = 1 NumberOfElements = 1000 }
Ch3 = { DataSource = Streamer3 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
Ch4 = { DataSource = Streamer3 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
TimeArray2 = { DataSource = Streamer3 Type = uint64 NumberOfDimensions = 1 NumberOfElements = 1000 }
Ch3 = { DataSource = Streamer3 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
Ch4 = { DataSource = Streamer3 Type = float32 NumberOfDimensions = 1 NumberOfElements = 1000 }
}
}
}
@@ -247,7 +263,7 @@ $App = {
}
}
+FastTimer = {
+MedTimer = {
Class = LinuxTimer
SleepNature = "Default"
Signals = {
@@ -256,7 +272,7 @@ $App = {
}
}
+FullArrTimer = {
+FastTimer = {
Class = LinuxTimer
SleepNature = "Default"
Signals = {
@@ -286,18 +302,21 @@ $App = {
Port = 44501
MaxPayloadSize = 1400
PublishingMode = "Decimate"
Ratio = 50
Ratio = 10
Signals = {
Time = { Type = uint32 Unit = "us" }
Ch1 = {
Type = float32 Unit = "V"
TimeArray1 = {
Type = uint64 Unit = "ns"
NumberOfDimensions = 1 NumberOfElements = 1000
TimeMode = FirstSample TimeSignal = Time SamplingRate = 5000000.0
}
Ch2 = {
Ch1 = {
Type = float32 Unit = "V"
NumberOfDimensions = 1 NumberOfElements = 1000
TimeMode = LastSample TimeSignal = Time SamplingRate = 5000000.0
TimeMode = FullArray TimeSignal = TimeArray1
}
Ch2 = {
Type = float32 Unit = "V"
NumberOfDimensions = 1 NumberOfElements = 1000
TimeMode = FullArray TimeSignal = TimeArray1
}
}
}
@@ -309,19 +328,19 @@ $App = {
PublishingMode = "Decimate"
Ratio = 50
Signals = {
TimeArray = {
TimeArray2 = {
Type = uint64 Unit = "ns"
NumberOfDimensions = 1 NumberOfElements = 1000
}
Ch3 = {
Type = float32 Unit = "V"
NumberOfDimensions = 1 NumberOfElements = 1000
TimeMode = FullArray TimeSignal = TimeArray
TimeMode = FullArray TimeSignal = TimeArray2
}
Ch4 = {
Type = float32 Unit = "V"
NumberOfDimensions = 1 NumberOfElements = 1000
TimeMode = FullArray TimeSignal = TimeArray
TimeMode = FullArray TimeSignal = TimeArray2
}
}
}
@@ -345,13 +364,13 @@ $App = {
+Thread2 = {
Class = RealTimeThread
CPUs = 0x2
Functions = {FastTimerGAM SineGAM3 SineGAM4 StreamerGAM2}
Functions = {MedTimerGAM SineGAM3 SineGAM4 TimeArrayGAM1 StreamerGAM2}
}
+Thread3 = {
Class = RealTimeThread
CPUs = 0x4
Functions = {FullArrTimerGAM SineGAM5 SineGAM6 TimeArrayGAM1 StreamerGAM3}
Functions = {FastTimerGAM SineGAM5 SineGAM6 TimeArrayGAM2 StreamerGAM3}
}
}
}
+69 -7
View File
@@ -67,6 +67,18 @@ type zoomPoints struct {
V []float64 `json:"v"`
}
type historyInfoMsg struct {
Enabled bool `json:"enabled"`
DurationHours float64 `json:"durationHours"`
Decimation uint32 `json:"decimation"`
Signals map[string]struct {
T0 float64 `json:"t0"`
T1 float64 `json:"t1"`
Count uint32 `json:"count"`
Capacity uint32 `json:"capacity"`
} `json:"signals"`
}
type event struct {
Type string `json:"type"`
Sources json.RawMessage `json:"sources"`
@@ -187,9 +199,11 @@ type client struct {
configs map[string][]signalInfo // sourceId → signals
pushes []*pushFrame
stats map[string]statInfo
zooms map[uint32]map[string]zoomPoints
trigSt []string // observed triggerState sequence
captures []*captureFrame
zooms map[uint32]map[string]zoomPoints
histZooms map[uint32]map[string]zoomPoints
historyInfo *historyInfoMsg
trigSt []string // observed triggerState sequence
captures []*captureFrame
}
func (c *client) send(v interface{}) {
@@ -263,6 +277,18 @@ func (c *client) pump() {
if err := json.Unmarshal(data, &body); err == nil {
c.zooms[ev.ReqID] = body.Signals
}
case "historyZoom":
var body struct {
Signals map[string]zoomPoints `json:"signals"`
}
if err := json.Unmarshal(data, &body); err == nil {
c.histZooms[ev.ReqID] = body.Signals
}
case "historyInfo":
var hi historyInfoMsg
if err := json.Unmarshal(data, &hi); err == nil {
c.historyInfo = &hi
}
case "triggerState":
c.trigSt = append(c.trigSt, ev.State)
}
@@ -302,10 +328,11 @@ func main() {
defer ws.Close()
c := &client{
ws: ws,
deadline: time.Now().Add(*timeout),
configs: map[string][]signalInfo{},
zooms: map[uint32]map[string]zoomPoints{},
ws: ws,
deadline: time.Now().Add(*timeout),
configs: map[string][]signalInfo{},
zooms: map[uint32]map[string]zoomPoints{},
histZooms: map[uint32]map[string]zoomPoints{},
}
// ── 1. sources ────────────────────────────────────────────────────────
@@ -409,6 +436,41 @@ func main() {
return true
})
// ── 5b. historyInfo — check the hub broadcast it on connect ─────────
if c.historyInfo != nil && c.historyInfo.Enabled {
log.Printf("OK historyInfo: enabled, %.1fh, decimation=%d, %d signals",
c.historyInfo.DurationHours, c.historyInfo.Decimation,
len(c.historyInfo.Signals))
// ── 5c. historyZoom round-trip ──────────────────────────────────
const hReqID = 4243
c.send(map[string]interface{}{
"type": "historyZoom", "reqId": hReqID,
"t0": t0, "t1": t1, "n": 200,
"signals": zoomKey,
})
c.waitFor(fmt.Sprintf("historyZoom reply (reqId=%d, %s)", hReqID, zoomKey),
10*time.Second, func() bool {
sigs, ok := c.histZooms[hReqID]
if !ok {
return false
}
pts, ok := sigs[zoomKey]
if !ok || len(pts.T) < 1 {
// History data may still be sparse right after startup
return true
}
for _, ht := range pts.T {
if ht < t0-1e-6 || ht > t1+1e-6 {
fail("historyZoom point outside range: t=%.9f not in [%.9f,%.9f]", ht, t0, t1)
}
}
return true
})
} else {
log.Println(" (history not enabled — skipping historyZoom test)")
}
// ── 6. trigger: arm → capture ────────────────────────────────────────
// Trigger on an *oscillating* signal at its mean observed value: a
// monotonic ramp (counter, time array) crosses its past mean only once,
Binary file not shown.