wshub: fix CalConfig.Normalise parity gaps vs C++ twin and JS client

Finding 1: strip trailing [digits] array-element suffix from Signal so one
calibration entry covers an entire array signal, matching the C++ strchr
truncation and the JS equivalent.

Finding 2: after the 16-byte Unit truncation, drop any trailing partial UTF-8
rune so json.Marshal never emits replacement characters; keeps byte limit in
sync with C++ strncpy(u, unit, kMaxUnitLen).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino Ferrari
2026-08-16 19:21:41 +02:00
co-authored by Claude Sonnet 4.6
parent 47f1567a26
commit 66efd74dd5
2 changed files with 75 additions and 0 deletions
+21
View File
@@ -4,11 +4,18 @@ import (
"encoding/json"
"log"
"math"
"regexp"
"sort"
"strings"
"sync"
"unicode/utf8"
)
// arrayIndexSuffix matches a trailing "[digits]" at the very end of a signal
// name, used to strip array-element suffixes so one entry covers the whole
// array. Mirrors the C++ `strchr(signal,'[')` truncation and the JS equivalent.
var arrayIndexSuffix = regexp.MustCompile(`\[\d+\]$`)
// maxUnitLen bounds the calibration unit override. Mirrored by kMaxUnitLen in
// the C++ StreamHub and MAX_UNIT_LEN in the SPA's calibration.js.
const maxUnitLen = 16
@@ -37,6 +44,10 @@ func calKey(source, signal string) string { return source + "\x00" + signal }
func (c *CalConfig) Normalise() bool {
c.Source = strings.TrimSpace(c.Source)
c.Signal = strings.TrimSpace(c.Signal)
// Strip a trailing "[digits]" suffix so one entry covers an entire array
// signal. "Adc[3]" → "Adc". Must run before the empty check below so
// that "[0]" → "" → rejected, matching C++ and JS behaviour.
c.Signal = arrayIndexSuffix.ReplaceAllString(c.Signal, "")
if c.Source == "" || c.Signal == "" {
return false
}
@@ -49,6 +60,16 @@ func (c *CalConfig) Normalise() bool {
c.Unit = strings.TrimSpace(c.Unit)
if len(c.Unit) > maxUnitLen {
c.Unit = c.Unit[:maxUnitLen]
// The byte cut may land mid-rune. Drop any trailing partial rune so
// the result is always valid UTF-8; json.Marshal would otherwise emit
// replacement characters and break the save→load round-trip.
for {
r, size := utf8.DecodeLastRuneInString(c.Unit)
if r != utf8.RuneError || size != 1 {
break
}
c.Unit = c.Unit[:len(c.Unit)-1]
}
}
return true
}