openvibers/openvibe.link Commits
Node: a plugin's ready after a fault ends the fault (#36)
Changes
@@@ -63,7 +63,7 @@ describes itself and waits for EOF without opening any hardware.|-------------|---------------------------------------------------------------------------------------------|| `describe` | `driver`, `version`, `protocol` (1), `capabilities` {…}, `motion_kinds` [kinds] || `ready` | — setup finished, every actuator stopped |--| `fault` | `fault_code`, `message` — the plugin runs but cannot drive (commands get this code as nack) |++| `fault` | `fault_code`, `message` — the plugin runs but cannot drive (commands get this code as nack); `ready` again when it can (the robot came back) ends the fault || `ack` | `id` || `nack` | `id`, `fault_code`, `message` || `telemetry` | `battery` {`volts`, `percent`}, `rssi`, `sensors` {…}, `faults` […] |@@@ -494,6 +494,10 @@ func (p *Plugin) readStdout(r io.Reader, described chan struct{}) {}p.log.Info("plugin described", "driver", d.Driver, "version", d.Version)case "ready":++ // Ready again after a fault (the robot came back): the fault is over, so status stops reporting it.++ p.mu.Lock()++ p.fault = nil++ p.mu.Unlock()p.setState(StateReady)case "ack", "nack":id, _ := m["id"].(string)@@@ -62,6 +62,9 @@ func fakePlugin(logPath string, in io.Reader, out io.Writer) {w.Encode(map[string]any{"op": "fault", "fault_code": "firmware_unsupported", "message": "v1 not v2"})continue}++ if mode == "recover" {++ w.Encode(map[string]any{"op": "fault", "fault_code": "not_connected", "message": "robot unreachable"})++ }w.Encode(map[string]any{"op": "ready"})w.Encode(map[string]any{"op": "event", "name": "booted"})if mode == "local_stop" {@@@ -192,6 +195,24 @@ func TestHelloDescribeCommand(t *testing.T) {}}++// A plugin that faults (the robot unreachable) and then reports ready again (it came back) carries no fault: status++// stops reporting it and commands reach the plugin again.++func TestReadyAfterFaultClearsIt(t *testing.T) {++ fx := start(t, "recover")++ fx.waitState(StateReady)++ p := fx.m.Get("fake")++ deadline := time.Now().Add(5 * time.Second)++ for p.Info().Fault != nil && time.Now().Before(deadline) {++ time.Sleep(5 * time.Millisecond)++ }++ if f := p.Info().Fault; f != nil || p.Info().State != StateReady {++ t.Fatalf("after ready: state %s, fault %+v", p.Info().State, f)++ }++ if r := p.Command(context.Background(), "c1", "drive", json.RawMessage(`{"throttle":0.2}`), 300); !r.OK {++ t.Fatalf("a command after recovery: %+v", r)++ }++}++func TestLocalStopIsForwarded(t *testing.T) {fx := start(t, "local_stop")fx.waitState(StateReady)
@@ -63,7 +63,7 @@ describes itself and waits for EOF without opening any hardware.
@@ -63,7 +63,7 @@ describes itself and waits for EOF without opening any hardware.
|-------------|---------------------------------------------------------------------------------------------|
|-------------|---------------------------------------------------------------------------------------------|
| `describe` | `driver`, `version`, `protocol` (1), `capabilities` {…}, `motion_kinds` [kinds] |
| `describe` | `driver`, `version`, `protocol` (1), `capabilities` {…}, `motion_kinds` [kinds] |
| `ready` | — setup finished, every actuator stopped |
| `ready` | — setup finished, every actuator stopped |
-| `fault` | `fault_code`, `message` — the plugin runs but cannot drive (commands get this code as nack) |
+| `fault` | `fault_code`, `message` — the plugin runs but cannot drive (commands get this code as nack); `ready` again when it can (the robot came back) ends the fault |
| `ack` | `id` |
| `ack` | `id` |
| `nack` | `id`, `fault_code`, `message` |
| `nack` | `id`, `fault_code`, `message` |
| `telemetry` | `battery` {`volts`, `percent`}, `rssi`, `sensors` {…}, `faults` […] |
| `telemetry` | `battery` {`volts`, `percent`}, `rssi`, `sensors` {…}, `faults` […] |
@@ -494,6 +494,10 @@ func (p *Plugin) readStdout(r io.Reader, described chan struct{}) {
@@ -494,6 +494,10 @@ func (p *Plugin) readStdout(r io.Reader, described chan struct{}) {
}
}
p.log.Info("plugin described", "driver", d.Driver, "version", d.Version)
p.log.Info("plugin described", "driver", d.Driver, "version", d.Version)
case "ready":
case "ready":
+ // Ready again after a fault (the robot came back): the fault is over, so status stops reporting it.
+ p.mu.Lock()
+ p.fault = nil
+ p.mu.Unlock()
p.setState(StateReady)
p.setState(StateReady)
case "ack", "nack":
case "ack", "nack":
id, _ := m["id"].(string)
id, _ := m["id"].(string)
@@ -62,6 +62,9 @@ func fakePlugin(logPath string, in io.Reader, out io.Writer) {
@@ -62,6 +62,9 @@ func fakePlugin(logPath string, in io.Reader, out io.Writer) {
w.Encode(map[string]any{"op": "fault", "fault_code": "firmware_unsupported", "message": "v1 not v2"})
w.Encode(map[string]any{"op": "fault", "fault_code": "firmware_unsupported", "message": "v1 not v2"})
continue
continue
}
}
+ if mode == "recover" {
+ w.Encode(map[string]any{"op": "fault", "fault_code": "not_connected", "message": "robot unreachable"})
+ }
w.Encode(map[string]any{"op": "ready"})
w.Encode(map[string]any{"op": "ready"})
w.Encode(map[string]any{"op": "event", "name": "booted"})
w.Encode(map[string]any{"op": "event", "name": "booted"})
if mode == "local_stop" {
if mode == "local_stop" {
@@ -192,6 +195,24 @@ func TestHelloDescribeCommand(t *testing.T) {
@@ -192,6 +195,24 @@ func TestHelloDescribeCommand(t *testing.T) {
}
}
}
}
+// A plugin that faults (the robot unreachable) and then reports ready again (it came back) carries no fault: status
+// stops reporting it and commands reach the plugin again.
+func TestReadyAfterFaultClearsIt(t *testing.T) {
+ fx := start(t, "recover")
+ fx.waitState(StateReady)
+ p := fx.m.Get("fake")
+ deadline := time.Now().Add(5 * time.Second)
+ for p.Info().Fault != nil && time.Now().Before(deadline) {
+ time.Sleep(5 * time.Millisecond)
+ }
+ if f := p.Info().Fault; f != nil || p.Info().State != StateReady {
+ t.Fatalf("after ready: state %s, fault %+v", p.Info().State, f)
+ }
+ if r := p.Command(context.Background(), "c1", "drive", json.RawMessage(`{"throttle":0.2}`), 300); !r.OK {
+ t.Fatalf("a command after recovery: %+v", r)
+ }
+}
+
func TestLocalStopIsForwarded(t *testing.T) {
func TestLocalStopIsForwarded(t *testing.T) {
fx := start(t, "local_stop")
fx := start(t, "local_stop")
fx.waitState(StateReady)
fx.waitState(StateReady)