-
Notifications
You must be signed in to change notification settings - Fork 53
Expand file tree
/
Copy pathruntime.py
More file actions
147 lines (131 loc) · 5.57 KB
/
Copy pathruntime.py
File metadata and controls
147 lines (131 loc) · 5.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
"""The closed loop: eyes -> brain -> decoder -> safety -> drone -> eyes."""
from __future__ import annotations
import time
from dataclasses import dataclass, field
import numpy as np
from .brain import Brain
from .drones.base import Drone
from .drones.sim import SimDrone
from .motor import FlightCommand, MotorDecoder
from .safety import SafetyGovernor, Telemetry
from .senses import GestureIllusion, InputEncoder, Retina
from .senses.gestures import GestureState
@dataclass
class TickInfo:
t: float
frame: np.ndarray | None
rates: dict[str, float]
raw: FlightCommand
cmd: FlightCommand
tel: Telemetry
gesture: GestureState | None
illusion: str
raster: list = field(default_factory=list)
rtf: float = float("nan")
spikes: int = 0
brain_ms: float = 0.0 # brain clock at the end of this tick
class Pilot:
"""One brain flying one drone."""
def __init__(self, brain: Brain, drone: Drone, cfg: dict, gestures=None, webcam=None, name: str = "fly-1"):
self.name = name
self.brain = brain
self.drone = drone
self.cfg = cfg
self.gestures = gestures
self.webcam = webcam
self.retina = Retina.from_config(cfg)
self.encoder = InputEncoder(brain.connectome, cfg)
self.decoder = MotorDecoder(cfg)
self.safety = SafetyGovernor(cfg)
self.illusion = GestureIllusion()
self.history: list[dict] = []
self._t0 = None
def warmup(self, seconds: float, dt: float = 0.05) -> None:
"""Let the brain settle on the ground (still scene) and measure resting rates."""
from .motor.command import FlightCommand as _FC
t = -seconds
while t < 0:
frame = self.drone.frame() if self.drone.has_camera else None
vision = self.retina.encode(frame)
inputs = self.encoder.encode(vision, 0.0)
rates = self.brain.tick(inputs, ms=dt * 1000.0)
self.decoder.update(rates, dt)
t += dt
self.drone.send(_FC.hover("warmup done"))
def tick(self, t: float, dt: float) -> TickInfo:
frame = self.drone.frame() if self.drone.has_camera else None
cam = self.webcam.read() if self.webcam is not None else None
vision = self.retina.encode(frame)
g = None
if self.gestures is not None:
g = self.gestures.read(t, cam)
vision = self.illusion.apply(vision, g, t)
tel = self.drone.telemetry()
inputs = self.encoder.encode(vision, tel.yaw_rate_dps)
rates = self.brain.tick(inputs, ms=dt * 1000.0)
raw = self.decoder.update(rates, dt)
cmd = self.safety.filter(raw, tel, dt)
if self.safety.land_requested:
self.drone.land()
else:
self.drone.send(cmd)
self.history.append({"t": t, "alt": tel.alt_m, "x": tel.x_m, "y": tel.y_m, "yaw": tel.yaw_deg, **{f"cmd_{k}": getattr(cmd, k) for k in ("throttle", "yaw", "forward")},
"escape": cmd.escape, **{f"hz_{k}": v for k, v in rates.items() if k.startswith("DN")}})
return TickInfo(t, cam if cam is not None else frame, rates, raw, cmd, tel, g, self.illusion.mode if g is not None else "camera",
self.brain.last_raster, self.brain.realtime_factor, int(self.brain.last_counts.sum()),
self.brain.net.t_ms)
def run_sim(pilots: list[Pilot], seconds: float, hz: float = 20.0, on_tick=None, physics_substeps: int = 4) -> list[list[TickInfo]]:
"""Run pilots whose drones are SimDrones in simulated time (no sleeping)."""
dt = 1.0 / hz
out: list[list[TickInfo]] = [[] for _ in pilots]
for p in pilots:
p.drone.connect()
p.warmup(p.decoder.settle_s + 0.1, dt)
if p.cfg.get("control", {}).get("takeoff", True):
p.drone.takeoff()
steps = int(seconds * hz)
for k in range(steps):
t = k * dt
infos = []
for i, p in enumerate(pilots):
info = p.tick(t, dt)
out[i].append(info)
infos.append(info)
assert isinstance(p.drone, SimDrone)
for _ in range(physics_substeps):
p.drone.step(dt / physics_substeps)
if on_tick:
on_tick(k, infos)
return out
def run_realtime(pilot: Pilot, seconds: float | None = None, hz: float = 20.0, on_tick=None) -> None:
"""Fly real hardware. Ctrl+C lands."""
dt_target = 1.0 / hz
d = pilot.drone
d.connect()
try:
print(f"warming up the brain for {pilot.decoder.settle_s:.1f} s (drone stays on the ground)...")
pilot.warmup(pilot.decoder.settle_s + 0.1, dt_target)
if pilot.cfg.get("control", {}).get("takeoff", True):
d.takeoff()
t0 = last = time.monotonic()
while seconds is None or time.monotonic() - t0 < seconds:
now = time.monotonic()
dt = min(0.25, max(1e-3, now - last))
last = now
info = pilot.tick(now - t0, dt)
if on_tick and on_tick(info) is False:
break
if pilot.safety.land_requested:
print("safety: landing ->", "; ".join(pilot.safety.events[-3:]))
break
if info.rtf < 0.8:
print(f"warning: brain runs at {info.rtf:.2f}x real time - try a sensorimotor core (build-brain --core-hops 3)")
sleep = dt_target - (time.monotonic() - now)
if sleep > 0:
time.sleep(sleep)
except KeyboardInterrupt:
print("\nCtrl+C -> landing")
finally:
d.send(FlightCommand.hover("stop"))
d.land()
d.close()