openc3-cosmos-fakesat 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,628 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2022 OpenC3, Inc.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is free software; you can modify and/or redistribute it
7
+ # under the terms of the GNU Affero General Public License
8
+ # as published by the Free Software Foundation; version 3 with
9
+ # attribution addendums as found in the LICENSE.txt
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+
16
+ # Simulates the fake satellite used in COSMOS User Training
17
+
18
+ require 'openc3'
19
+
20
+ module OpenC3
21
+ MAX_PWR_WATT_SECONDS = 100000
22
+ INIT_PWR_WATT_SECONDS = 25000
23
+ HYSTERESIS = 2.0
24
+
25
+ # Simulated satellite for the training. Populates several packets and cycles
26
+ # the telemetry to simulate a real satellite.
27
+ class SimSat < SimulatedTarget
28
+ def initialize(target_name)
29
+ super(target_name)
30
+
31
+ @target = System.targets[target_name]
32
+ position_filename = File.join(@target.dir, 'data', 'position.bin')
33
+ attitude_filename = File.join(@target.dir, 'data', 'attitude.bin')
34
+ @position_file = File.open(position_filename, 'rb')
35
+ @attitude_file = File.open(attitude_filename, 'rb')
36
+ @position_file_size = File.size(position_filename)
37
+ @attitude_file_size = File.size(attitude_filename)
38
+ @position_file_bytes_read = 0
39
+ @attitude_file_bytes_read = 0
40
+
41
+ @pos_packet = Structure.new(:BIG_ENDIAN)
42
+ @pos_packet.append_item('DAY', 16, :UINT)
43
+ @pos_packet.append_item('MSOD', 32, :UINT)
44
+ @pos_packet.append_item('USOMS', 16, :UINT)
45
+ @pos_packet.append_item('POSX', 32, :FLOAT)
46
+ @pos_packet.append_item('POSY', 32, :FLOAT)
47
+ @pos_packet.append_item('POSZ', 32, :FLOAT)
48
+ @pos_packet.append_item('SPARE1', 16, :UINT)
49
+ @pos_packet.append_item('SPARE2', 32, :UINT)
50
+ @pos_packet.append_item('SPARE3', 16, :UINT)
51
+ @pos_packet.append_item('VELX', 32, :FLOAT)
52
+ @pos_packet.append_item('VELY', 32, :FLOAT)
53
+ @pos_packet.append_item('VELZ', 32, :FLOAT)
54
+ @pos_packet.append_item('SPARE4', 32, :UINT)
55
+ @pos_packet.enable_method_missing
56
+
57
+ @att_packet = Structure.new(:BIG_ENDIAN)
58
+ @att_packet.append_item('DAY', 16, :UINT)
59
+ @att_packet.append_item('MSOD', 32, :UINT)
60
+ @att_packet.append_item('USOMS', 16, :UINT)
61
+ @att_packet.append_item('Q1', 32, :FLOAT)
62
+ @att_packet.append_item('Q2', 32, :FLOAT)
63
+ @att_packet.append_item('Q3', 32, :FLOAT)
64
+ @att_packet.append_item('Q4', 32, :FLOAT)
65
+ @att_packet.append_item('BIASX', 32, :FLOAT)
66
+ @att_packet.append_item('BIASY', 32, :FLOAT)
67
+ @att_packet.append_item('BIASZ', 32, :FLOAT)
68
+ @att_packet.append_item('SPARE', 32, :FLOAT)
69
+ @att_packet.enable_method_missing
70
+
71
+ # Initialize fixed parts of packets
72
+ packet = @tlm_packets['HEALTH_STATUS']
73
+ packet.enable_method_missing
74
+ packet.CcsdsSeqFlags = 'NOGROUP'
75
+ packet.CcsdsLength = packet.buffer.length - 7
76
+
77
+ packet = @tlm_packets['THERMAL']
78
+ packet.enable_method_missing
79
+ packet.CcsdsSeqFlags = 'NOGROUP'
80
+ packet.CcsdsLength = packet.buffer.length - 7
81
+
82
+ packet = @tlm_packets['EVENT']
83
+ packet.enable_method_missing
84
+ packet.CcsdsSeqFlags = 'NOGROUP'
85
+ packet.CcsdsLength = packet.buffer.length - 7
86
+
87
+ packet = @tlm_packets['ADCS']
88
+ packet.enable_method_missing
89
+ packet.CcsdsSeqFlags = 'NOGROUP'
90
+ packet.CcsdsLength = packet.buffer.length - 7
91
+
92
+ packet = @tlm_packets['IMAGE']
93
+ packet.enable_method_missing
94
+ packet.CcsdsSeqFlags = 'NOGROUP'
95
+ packet.image = ("\x05" * 10000) + "The Secret is Astral Body"
96
+ packet.CcsdsLength = packet.buffer.length - 7
97
+
98
+ packet = @tlm_packets['MECH']
99
+ packet.enable_method_missing
100
+ packet.CcsdsSeqFlags = 'NOGROUP'
101
+ packet.CcsdsLength = packet.buffer.length - 7
102
+
103
+ packet = @tlm_packets['IMAGER']
104
+ packet.enable_method_missing
105
+ packet.CcsdsSeqFlags = 'NOGROUP'
106
+ packet.CcsdsLength = packet.buffer.length - 7
107
+
108
+ @get_count = 0
109
+ @queue = Queue.new
110
+
111
+ # ADCS
112
+ @trackStars = Array.new
113
+ @trackStars[0] = 1237
114
+ @trackStars[1] = 1329
115
+ @trackStars[2] = 1333
116
+ @trackStars[3] = 1139
117
+ @trackStars[4] = 1161
118
+ @trackStars[5] = 682
119
+ @trackStars[6] = 717
120
+ @trackStars[7] = 814
121
+ @trackStars[8] = 583
122
+ @trackStars[9] = 622
123
+ @adcs_ctrl = 'OFF'
124
+ @sr_ang_to_sun = 0
125
+
126
+ # HEALTH_STATUS
127
+ @cmd_acpt_cnt = 0
128
+ @cmd_rjct_cnt = 0
129
+ @mode = 'SAFE'
130
+ @cpu_pwr = 100
131
+ @table_data = "\x00" * 10
132
+
133
+ # THERMAL
134
+ @temp1 = 0
135
+ @temp2 = 0
136
+ @heater1_ctrl = 'OFF'
137
+ @heater1_state = 'OFF'
138
+ @heater1_setpt = 0.0
139
+ @heater1_pwr = 0.0
140
+ @heater2_ctrl = 'OFF'
141
+ @heater2_state = 'OFF'
142
+ @heater2_setpt = 0.0
143
+ @heater2_pwr = 0.0
144
+
145
+ # MECH
146
+ @slrpnl1_ang = 180.0
147
+ @slrpnl2_ang = 180.0
148
+ @slrpnl1_state = 'STOWED'
149
+ @slrpnl2_state = 'STOWED'
150
+ @slrpnl1_pwr = 0.0
151
+ @slrpnl2_pwr = 0.0
152
+ @pwr_watt_seconds = INIT_PWR_WATT_SECONDS
153
+ @battery = (@pwr_watt_seconds.to_f / MAX_PWR_WATT_SECONDS.to_f) * 100.0
154
+
155
+ # IMAGER
156
+ @collects = 0
157
+ @duration = 10
158
+ @collect_type = 'NORMAL'
159
+ @imager_state = 'OFF'
160
+ @imager_pwr = 0.0
161
+ @collect_end_time = nil
162
+ end
163
+
164
+ def set_rates
165
+ set_rate('ADCS', 10)
166
+ set_rate('HEALTH_STATUS', 100)
167
+ set_rate('THERMAL', 100)
168
+ set_rate('MECH', 100)
169
+ set_rate('IMAGER', 100)
170
+ end
171
+
172
+ def accept_cmd(message = nil)
173
+ if message
174
+ event_packet = @tlm_packets['EVENT']
175
+ event_packet.message = message
176
+ time = Time.now
177
+ event_packet.timesec = time.tv_sec
178
+ event_packet.timeus = time.tv_usec
179
+ event_packet.ccsdsseqcnt += 1
180
+ @queue << event_packet.dup
181
+ end
182
+ @cmd_acpt_cnt += 1
183
+ end
184
+
185
+ def reject_cmd(message)
186
+ event_packet = @tlm_packets['EVENT']
187
+ event_packet.message = message
188
+ time = Time.now
189
+ event_packet.timesec = time.tv_sec
190
+ event_packet.timeus = time.tv_usec
191
+ event_packet.ccsdsseqcnt += 1
192
+ @queue << event_packet.dup
193
+ @cmd_rjct_cnt += 1
194
+ end
195
+
196
+ def write(packet)
197
+ name = packet.packet_name.upcase
198
+
199
+ case name
200
+ when 'COLLECT'
201
+ if @mode == 'OPERATE'
202
+ @collects += 1
203
+ @duration = packet.read('duration')
204
+ @collect_type = packet.read("type")
205
+ @collect_end_time = Time.now + @duration
206
+ accept_cmd()
207
+ else
208
+ reject_cmd("Mode must be OPERATE to collect images")
209
+ end
210
+
211
+ when 'ABORT'
212
+ @collect_end_time = nil
213
+ accept_cmd()
214
+
215
+ when 'CLEAR'
216
+ accept_cmd()
217
+ @collects = 0
218
+ @cmd_acpt_cnt = 0
219
+ @cmd_rjct_cnt = 0
220
+
221
+ when 'SET_MODE'
222
+ mode = packet.read('mode')
223
+ case mode
224
+ when 'SAFE'
225
+ @mode = mode
226
+ accept_cmd()
227
+ when 'CHECKOUT'
228
+ if @battery >= 50.0
229
+ @mode = mode
230
+ accept_cmd()
231
+ else
232
+ reject_cmd("Cannot enter checkout if battery < 50.0%")
233
+ end
234
+ when 'OPERATE'
235
+ if @temp1 < 35.0 and @temp1 > 25.0 and @temp2 < 35.0 and @temp2 > 25.0
236
+ @mode = mode
237
+ accept_cmd()
238
+ else
239
+ reject_cmd("Cannot enter OPERATE unless temperatures are stable near 30.0")
240
+ end
241
+ else
242
+ reject_cmd("Invalid Mode: #{mode}")
243
+ end
244
+
245
+ when 'SLRPNLDEPLOY'
246
+ num = packet.read('NUM')
247
+ case num
248
+ when 1
249
+ @slrpnl1_state = 'DEPLOYED'
250
+ accept_cmd()
251
+ when 2
252
+ @slrpnl2_state = 'DEPLOYED'
253
+ accept_cmd()
254
+ else
255
+ reject_cmd("Invalid Solar Array Number: #{num}")
256
+ end
257
+
258
+ when 'SLRPNLRESET'
259
+ num = packet.read('NUM')
260
+ case num
261
+ when 1
262
+ @slrpnl1_state = 'STOWED'
263
+ accept_cmd()
264
+ when 2
265
+ @slrpnl2_state = 'STOWED'
266
+ accept_cmd()
267
+ else
268
+ reject_cmd("Invalid Solar Array Number: #{num}")
269
+ end
270
+
271
+ when 'SLRPNLANG'
272
+ num = packet.read('NUM')
273
+ ang = packet.read('ANG')
274
+ case num
275
+ when 1
276
+ case ang
277
+ when 0..360
278
+ @slrpnl1_ang = ang
279
+ accept_cmd()
280
+ else
281
+ reject_cmd("Invalid Solar Array Angle: #{setpt}")
282
+ end
283
+ when 2
284
+ case ang
285
+ when 0..360
286
+ @slrpnl2_ang = ang
287
+ accept_cmd()
288
+ else
289
+ reject_cmd("Invalid Solar Array Angle: #{setpt}")
290
+ end
291
+ else
292
+ reject_cmd("Invalid Solar Array Number: #{num}")
293
+ end
294
+
295
+ when 'TABLE_LOAD'
296
+ @table_data = packet.read('DATA')
297
+
298
+ when 'HTR_CTRL'
299
+ num = packet.read('NUM')
300
+ state = packet.read('STATE')
301
+ case num
302
+ when 1
303
+ case state
304
+ when 'ON', 'OFF'
305
+ @heater1_ctrl = state
306
+ accept_cmd()
307
+ else
308
+ reject_cmd("Invalid Heater Control: #{state}")
309
+ end
310
+ when 2
311
+ case state
312
+ when 'ON', 'OFF'
313
+ @heater2_ctrl = state
314
+ accept_cmd()
315
+ else
316
+ reject_cmd("Invalid Heater Control: #{state}")
317
+ end
318
+ else
319
+ reject_cmd("Invalid Heater Number: #{num}")
320
+ end
321
+
322
+ when 'HTR_STATE'
323
+ num = packet.read('NUM')
324
+ state = packet.read('STATE')
325
+ case num
326
+ when 1
327
+ case state
328
+ when 'ON', 'OFF'
329
+ @heater1_state = state
330
+ accept_cmd()
331
+ else
332
+ reject_cmd("Invalid Heater State: #{state}")
333
+ end
334
+ when 2
335
+ case state
336
+ when 'ON', 'OFF'
337
+ @heater2_state = state
338
+ accept_cmd()
339
+ else
340
+ reject_cmd("Invalid Heater State: #{state}")
341
+ end
342
+ else
343
+ reject_cmd("Invalid Heater Number: #{num}")
344
+ end
345
+
346
+ when 'HTR_SETPT'
347
+ num = packet.read('NUM')
348
+ setpt = packet.read('SETPT')
349
+ case num
350
+ when 1
351
+ case setpt
352
+ when -100..100
353
+ @heater1_setpt = setpt
354
+ accept_cmd()
355
+ else
356
+ reject_cmd("Invalid Heater Setpoint: #{setpt}")
357
+ end
358
+ when 2
359
+ case setpt
360
+ when -100..100
361
+ @heater2_setpt = setpt
362
+ accept_cmd()
363
+ else
364
+ reject_cmd("Invalid Heater Setpoint: #{setpt}")
365
+ end
366
+ else
367
+ reject_cmd("Invalid Heater Number: #{num}")
368
+ end
369
+
370
+ when 'ADCS_CTRL'
371
+ state = packet.read('STATE')
372
+ case state
373
+ when 'ON', 'OFF'
374
+ @adcs_ctrl = state
375
+ accept_cmd()
376
+ else
377
+ reject_cmd("Invalid ADCS Control: #{state}")
378
+ end
379
+
380
+ when 'IMAGER_STATE'
381
+
382
+
383
+ end
384
+ end
385
+
386
+ def graceful_kill
387
+ end
388
+
389
+ def get_pending_packets(count_100hz)
390
+ pending_packets = super(count_100hz)
391
+ while @queue.length > 0
392
+ pending_packets << @queue.pop
393
+ end
394
+ pending_packets
395
+ end
396
+
397
+ def read(count_100hz, time)
398
+ pending_packets = get_pending_packets(count_100hz)
399
+
400
+ pending_packets.each do |packet|
401
+ case packet.packet_name
402
+ when 'ADCS'
403
+ # Read 44 Bytes for Position Data
404
+ pos_data = nil
405
+ begin
406
+ pos_data = @position_file.read(44)
407
+ @position_file_bytes_read += 44
408
+ rescue
409
+ # Do Nothing
410
+ end
411
+
412
+ if pos_data.nil? or pos_data.length == 0
413
+ # Assume end of file - close and reopen
414
+ @position_file.close
415
+ @position_file = File.open(File.join(@target.dir, 'data', 'position.bin'), 'rb')
416
+ pos_data = @position_file.read(44)
417
+ @position_file_bytes_read = 44
418
+ end
419
+
420
+ @pos_packet.buffer = pos_data
421
+ packet.posx = @pos_packet.posx
422
+ packet.posy = @pos_packet.posy
423
+ packet.posz = @pos_packet.posz
424
+ packet.velx = @pos_packet.velx
425
+ packet.vely = @pos_packet.vely
426
+ packet.velz = @pos_packet.velz
427
+
428
+ # Read 40 Bytes for Attitude Data
429
+ att_data = nil
430
+ begin
431
+ att_data = @attitude_file.read(40)
432
+ @attitude_file_bytes_read += 40
433
+ rescue
434
+ # Do Nothing
435
+ end
436
+
437
+ if att_data.nil? or att_data.length == 0
438
+ @attitude_file.close
439
+ @attitude_file = File.open(File.join(@target.dir, 'data', 'attitude.bin'), 'rb')
440
+ att_data = @attitude_file.read(40)
441
+ @attitude_file_bytes_read = 40
442
+ end
443
+
444
+ @att_packet.buffer = att_data
445
+ packet.q1 = @att_packet.q1
446
+ packet.q2 = @att_packet.q2
447
+ packet.q3 = @att_packet.q3
448
+ packet.q4 = @att_packet.q4
449
+ packet.biasx = @att_packet.biasx
450
+ packet.biasy = @att_packet.biasy
451
+ packet.biasy = @att_packet.biasz
452
+
453
+ packet.star1id = @trackStars[((@get_count / 100) + 0) % 10]
454
+ packet.star2id = @trackStars[((@get_count / 100) + 1) % 10]
455
+ packet.star3id = @trackStars[((@get_count / 100) + 2) % 10]
456
+ packet.star4id = @trackStars[((@get_count / 100) + 3) % 10]
457
+ packet.star5id = @trackStars[((@get_count / 100) + 4) % 10]
458
+
459
+ packet.posprogress = (@position_file_bytes_read.to_f / @position_file_size.to_f) * 100.0
460
+ packet.attprogress = (@attitude_file_bytes_read.to_f / @attitude_file_size.to_f) * 100.0
461
+ @sr_ang_to_sun = packet.posprogress * 3.6
462
+ packet.sr_ang_to_sun = @sr_ang_to_sun
463
+ packet.adcs_ctrl = @adcs_ctrl
464
+
465
+ packet.timesec = time.tv_sec
466
+ packet.timeus = time.tv_usec
467
+ packet.ccsdsseqcnt += 1
468
+
469
+ when 'HEALTH_STATUS'
470
+ packet.timesec = time.tv_sec
471
+ packet.timeus = time.tv_usec
472
+ packet.ccsdsseqcnt += 1
473
+
474
+ packet.cmd_acpt_cnt = @cmd_acpt_cnt
475
+ packet.cmd_rjct_cnt = @cmd_rjct_cnt
476
+ packet.mode = @mode
477
+ packet.cpu_pwr = @cpu_pwr
478
+ packet.table_data = @table_data
479
+
480
+ when 'THERMAL'
481
+ packet.timesec = time.tv_sec
482
+ packet.timeus = time.tv_usec
483
+ packet.ccsdsseqcnt += 1
484
+
485
+ if @heater1_ctrl == 'ON'
486
+ if @temp1 < (@heater1_setpt - HYSTERESIS)
487
+ @heater1_state = 'ON'
488
+ elsif @temp1 > (@heater1_setpt + HYSTERESIS)
489
+ @heater1_state = 'OFF'
490
+ end
491
+ end
492
+
493
+ if @heater2_ctrl == 'ON'
494
+ if @temp2 < (@heater2_setpt - HYSTERESIS)
495
+ @heater2_state = 'ON'
496
+ elsif @temp2 > (@heater2_setpt + HYSTERESIS)
497
+ @heater2_state = 'OFF'
498
+ end
499
+ end
500
+
501
+ if @heater1_state == 'ON'
502
+ @heater1_pwr = 300
503
+ @temp1 += 0.5
504
+ if @temp1 > 100.0
505
+ @temp1 = 100.0
506
+ end
507
+ else
508
+ @heater1_pwr = 0
509
+ @temp1 -= 0.1
510
+ if @temp1 < -20.0
511
+ @temp1 = -20.0
512
+ end
513
+ end
514
+
515
+ if @heater2_state == 'ON'
516
+ @heater2_pwr = 300
517
+ @temp2 += 0.5
518
+ if @temp2 > 100.0
519
+ @temp2 = 100.0
520
+ end
521
+ else
522
+ @heater2_pwr = 0
523
+ @temp2 -= 0.1
524
+ if @temp2 < -20.0
525
+ @temp2 = -20.0
526
+ end
527
+ end
528
+
529
+ packet.heater1_ctrl = @heater1_ctrl
530
+ packet.heater1_state = @heater1_state
531
+ packet.heater1_setpt = @heater1_setpt
532
+ packet.heater1_pwr = @heater1_pwr
533
+ packet.heater2_ctrl = @heater2_ctrl
534
+ packet.heater2_state = @heater2_state
535
+ packet.heater2_setpt = @heater2_setpt
536
+ packet.heater2_pwr = @heater2_pwr
537
+ packet.temp1 = @temp1
538
+ packet.temp2 = @temp2
539
+
540
+ when 'MECH'
541
+ if @adcs_ctrl == 'ON'
542
+ @slrpnl1_ang = @sr_ang_to_sun
543
+ @slrpnl2_ang = @sr_ang_to_sun
544
+ end
545
+
546
+ delta_ang = (@sr_ang_to_sun - @slrpnl1_ang).abs
547
+ if delta_ang > 180.0
548
+ delta_ang = 360 - delta_ang
549
+ end
550
+ if @slrpnl1_state == 'DEPLOYED'
551
+ @slrpnl1_pwr = 500 * (1 - (delta_ang / 180.0))
552
+ else
553
+ @slrpnl1_pwr = 0
554
+ end
555
+
556
+ delta_ang = (@sr_ang_to_sun - @slrpnl2_ang).abs
557
+ if delta_ang > 180.0
558
+ delta_ang = 360 - delta_ang
559
+ end
560
+ if @slrpnl2_state == 'DEPLOYED'
561
+ @slrpnl2_pwr = 500 * (1 - (delta_ang / 180.0))
562
+ else
563
+ @slrpnl2_pwr = 0
564
+ end
565
+
566
+ incoming_pwr = @slrpnl1_pwr + @slrpnl2_pwr # Upto 1000 per second
567
+
568
+ used_pwr = @cpu_pwr + @imager_pwr + @heater1_pwr + @heater2_pwr # Up to 900 per second
569
+ delta_pwr = incoming_pwr - used_pwr
570
+ @pwr_watt_seconds += delta_pwr
571
+ if @pwr_watt_seconds < 0
572
+ @pwr_watt_seconds = 100
573
+ elsif @pwr_watt_seconds > MAX_PWR_WATT_SECONDS
574
+ @pwr_watt_seconds = MAX_PWR_WATT_SECONDS
575
+ end
576
+ @battery = (@pwr_watt_seconds.to_f / MAX_PWR_WATT_SECONDS.to_f) * 100.0
577
+ if @battery < 50.0
578
+ @mode = 'SAFE'
579
+ end
580
+
581
+ packet.timesec = time.tv_sec
582
+ packet.timeus = time.tv_usec
583
+ packet.ccsdsseqcnt += 1
584
+ packet.slrpnl1_ang = @slrpnl1_ang
585
+ packet.slrpnl2_ang = @slrpnl2_ang
586
+ packet.slrpnl1_state = @slrpnl1_state
587
+ packet.slrpnl2_state = @slrpnl2_state
588
+ packet.slrpnl1_pwr = @slrpnl1_pwr
589
+ packet.slrpnl2_pwr = @slrpnl2_pwr
590
+ packet.battery = @battery
591
+
592
+ when 'IMAGER'
593
+ if @collect_end_time
594
+ if @collect_end_time < Time.now
595
+ @imager_state = 'OFF'
596
+ @collect_end_time = nil
597
+ @imager_pwr = 0
598
+ image_packet = @tlm_packets['IMAGE']
599
+ time = Time.now
600
+ image_packet.timesec = time.tv_sec
601
+ image_packet.timeus = time.tv_usec
602
+ image_packet.ccsdsseqcnt += 1
603
+ @queue << image_packet.dup
604
+ else
605
+ @imager_state = 'ON'
606
+ @imager_pwr = 200
607
+ end
608
+ else
609
+ @imager_pwr = 0
610
+ end
611
+
612
+ packet.timesec = time.tv_sec
613
+ packet.timeus = time.tv_usec
614
+ packet.ccsdsseqcnt += 1
615
+ packet.collects = @collects
616
+ packet.duration = @duration
617
+ packet.collect_type = @collect_type
618
+ packet.imager_state = @imager_state
619
+ packet.imager_pwr = @imager_pwr
620
+
621
+ end
622
+ end
623
+
624
+ @get_count += 1
625
+ pending_packets
626
+ end
627
+ end
628
+ end
@@ -0,0 +1,25 @@
1
+ cmd("FAKESAT SLRPNLDEPLOY with NUM 1")
2
+ wait_check("FAKESAT MECH SLRPNL1_STATE == 'DEPLOYED'", 5)
3
+ cmd("FAKESAT SLRPNLDEPLOY with NUM 2")
4
+ wait_check("FAKESAT MECH SLRPNL2_STATE == 'DEPLOYED'", 5)
5
+ cmd("FAKESAT ADCS_CTRL with STATE ON")
6
+ wait_check("FAKESAT ADCS ADCS_CTRL == 'ON'", 5)
7
+
8
+ wait_check("FAKESAT MECH BATTERY > 50.0", 240)
9
+
10
+ cmd("FAKESAT HTR_SETPT with NUM 1, SETPT 30.0")
11
+ wait_check_tolerance("FAKESAT THERMAL HEATER1_SETPT", 30.0, 1.0, 5)
12
+ cmd("FAKESAT HTR_SETPT with NUM 2, SETPT 30.0")
13
+ wait_check_tolerance("FAKESAT THERMAL HEATER2_SETPT", 30.0, 1.0, 5)
14
+ cmd("FAKESAT HTR_CTRL with NUM 1, STATE ON")
15
+ wait_check("FAKESAT THERMAL HEATER1_CTRL == 'ON'", 5)
16
+ cmd("FAKESAT HTR_CTRL with NUM 2, STATE ON")
17
+ wait_check("FAKESAT THERMAL HEATER2_CTRL == 'ON'", 5)
18
+
19
+ wait_check("FAKESAT THERMAL TEMP1 > 25.0", 240)
20
+ wait_check("FAKESAT THERMAL TEMP2 > 25.0", 240)
21
+
22
+ cmd("FAKESAT SET_MODE with MODE CHECKOUT")
23
+ wait_check("FAKESAT HEALTH_STATUS MODE == 'CHECKOUT'", 5)
24
+ cmd("FAKESAT SET_MODE with MODE OPERATE")
25
+ wait_check("FAKESAT HEALTH_STATUS MODE == 'OPERATE'", 5)
@@ -0,0 +1,11 @@
1
+ # Note hex 0x20 is ASCII space ' ' character
2
+ data = "\x20" * 10
3
+ cmd("FAKESAT TABLE_LOAD with DATA #{data}")
4
+ cmd("FAKESAT", "TABLE_LOAD", "DATA" => data)
5
+ wait 2 # Allow telemetry to change
6
+ # Can't use check for binary data so we grab the data
7
+ # and check simply using Ruby comparison
8
+ block = tlm("FAKESAT HEALTH_STATUS TABLE_DATA")
9
+ if data != block
10
+ raise "TABLE_DATA not updated correctly!"
11
+ end
@@ -0,0 +1,48 @@
1
+ require 'openc3/script/suite.rb'
2
+ load_utility 'FAKESAT/lib/fake_sat.rb'
3
+
4
+ class CollectGroup < OpenC3::Group
5
+ def script_normal_collect
6
+ puts "Running #{OpenC3::Group.current_suite}:#{OpenC3::Group.current_group}:#{OpenC3::Group.current_script}"
7
+ OpenC3::Group.puts "Perform Normal Collect"
8
+
9
+ cmd_cnt = tlm("FAKESAT HEALTH_STATUS CMD_ACPT_CNT")
10
+ collect_cnt = tlm("FAKESAT IMAGER COLLECTS")
11
+ cmd("FAKESAT COLLECT with TYPE NORMAL, DURATION 5")
12
+ wait_check("FAKESAT HEALTH_STATUS CMD_ACPT_CNT == #{cmd_cnt + 1}", 5)
13
+ wait_check("FAKESAT IMAGER COLLECTS == #{collect_cnt + 1}", 5)
14
+ wait_check("FAKESAT IMAGER COLLECT_TYPE == 'NORMAL'", 5)
15
+ end
16
+
17
+ def script_special_collect
18
+ puts "Running #{OpenC3::Group.current_suite}:#{OpenC3::Group.current_group}:#{OpenC3::Group.current_script}"
19
+ OpenC3::Group.puts "Perform Special Collect"
20
+
21
+ cmd_cnt = tlm("FAKESAT HEALTH_STATUS CMD_ACPT_CNT")
22
+ collect_cnt = tlm("FAKESAT IMAGER COLLECTS")
23
+ cmd("FAKESAT COLLECT with TYPE SPECIAL, DURATION 5")
24
+ wait_check("FAKESAT HEALTH_STATUS CMD_ACPT_CNT == #{cmd_cnt + 1}", 5)
25
+ wait_check("FAKESAT IMAGER COLLECTS == #{collect_cnt + 1}", 5)
26
+ wait_check("FAKESAT IMAGER COLLECT_TYPE == 'SPECIAL'", 5)
27
+ end
28
+ end
29
+
30
+ class ModeGroup < OpenC3::Group
31
+ def script_safe
32
+ FakeSat.safe
33
+ end
34
+ def script_checkout
35
+ FakeSat.checkout
36
+ end
37
+ def script_operate
38
+ FakeSat.operate
39
+ end
40
+ end
41
+
42
+ class OpsSuite < OpenC3::Suite
43
+ def initialize
44
+ super()
45
+ add_group('CollectGroup')
46
+ add_group('ModeGroup')
47
+ end
48
+ end