Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
build/
zz_fw_update_e2e_identity.go
.vscode/settings.json
30 changes: 28 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,26 @@ import (
"devicecode-go/bus"
"devicecode-go/services/hal"
"devicecode-go/services/reactor"
"devicecode-go/services/updater"
"devicecode-go/types"
"devicecode-go/utilities"
"pico2-a-b/abupdate"
)

// HAL
const halTimeout = 5 * time.Second

var halReadiness = bus.T("hal", "state")

// Firmware identity is set by host build tooling before main runs. The e2e
// harness generates a same-package init file because TinyGo's -X support is
// narrower than the standard Go linker's support.
var (
FirmwareVersion = "0.0.0-dev"
FirmwareBuild = "local"
FirmwareImageID = "img-dev"
)

// -----------------------------------------------------------------------------
// Main
// -----------------------------------------------------------------------------
Expand All @@ -24,6 +36,11 @@ func main() {
time.Sleep(3 * time.Second)
log.SetStart(time.Now())

bootBuyRC := abupdate.CheckAndBuy()
if bootBuyRC != 0 {
log.Println("[main] abupdate CheckAndBuy rc =", bootBuyRC)
}

ctx := context.Background()

log.Println("[main] bootstrapping bus …")
Expand All @@ -47,8 +64,17 @@ func main() {
}
}

// boot_id: generate AFTER HAL ready and BEFORE the reactor opens
// fabric. RAM-only — never persisted.
bootID := updater.GenerateBootID()
log.Println("[main] boot_id =", bootID)

reactor.FirmwareVersion = FirmwareVersion
reactor.FirmwareBuild = FirmwareBuild
reactor.FirmwareImageID = FirmwareImageID

// Reactor
r := reactor.NewReactor(b, uiConn)
r := reactor.NewReactorWithOptions(b, uiConn, reactor.Options{BootBuyRC: bootBuyRC})
r.Run(ctx)
}

Expand Down Expand Up @@ -76,4 +102,4 @@ func waitHALReady(ctx context.Context, c *bus.Connection, d time.Duration) bool
}

// Global logger instance
var log = utilities.Logger{LineStart: true}
var log = utilities.Logger{LineStart: true}
104 changes: 10 additions & 94 deletions services/fabric/config.go
Original file line number Diff line number Diff line change
@@ -1,88 +1,15 @@
package fabric

import (
"encoding/json"

"devicecode-go/types"
)

// decodeHALConfig extracts a HALConfig from an arbitrary payload,
// normalizing Lua empty-table encoding ({} → []) for known slice fields.
func decodeHALConfig(payload any) (types.HALConfig, string) {
switch v := payload.(type) {
case types.HALConfig:
return v, ""
case *types.HALConfig:
if v == nil {
return types.HALConfig{}, "nil_hal_config"
}
return *v, ""
case json.RawMessage:
return decodeHALConfigBytes(v)
case []byte:
return decodeHALConfigBytes(v)
default:
b, err := json.Marshal(v)
if err != nil {
return types.HALConfig{}, "payload_marshal_failed: " + err.Error()
}
return decodeHALConfigBytes(b)
}
}

func decodeHALConfigBytes(b []byte) (types.HALConfig, string) {
var probe map[string]json.RawMessage
if err := json.Unmarshal(b, &probe); err != nil {
return types.HALConfig{}, "json_unmarshal_failed: " + err.Error() + "; raw=" + truncateRawJSON(b)
}
if _, ok := probe["devices"]; !ok {
return types.HALConfig{}, "missing_devices_field; raw=" + truncateRawJSON(b)
}

// Lua encodes empty tables as {} (object) not [] (array).
// Normalize known slice fields so Go unmarshal accepts them.
for _, key := range []string{"devices", "pollers"} {
if raw, ok := probe[key]; ok && len(raw) == 2 && raw[0] == '{' && raw[1] == '}' {
probe[key] = json.RawMessage("[]")
}
}
fixed, err := json.Marshal(probe)
if err != nil {
return types.HALConfig{}, "normalize_failed: " + err.Error()
}

var out types.HALConfig
if err := json.Unmarshal(fixed, &out); err != nil {
return types.HALConfig{}, "hal_config_unmarshal_failed: " + err.Error() + "; raw=" + truncateRawJSON(fixed)
}
return out, ""
}

func decodeHALState(payload any) (types.HALState, bool) {
switch v := payload.(type) {
case types.HALState:
return v, true
case *types.HALState:
if v == nil {
return types.HALState{}, false
}
return *v, true
case json.RawMessage:
var out types.HALState
return out, json.Unmarshal(v, &out) == nil
case []byte:
var out types.HALState
return out, json.Unmarshal(v, &out) == nil
default:
b, err := json.Marshal(v)
if err != nil {
return types.HALState{}, false
}
var out types.HALState
return out, json.Unmarshal(b, &out) == nil
}
}

import "encoding/json"

// decodePayload normalises whatever shape the bus delivered into a
// reasonable Go value for the reply path. The wire delivers
// json.RawMessage; in-process callers may pass already-typed values.
// Used by session.onReply when forwarding RPC replies onto the
// originating Request's reply path.
//
// This file intentionally contains only reply-payload decoding; legacy
// config/device and rpc/hal/dump glue is no longer part of the MCU contract.
func decodePayload(payload any) any {
switch v := payload.(type) {
case nil:
Expand Down Expand Up @@ -111,14 +38,3 @@ func decodePayload(payload any) any {
return v
}
}

func truncateRawJSON(b []byte) string {
if len(b) == 0 {
return ""
}
const max = 160
if len(b) <= max {
return string(b)
}
return string(b[:max]) + "..."
}
12 changes: 8 additions & 4 deletions services/fabric/fabric.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"devicecode-go/bus"
"devicecode-go/services/updater"
"devicecode-go/x/strconvx"
)

Expand All @@ -16,12 +17,11 @@ type Transport interface {
Close() error
}

const protoVersion = 1
const defaultLinkID = "mcu0"
const defaultLinkID = "mcu-uart0"

// LinkConfig carries the fabric link parameters that the CM5 publishes
// alongside its own session/transfer-mgr instances. Mirrors the relevant
// keys in `bigbox-v1-cm-2.json` `service.fabric.links.<id>` for the
// keys in `bigbox-v1-cm-2.json` `fabric.data.links.<id>` for the
// MCU-facing link. Missing fields fall back to release defaults via
// applyDefaults so callers can pass `LinkConfig{}` to mean "release".
type LinkConfig struct {
Expand Down Expand Up @@ -95,7 +95,11 @@ func (c *LinkConfig) applyDefaults() {
var nextSessionID atomic.Uint64

func newLocalSID() string {
return "mcu-sid-" + strconvx.Utoa64(nextSessionID.Add(1))
bootID := updater.BootID()
if bootID == "" {
bootID = updater.GenerateBootID()
}
return "mcu-sid-" + bootID + "-" + strconvx.Utoa64(nextSessionID.Add(1))
}

// Run starts the fabric session. Blocks until ctx is cancelled or the
Expand Down
Loading