SpaceOS flight simulator with ISS orbit and telemetry
at main 159 lines 5.1 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(** P0 flight simulator — minimal init process for the flight partition. 7 8 1. Mount devtmpfs, proc, sysfs 2. Open /dev/virtio-ports/ipc for writing 3. 9 Propagate ISS orbit via SGP4 and stream continuous telemetry *) 10 11let banner = 12 {| 13 ____ ___ 14| _ \ / _ \ 15| |_) | | | | 16| __/| |_| | 17|_| \___/ Flight Simulator 18|} 19 20let pr fmt = Fmt.pr (fmt ^^ "@.") 21let ipc_name = "ipc" 22 23let write_frame port (frame : Space_wire.Msg.t) = 24 let buf = Bytes.make Space_wire.Msg.frame_size '\x00' in 25 Wire.Codec.encode Space_wire.Msg.codec frame buf 0; 26 Zephyr.Virtio_port.write_all port (Bytes.unsafe_to_string buf) 27 28let send port kind ~apid payload = 29 pr "Sending %a: %s" Space_wire.Msg.pp_kind kind payload; 30 let frame = Space_wire.Msg.v kind ~apid payload in 31 write_frame port frame 32 33(* ISS TLE (epoch 2024) — good enough for a continuous demo. *) 34let iss_tle = 35 {|ISS (ZARYA) 361 25544U 98067A 24001.00000000 .00016717 00000-0 10270-3 0 9003 372 25544 51.6400 100.0000 0007417 50.0000 310.1280 15.49560000000017|} 38 39let rand_float lo hi = lo +. Random.float (hi -. lo) 40let rand_pick a = a.(Random.int (Array.length a)) 41 42(* Simulate slowly-varying onboard sensors *) 43let temp = ref 22.0 44let voltage = ref 3.3 45let cpu = ref 15 46 47let drift_sensor () = 48 temp := !temp +. rand_float (-0.3) 0.3; 49 temp := Float.max 10.0 (Float.min 40.0 !temp); 50 voltage := !voltage +. rand_float (-0.02) 0.02; 51 voltage := Float.max 3.0 (Float.min 3.6 !voltage); 52 cpu := !cpu + (Random.int 5 - 2); 53 cpu := max 5 (min 95 !cpu) 54 55let send_housekeeping port seq = 56 drift_sensor (); 57 match seq mod 5 with 58 | 0 -> send port TM ~apid:0x100 "heartbeat:ok" 59 | 1 -> 60 send port TM ~apid:0x102 61 (Fmt.str "sensor:temp=%.1f,voltage=%.2f" !temp !voltage) 62 | 2 -> 63 send port HEALTH ~apid:0x100 64 (Fmt.str "cpu=%d%%,mem=%dMB,uptime=%d" !cpu 65 (48 + Random.int 20) 66 (seq * 6)) 67 | 3 -> 68 let evt = 69 rand_pick 70 [| 71 "ORBIT_UPDATE:epoch"; 72 "CMD_ACK:0x42"; 73 "BEACON_SENT"; 74 "WATCHDOG_PET"; 75 "SENSOR_CAL:temp:done"; 76 |] 77 in 78 send port EVR ~apid:0x200 evt 79 | _ -> 80 let mode = rand_pick [| "nominal"; "nominal"; "nominal"; "safe" |] in 81 send port TM ~apid:0x103 (Fmt.str "status:%s" mode) 82 83let run_sim port = 84 let tle = 85 match Sgp4.parse_tle_string iss_tle with 86 | Ok t -> t 87 | Error e -> Fmt.failwith "TLE parse: %a" Sgp4.pp_error e 88 in 89 let state = 90 match Sgp4.init tle with 91 | Ok s -> s 92 | Error e -> Fmt.failwith "SGP4 init: %a" Sgp4.pp_error e 93 in 94 let gsto = Sgp4.gsto state in 95 pr "SGP4 initialized: %s (epoch=%.1f)" tle.name (Sgp4.epoch_unix tle); 96 (* Initial boot burst *) 97 send port TM ~apid:0x100 "heartbeat:ok"; 98 Unix.sleepf 0.2; 99 send port EVR ~apid:0x200 "IPC_LINK_UP:p1"; 100 Unix.sleepf 0.2; 101 send port EVR ~apid:0x200 "SGP4_INIT:ok"; 102 Unix.sleepf 0.2; 103 (* Continuous telemetry loop — 1 Hz orbit + mixed housekeeping *) 104 pr "Entering continuous telemetry loop..."; 105 let seq = ref 0 in 106 while true do 107 let tsince = float_of_int !seq *. 0.1 in 108 (* 0.1 min = 6s per tick *) 109 incr seq; 110 (* Orbit propagation *) 111 (match Sgp4.propagate state tle tsince with 112 | Ok (pos, vel) -> 113 let speed = 114 sqrt ((vel.vx *. vel.vx) +. (vel.vy *. vel.vy) +. (vel.vz *. vel.vz)) 115 in 116 let alt = 117 sqrt ((pos.x *. pos.x) +. (pos.y *. pos.y) +. (pos.z *. pos.z)) 118 -. 6378.135 119 in 120 let lat, lon, _alt_geo = Sgp4.to_geodetic pos gsto tsince in 121 send port TM ~apid:0x101 122 (Fmt.str "orbit:lat=%.2f,lon=%.2f,alt=%.1f,v=%.2f" lat lon alt speed) 123 | Error _ -> send port ERROR ~apid:0x1FF "sgp4:propagation_error"); 124 Unix.sleepf 0.3; 125 send_housekeeping port !seq; 126 Unix.sleepf 0.7 127 done 128 129let () = 130 Random.self_init (); 131 Eio_main.run @@ fun env -> 132 Eio.Switch.run @@ fun _sw -> 133 let fs = Eio.Stdenv.fs env in 134 let pid = Unix.getpid () in 135 Fmt.pr "%s@." banner; 136 pr "P0 Flight Simulator (PID %d)" pid; 137 if pid = 1 then begin 138 pr "Setting up filesystems..."; 139 Pid1.setup_filesystems ~fs; 140 Pid1.setup_signals (); 141 pr "Waiting for IPC device..."; 142 if not (Zephyr.Virtio_port.wait ipc_name ~timeout:5.0) then begin 143 pr "IPC device not found after 5s, powering off."; 144 Pid1.poweroff () 145 end; 146 let port = Zephyr.Virtio_port.open_write ipc_name in 147 Fun.protect 148 ~finally:(fun () -> Zephyr.Virtio_port.close port) 149 (fun () -> run_sim port) 150 end 151 else begin 152 pr "Not running as PID 1 (demo mode)"; 153 pr ""; 154 pr "In a real VM, this would:"; 155 pr " 1. Mount /proc, /sys, /dev"; 156 pr " 2. Open /dev/virtio-ports/ipc"; 157 pr " 3. Propagate ISS orbit via SGP4"; 158 pr " 4. Stream continuous TM, HEALTH, EVR frames" 159 end