1use crate::protocol::{Response, ResponseData};
2use comfy_table::{ContentArrangement, Table, presets::UTF8_HORIZONTAL_ONLY};
3use serde_json::json;
4
5pub fn print_response(response: &Response, json_mode: bool) {
6 if json_mode {
7 if let Some(ResponseData::WatchSamples { samples }) = &response.data {
8 for sample in samples {
9 let line = json!({
10 "tick": sample.tick,
11 "time_us": sample.time_us,
12 "name": sample.signal,
13 "value": sample.value
14 });
15 println!(
16 "{}",
17 serde_json::to_string(&line).unwrap_or_else(|_| "{}".to_string())
18 );
19 }
20 return;
21 }
22 println!(
23 "{}",
24 serde_json::to_string(response).unwrap_or_else(|_| {
25 "{\"success\":false,\"error\":\"failed to serialize response\"}".to_string()
26 })
27 );
28 return;
29 }
30
31 if !response.success {
32 eprintln!("{}", response.error.as_deref().unwrap_or("unknown error"));
33 return;
34 }
35 match &response.data {
36 Some(ResponseData::Ack) => println!("ok"),
37 Some(ResponseData::Loaded {
38 libpath,
39 signal_count,
40 }) => {
41 println!("Loaded: {libpath}");
42 println!("Signals: {signal_count}");
43 }
44 Some(ResponseData::ProjectInfo {
45 libpath,
46 tick_duration_us,
47 signal_count,
48 }) => {
49 println!("Loaded: true");
50 println!("Project: {libpath}");
51 println!("Tick(us): {tick_duration_us}");
52 println!("Signals: {signal_count}");
53 }
54 Some(ResponseData::Signals { signals }) => {
55 let mut table = Table::new();
56 table
57 .load_preset(UTF8_HORIZONTAL_ONLY)
58 .set_content_arrangement(ContentArrangement::Dynamic)
59 .set_header(vec!["ID", "Name", "Type", "Units"]);
60 for signal in signals {
61 table.add_row(vec![
62 signal.id.to_string(),
63 signal.name.clone(),
64 signal.signal_type.to_string(),
65 signal.units.clone().unwrap_or_else(|| "-".to_string()),
66 ]);
67 }
68 println!("{table}");
69 }
70 Some(ResponseData::SignalValues { values }) => {
71 let mut table = Table::new();
72 table
73 .load_preset(UTF8_HORIZONTAL_ONLY)
74 .set_header(vec!["ID", "Name", "Type", "Value", "Units"]);
75 for signal in values {
76 table.add_row(vec![
77 signal.id.to_string(),
78 signal.name.clone(),
79 signal.signal_type.to_string(),
80 format!("{:?}", signal.value),
81 signal.units.clone().unwrap_or_else(|| "-".to_string()),
82 ]);
83 }
84 println!("{table}");
85 }
86 Some(ResponseData::SignalSample {
87 tick,
88 time_us,
89 values,
90 }) => {
91 println!("Tick: {tick}");
92 println!("Time(us): {time_us}");
93 let mut table = Table::new();
94 table
95 .load_preset(UTF8_HORIZONTAL_ONLY)
96 .set_header(vec!["ID", "Name", "Type", "Value", "Units"]);
97 for signal in values {
98 table.add_row(vec![
99 signal.id.to_string(),
100 signal.name.clone(),
101 signal.signal_type.to_string(),
102 format!("{:?}", signal.value),
103 signal.units.clone().unwrap_or_else(|| "-".to_string()),
104 ]);
105 }
106 println!("{table}");
107 }
108 Some(ResponseData::SetResult { writes_applied }) => {
109 println!("Writes applied: {writes_applied}");
110 }
111 Some(ResponseData::TimeStatus {
112 state,
113 elapsed_ticks,
114 elapsed_time_us,
115 speed,
116 }) => {
117 println!(
118 "State: {:?} Ticks: {} Sim-time: {:.6}s Speed: {}x",
119 state,
120 elapsed_ticks,
121 *elapsed_time_us as f64 / 1_000_000.0,
122 speed
123 );
124 }
125 Some(ResponseData::TimeAdvanced {
126 requested_us,
127 advanced_ticks,
128 advanced_us,
129 }) => {
130 println!(
131 "Requested: {}us Advanced: {} ticks ({}us)",
132 requested_us, advanced_ticks, advanced_us
133 );
134 }
135 Some(ResponseData::Speed { speed }) => println!("{speed}"),
136 Some(ResponseData::CanBuses { buses }) => {
137 let mut table = Table::new();
138 table.load_preset(UTF8_HORIZONTAL_ONLY).set_header(vec![
139 "ID",
140 "Bus",
141 "Bitrate",
142 "Data Bitrate",
143 "FD",
144 "Attached",
145 ]);
146 for bus in buses {
147 table.add_row(vec![
148 bus.id.to_string(),
149 bus.name.clone(),
150 bus.bitrate.to_string(),
151 if bus.bitrate_data == 0 {
152 "-".to_string()
153 } else {
154 bus.bitrate_data.to_string()
155 },
156 bus.fd_capable.to_string(),
157 bus.attached_iface
158 .clone()
159 .unwrap_or_else(|| "-".to_string()),
160 ]);
161 }
162 println!("{table}");
163 }
164 Some(ResponseData::CanSend { bus, arb_id, len }) => {
165 println!("Sent frame on {bus}: id=0x{arb_id:X} len={len}");
166 }
167 Some(ResponseData::CanInspect { bus, frames }) => {
168 println!("Bus: {bus}");
169 let mut table = Table::new();
170 table
171 .load_preset(UTF8_HORIZONTAL_ONLY)
172 .set_header(vec!["Arb ID", "Len", "Flags", "Data"]);
173 for frame in frames {
174 table.add_row(vec![
175 format!("0x{:X}", frame.arb_id),
176 frame.len.to_string(),
177 format!("0x{:02X}", frame.flags),
178 frame.data_hex.clone(),
179 ]);
180 }
181 println!("{table}");
182 }
183 Some(ResponseData::CanSchedules { schedules }) => {
184 let mut table = Table::new();
185 table.load_preset(UTF8_HORIZONTAL_ONLY).set_header(vec![
186 "Job",
187 "Bus",
188 "Arb ID",
189 "Data",
190 "Flags",
191 "Every (ticks)",
192 "Next due",
193 "Enabled",
194 ]);
195 for schedule in schedules {
196 table.add_row(vec![
197 schedule.job_id.clone(),
198 schedule.bus.clone(),
199 format!("0x{:X}", schedule.arb_id),
200 schedule.data_hex.clone(),
201 format!("0x{:02X}", schedule.flags),
202 schedule.every_ticks.to_string(),
203 schedule.next_due_tick.to_string(),
204 schedule.enabled.to_string(),
205 ]);
206 }
207 println!("{table}");
208 }
209 Some(ResponseData::DbcLoaded { bus, signal_count }) => {
210 println!("Loaded DBC for {bus}: {signal_count} signals");
211 }
212 Some(ResponseData::SharedChannels { channels }) => {
213 let mut table = Table::new();
214 table
215 .load_preset(UTF8_HORIZONTAL_ONLY)
216 .set_header(vec!["ID", "Channel", "Slots"]);
217 for channel in channels {
218 table.add_row(vec![
219 channel.id.to_string(),
220 channel.name.clone(),
221 channel.slot_count.to_string(),
222 ]);
223 }
224 println!("{table}");
225 }
226 Some(ResponseData::SharedValues { channel, slots }) => {
227 println!("Channel: {channel}");
228 let mut table = Table::new();
229 table
230 .load_preset(UTF8_HORIZONTAL_ONLY)
231 .set_header(vec!["Slot", "Type", "Value"]);
232 for slot in slots {
233 table.add_row(vec![
234 slot.slot_id.to_string(),
235 slot.signal_type.to_string(),
236 format!("{:?}", slot.value),
237 ]);
238 }
239 println!("{table}");
240 }
241 Some(ResponseData::WatchSamples { samples }) => {
242 for sample in samples {
243 println!(
244 "tick={} time_us={} {}={:?}",
245 sample.tick, sample.time_us, sample.signal, sample.value
246 );
247 }
248 }
249 Some(ResponseData::RecipeResult {
250 recipe,
251 dry_run,
252 steps_executed,
253 steps,
254 }) => {
255 println!("Recipe: {recipe}");
256 println!("Dry run: {dry_run}");
257 println!("Steps: {steps_executed}");
258 for step in steps {
259 let instance = step.instance.as_deref().unwrap_or("-");
260 println!("- [{:?}] {} {}", step.kind, instance, step.detail);
261 }
262 }
263 Some(ResponseData::EnvStatus {
264 env,
265 running,
266 instance_count,
267 tick_duration_us,
268 }) => {
269 println!("Env: {env}");
270 println!("Running: {running}");
271 println!("Instances: {instance_count}");
272 println!("Tick(us): {tick_duration_us}");
273 }
274 Some(ResponseData::InstanceStatus {
275 instance,
276 socket_path,
277 running,
278 env,
279 }) => {
280 println!("Instance: {instance}");
281 println!("Socket: {socket_path}");
282 println!("Running: {running}");
283 println!("Env: {}", env.clone().unwrap_or_else(|| "-".to_string()));
284 }
285 Some(ResponseData::InstanceList { instances }) => {
286 let mut table = Table::new();
287 table
288 .load_preset(UTF8_HORIZONTAL_ONLY)
289 .set_header(vec!["Instance", "Running", "Env", "Socket"]);
290 for item in instances {
291 table.add_row(vec![
292 item.name.clone(),
293 item.running.to_string(),
294 item.env.clone().unwrap_or_else(|| "-".to_string()),
295 item.socket_path.clone(),
296 ]);
297 }
298 println!("{table}");
299 }
300 None => println!("ok"),
301 }
302}