CCSDS Command Link Control Word (CLCW) for spacecraft command
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add CCSDS space protocol libraries: aos, clcw, uslp

New libraries for CCSDS (Consultative Committee for Space Data Systems)
protocols:

- ocaml-aos: AOS (Advanced Orbiting Systems) Transfer Frame (CCSDS 732.0-B-4)
- ocaml-clcw: CLCW (Communications Link Control Word) for COP-1
- ocaml-uslp: USLP (Unified Space Link Protocol) Transfer Frame

All use ocamlformat 0.28.1.

+586
+5
.gitignore
··· 1 + _build/ 2 + *.install 3 + .merlin 4 + *.opam.locked 5 + _opam/
+1
.ocamlformat
··· 1 + version = 0.28.1
+21
LICENSE.md
··· 1 + MIT License 2 + 3 + Copyright (c) 2025 Thomas Gazagnaire 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+55
README.md
··· 1 + # ocaml-clcw 2 + 3 + CCSDS Command Link Control Word (CLCW) parser and encoder. 4 + 5 + The CLCW is a 32-bit word defined in [CCSDS 232.1-B-2](https://public.ccsds.org/Pubs/232x1b2.pdf), 6 + carried in the Operational Control Field (OCF) of TM and AOS Transfer Frames. 7 + It provides feedback from the receiving spacecraft to the ground about the 8 + state of the command link. 9 + 10 + ## Installation 11 + 12 + ``` 13 + opam install clcw 14 + ``` 15 + 16 + ## Usage 17 + 18 + ```ocaml 19 + (* Decode a CLCW word *) 20 + let () = 21 + let word = 0x01280032 in 22 + match Clcw.decode word with 23 + | Error e -> Printf.printf "Error: %a\n" Clcw.pp_error e 24 + | Ok clcw -> Printf.printf "CLCW: %a\n" Clcw.pp clcw 25 + 26 + (* Encode a CLCW *) 27 + let () = 28 + let vcid = Clcw.vcid_exn 10 in 29 + let clcw = Clcw.v ~vcid ~report_value:50 ~lockout:true () in 30 + let word = Clcw.encode clcw in 31 + Printf.printf "Encoded: 0x%08X\n" word 32 + ``` 33 + 34 + ## CLCW Format 35 + 36 + ``` 37 + Bit 0: Control Word Type (0=CLCW) 38 + Bits 1-2: CLCW Version Number 39 + Bits 3-5: Status Field 40 + Bits 6-7: COP in Effect 41 + Bits 8-13: Virtual Channel ID 42 + Bits 14-15: Reserved (spare) 43 + Bit 16: No RF Available 44 + Bit 17: No Bit Lock 45 + Bit 18: Lockout 46 + Bit 19: Wait 47 + Bit 20: Retransmit 48 + Bits 21-22: FARM-B Counter 49 + Bit 23: Reserved (spare) 50 + Bits 24-31: Report Value N(R) 51 + ``` 52 + 53 + ## License 54 + 55 + MIT
+31
clcw.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + synopsis: "CCSDS Command Link Control Word (CCSDS 232.1-B-2)" 4 + description: 5 + "Parser and encoder for CCSDS Command Link Control Word (CLCW). The CLCW is a 32-bit word carried in the Operational Control Field (OCF) of TM and AOS Transfer Frames, providing feedback from spacecraft to ground about the state of the command link." 6 + maintainer: ["Thomas Gazagnaire <thomas@gazagnaire.org>"] 7 + authors: ["Thomas Gazagnaire <thomas@gazagnaire.org>"] 8 + license: "MIT" 9 + depends: [ 10 + "dune" {>= "3.0"} 11 + "ocaml" {>= "4.14"} 12 + "fmt" {>= "0.9"} 13 + "alcotest" {with-test} 14 + "crowbar" {with-test} 15 + "odoc" {with-doc} 16 + ] 17 + build: [ 18 + ["dune" "subst"] {dev} 19 + [ 20 + "dune" 21 + "build" 22 + "-p" 23 + name 24 + "-j" 25 + jobs 26 + "@install" 27 + "@runtest" {with-test} 28 + "@doc" {with-doc} 29 + ] 30 + ] 31 + dev-repo: "https://tangled.org/gazagnaire.org/ocaml-clcw"
+26
dune-project
··· 1 + (lang dune 3.0) 2 + 3 + (name clcw) 4 + 5 + (generate_opam_files true) 6 + 7 + (license MIT) 8 + (authors "Thomas Gazagnaire <thomas@gazagnaire.org>") 9 + (maintainers "Thomas Gazagnaire <thomas@gazagnaire.org>") 10 + 11 + (source 12 + (uri https://tangled.org/gazagnaire.org/ocaml-clcw)) 13 + 14 + (package 15 + (name clcw) 16 + (synopsis "CCSDS Command Link Control Word (CCSDS 232.1-B-2)") 17 + (description 18 + "Parser and encoder for CCSDS Command Link Control Word (CLCW). The CLCW \ 19 + is a 32-bit word carried in the Operational Control Field (OCF) of TM \ 20 + and AOS Transfer Frames, providing feedback from spacecraft to ground \ 21 + about the state of the command link.") 22 + (depends 23 + (ocaml (>= 4.14)) 24 + (fmt (>= 0.9)) 25 + (alcotest :with-test) 26 + (crowbar :with-test)))
+3
fuzz/dune
··· 1 + (test 2 + (name fuzz_clcw) 3 + (libraries clcw crowbar))
+32
fuzz/fuzz_clcw.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 + SPDX-License-Identifier: MIT 4 + ---------------------------------------------------------------------------*) 5 + 6 + open Crowbar 7 + 8 + let () = 9 + add_test ~name:"clcw roundtrip" [ int32 ] (fun word -> 10 + (* Treat as unsigned 32-bit value *) 11 + let word = Int32.to_int word land 0xFFFFFFFF in 12 + (* Mask out reserved bits (14-15 and 23) which are not preserved *) 13 + let reserved_mask = lnot ((3 lsl 16) lor (1 lsl 8)) in 14 + let word_normalized = word land reserved_mask in 15 + match Clcw.decode word with 16 + | Error _ -> () 17 + | Ok t -> 18 + let word' = Clcw.encode t in 19 + check_eq ~pp:Fmt.int word_normalized word') 20 + 21 + let () = 22 + add_test ~name:"clcw encode-decode" 23 + [ range 64; range 256 ] 24 + (fun vcid nr -> 25 + match Clcw.vcid vcid with 26 + | None -> () 27 + | Some vcid -> ( 28 + let t = Clcw.v ~vcid ~report_value:nr () in 29 + let word = Clcw.encode t in 30 + match Clcw.decode word with 31 + | Error e -> fail (Fmt.str "decode failed: %a" Clcw.pp_error e) 32 + | Ok t' -> check_eq ~pp:Clcw.pp ~eq:Clcw.equal t t'))
+229
lib/clcw.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 + SPDX-License-Identifier: MIT 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** CLCW - Command Link Control Word (CCSDS 232.1-B-2). 7 + 8 + The CLCW is a 32-bit word carried in the Operational Control Field (OCF) of 9 + TM and AOS Transfer Frames. It provides feedback from the receiving 10 + spacecraft to the ground about the state of the command link. 11 + 12 + {b CLCW Format (32 bits)} 13 + {v 14 + Bit 0: Control Word Type (0=CLCW) 15 + Bits 1-2: CLCW Version Number 16 + Bits 3-5: Status Field 17 + Bits 6-7: COP in Effect 18 + Bits 8-13: Virtual Channel ID 19 + Bits 14-15: Reserved (spare) 20 + Bit 16: No RF Available 21 + Bit 17: No Bit Lock 22 + Bit 18: Lockout 23 + Bit 19: Wait 24 + Bit 20: Retransmit 25 + Bits 21-22: FARM-B Counter 26 + Bit 23: Reserved (spare) 27 + Bits 24-31: Report Value N(R) 28 + v} *) 29 + 30 + (* {1 Virtual Channel ID} *) 31 + 32 + type vcid = int 33 + 34 + let vcid n = if n >= 0 && n <= 63 then Some n else None 35 + 36 + let vcid_exn n = 37 + if n >= 0 && n <= 63 then n 38 + else invalid_arg (Printf.sprintf "vcid: %d out of range 0-63" n) 39 + 40 + let vcid_to_int v = v 41 + let pp_vcid = Fmt.int 42 + let equal_vcid = Int.equal 43 + 44 + (* {1 Types} *) 45 + 46 + type status = Ready | Active | Reserved of int 47 + 48 + let pp_status ppf = function 49 + | Ready -> Fmt.string ppf "Ready" 50 + | Active -> Fmt.string ppf "Active" 51 + | Reserved n -> Fmt.pf ppf "Reserved(%d)" n 52 + 53 + let status_of_int = function 0 -> Ready | 1 -> Active | n -> Reserved n 54 + 55 + let int_of_status = function 56 + | Ready -> 0 57 + | Active -> 1 58 + | Reserved n -> n land 0x7 59 + 60 + let equal_status a b = 61 + match (a, b) with 62 + | Ready, Ready -> true 63 + | Active, Active -> true 64 + | Reserved x, Reserved y -> x = y 65 + | _ -> false 66 + 67 + type flags = { 68 + no_rf_available : bool; 69 + no_bit_lock : bool; 70 + lockout : bool; 71 + wait : bool; 72 + retransmit : bool; 73 + } 74 + 75 + let no_flags = 76 + { 77 + no_rf_available = false; 78 + no_bit_lock = false; 79 + lockout = false; 80 + wait = false; 81 + retransmit = false; 82 + } 83 + 84 + let pp_flags ppf f = 85 + let flags = [] in 86 + let flags = if f.no_rf_available then "no_rf" :: flags else flags in 87 + let flags = if f.no_bit_lock then "no_bit_lock" :: flags else flags in 88 + let flags = if f.lockout then "lockout" :: flags else flags in 89 + let flags = if f.wait then "wait" :: flags else flags in 90 + let flags = if f.retransmit then "retransmit" :: flags else flags in 91 + match flags with 92 + | [] -> Fmt.string ppf "none" 93 + | _ -> Fmt.list ~sep:(Fmt.any ",") Fmt.string ppf (List.rev flags) 94 + 95 + let equal_flags a b = 96 + a.no_rf_available = b.no_rf_available 97 + && a.no_bit_lock = b.no_bit_lock 98 + && a.lockout = b.lockout && a.wait = b.wait 99 + && a.retransmit = b.retransmit 100 + 101 + type t = { 102 + control_word_type : int; 103 + version : int; 104 + status : status; 105 + cop_in_effect : int; 106 + vcid : vcid; 107 + flags : flags; 108 + farm_b_counter : int; 109 + report_value : int; 110 + } 111 + 112 + let equal a b = 113 + a.control_word_type = b.control_word_type 114 + && a.version = b.version 115 + && equal_status a.status b.status 116 + && a.cop_in_effect = b.cop_in_effect 117 + && equal_vcid a.vcid b.vcid 118 + && equal_flags a.flags b.flags 119 + && a.farm_b_counter = b.farm_b_counter 120 + && a.report_value = b.report_value 121 + 122 + let pp ppf t = 123 + Fmt.pf ppf 124 + "@[<hv 2>{ cwt=%d;@ version=%d;@ status=%a;@ cop=%d;@ vcid=%a;@ flags=%a;@ \ 125 + farm_b=%d;@ N(R)=%d }@]" 126 + t.control_word_type t.version pp_status t.status t.cop_in_effect pp_vcid 127 + t.vcid pp_flags t.flags t.farm_b_counter t.report_value 128 + 129 + (* {1 Parsing} *) 130 + 131 + type error = Invalid_control_word_type of int | Invalid_vcid of int 132 + 133 + let pp_error ppf = function 134 + | Invalid_control_word_type n -> 135 + Fmt.pf ppf "invalid control word type: %d (expected 0)" n 136 + | Invalid_vcid n -> Fmt.pf ppf "invalid VCID: %d" n 137 + 138 + let decode word = 139 + (* Bit 0: Control Word Type *) 140 + let cwt = (word lsr 31) land 1 in 141 + (* Bits 1-2: Version *) 142 + let version = (word lsr 29) land 3 in 143 + (* Bits 3-5: Status *) 144 + let status_raw = (word lsr 26) land 7 in 145 + let status = status_of_int status_raw in 146 + (* Bits 6-7: COP in Effect *) 147 + let cop = (word lsr 24) land 3 in 148 + (* Bits 8-13: VCID *) 149 + let vcid_raw = (word lsr 18) land 63 in 150 + (* Bits 14-15: Reserved *) 151 + (* Bit 16: No RF Available *) 152 + let no_rf = (word lsr 15) land 1 = 1 in 153 + (* Bit 17: No Bit Lock *) 154 + let no_bit_lock = (word lsr 14) land 1 = 1 in 155 + (* Bit 18: Lockout *) 156 + let lockout = (word lsr 13) land 1 = 1 in 157 + (* Bit 19: Wait *) 158 + let wait = (word lsr 12) land 1 = 1 in 159 + (* Bit 20: Retransmit *) 160 + let retransmit = (word lsr 11) land 1 = 1 in 161 + (* Bits 21-22: FARM-B Counter *) 162 + let farm_b = (word lsr 9) land 3 in 163 + (* Bit 23: Reserved *) 164 + (* Bits 24-31: Report Value N(R) *) 165 + let nr = word land 0xFF in 166 + match vcid vcid_raw with 167 + | None -> Error (Invalid_vcid vcid_raw) 168 + | Some vcid -> 169 + Ok 170 + { 171 + control_word_type = cwt; 172 + version; 173 + status; 174 + cop_in_effect = cop; 175 + vcid; 176 + flags = 177 + { no_rf_available = no_rf; no_bit_lock; lockout; wait; retransmit }; 178 + farm_b_counter = farm_b; 179 + report_value = nr; 180 + } 181 + 182 + (* {1 Encoding} *) 183 + 184 + let encode t = 185 + let word = 0 in 186 + (* Bit 0: Control Word Type *) 187 + let word = word lor ((t.control_word_type land 1) lsl 31) in 188 + (* Bits 1-2: Version *) 189 + let word = word lor ((t.version land 3) lsl 29) in 190 + (* Bits 3-5: Status *) 191 + let word = word lor ((int_of_status t.status land 7) lsl 26) in 192 + (* Bits 6-7: COP in Effect *) 193 + let word = word lor ((t.cop_in_effect land 3) lsl 24) in 194 + (* Bits 8-13: VCID *) 195 + let word = word lor (vcid_to_int t.vcid lsl 18) in 196 + (* Bits 14-15: Reserved (0) *) 197 + (* Bit 16: No RF Available *) 198 + let word = word lor if t.flags.no_rf_available then 1 lsl 15 else 0 in 199 + (* Bit 17: No Bit Lock *) 200 + let word = word lor if t.flags.no_bit_lock then 1 lsl 14 else 0 in 201 + (* Bit 18: Lockout *) 202 + let word = word lor if t.flags.lockout then 1 lsl 13 else 0 in 203 + (* Bit 19: Wait *) 204 + let word = word lor if t.flags.wait then 1 lsl 12 else 0 in 205 + (* Bit 20: Retransmit *) 206 + let word = word lor if t.flags.retransmit then 1 lsl 11 else 0 in 207 + (* Bits 21-22: FARM-B Counter *) 208 + let word = word lor ((t.farm_b_counter land 3) lsl 9) in 209 + (* Bit 23: Reserved (0) *) 210 + (* Bits 24-31: Report Value N(R) *) 211 + let word = word lor (t.report_value land 0xFF) in 212 + word 213 + 214 + (* {1 Constructors} *) 215 + 216 + let v ?(control_word_type = 0) ?(version = 0) ?(status = Ready) 217 + ?(cop_in_effect = 0) ~vcid ?(no_rf_available = false) ?(no_bit_lock = false) 218 + ?(lockout = false) ?(wait = false) ?(retransmit = false) 219 + ?(farm_b_counter = 0) ~report_value () = 220 + { 221 + control_word_type; 222 + version; 223 + status; 224 + cop_in_effect; 225 + vcid; 226 + flags = { no_rf_available; no_bit_lock; lockout; wait; retransmit }; 227 + farm_b_counter; 228 + report_value; 229 + }
+98
lib/clcw.mli
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 + SPDX-License-Identifier: MIT 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** CLCW - Command Link Control Word (CCSDS 232.1-B-2). 7 + 8 + The CLCW is a 32-bit word carried in the Operational Control Field (OCF) of 9 + TM and AOS Transfer Frames. It provides feedback from the receiving 10 + spacecraft to the ground about the state of the command link. *) 11 + 12 + (** {1 Virtual Channel ID} *) 13 + 14 + type vcid = private int 15 + (** Virtual Channel Identifier (6 bits, 0-63). *) 16 + 17 + val vcid : int -> vcid option 18 + (** [vcid n] validates [n] is in range 0-63. *) 19 + 20 + val vcid_exn : int -> vcid 21 + (** [vcid_exn n] returns [n] as a vcid, raising [Invalid_argument] if out of 22 + range. *) 23 + 24 + val vcid_to_int : vcid -> int 25 + val pp_vcid : vcid Fmt.t 26 + val equal_vcid : vcid -> vcid -> bool 27 + 28 + (** {1 Types} *) 29 + 30 + type status = Ready | Active | Reserved of int (** CLCW status field. *) 31 + 32 + val pp_status : status Fmt.t 33 + val equal_status : status -> status -> bool 34 + val status_of_int : int -> status 35 + val int_of_status : status -> int 36 + 37 + type flags = { 38 + no_rf_available : bool; (** No RF Available flag. *) 39 + no_bit_lock : bool; (** No Bit Lock flag. *) 40 + lockout : bool; (** Lockout flag. *) 41 + wait : bool; (** Wait flag. *) 42 + retransmit : bool; (** Retransmit flag. *) 43 + } 44 + (** CLCW flags. *) 45 + 46 + val no_flags : flags 47 + (** All flags set to false. *) 48 + 49 + val pp_flags : flags Fmt.t 50 + val equal_flags : flags -> flags -> bool 51 + 52 + type t = { 53 + control_word_type : int; (** Control word type (should be 0 for CLCW). *) 54 + version : int; (** CLCW version number (2 bits). *) 55 + status : status; (** Status field. *) 56 + cop_in_effect : int; (** COP in effect (2 bits). *) 57 + vcid : vcid; (** Virtual channel ID. *) 58 + flags : flags; (** CLCW flags. *) 59 + farm_b_counter : int; (** FARM-B counter (2 bits). *) 60 + report_value : int; (** Report value N(R) (8 bits). *) 61 + } 62 + (** Command Link Control Word. *) 63 + 64 + val equal : t -> t -> bool 65 + val pp : t Fmt.t 66 + 67 + (** {1 Encoding/Decoding} *) 68 + 69 + type error = 70 + | Invalid_control_word_type of int 71 + | Invalid_vcid of int (** Decode errors. *) 72 + 73 + val pp_error : error Fmt.t 74 + 75 + val decode : int -> (t, error) result 76 + (** [decode word] decodes a 32-bit CLCW word. *) 77 + 78 + val encode : t -> int 79 + (** [encode t] encodes a CLCW to a 32-bit word. *) 80 + 81 + (** {1 Constructors} *) 82 + 83 + val v : 84 + ?control_word_type:int -> 85 + ?version:int -> 86 + ?status:status -> 87 + ?cop_in_effect:int -> 88 + vcid:vcid -> 89 + ?no_rf_available:bool -> 90 + ?no_bit_lock:bool -> 91 + ?lockout:bool -> 92 + ?wait:bool -> 93 + ?retransmit:bool -> 94 + ?farm_b_counter:int -> 95 + report_value:int -> 96 + unit -> 97 + t 98 + (** [v ~vcid ~report_value ()] constructs a CLCW with the given fields. *)
+4
lib/dune
··· 1 + (library 2 + (name clcw) 3 + (public_name clcw) 4 + (libraries fmt))
+3
test/dune
··· 1 + (test 2 + (name test_clcw) 3 + (libraries clcw alcotest))
+78
test/test_clcw.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 + SPDX-License-Identifier: MIT 4 + ---------------------------------------------------------------------------*) 5 + 6 + let clcw = Alcotest.testable Clcw.pp Clcw.equal 7 + 8 + let test_roundtrip () = 9 + let vcid = Clcw.vcid_exn 5 in 10 + let t = 11 + Clcw.v ~vcid ~report_value:42 ~lockout:true ~farm_b_counter:2 ~version:1 () 12 + in 13 + let word = Clcw.encode t in 14 + match Clcw.decode word with 15 + | Error e -> Alcotest.failf "decode failed: %a" Clcw.pp_error e 16 + | Ok t' -> Alcotest.(check clcw) "roundtrip" t t' 17 + 18 + let test_decode_known () = 19 + (* Example CLCW with known values: 20 + - CWT=0, Version=0, Status=Active(1), COP=1, VCID=10 21 + - No flags set, FARM-B=0, N(R)=50 *) 22 + let word = 23 + (0 lsl 31) (* CWT = 0 *) 24 + lor (0 lsl 29) (* Version = 0 *) 25 + lor (1 lsl 26) (* Status = Active *) 26 + lor (1 lsl 24) (* COP = 1 *) 27 + lor (10 lsl 18) (* VCID = 10 *) 28 + lor (0 lsl 15) (* No RF = false *) 29 + lor (0 lsl 14) (* No bit lock = false *) 30 + lor (0 lsl 13) (* Lockout = false *) 31 + lor (0 lsl 12) (* Wait = false *) 32 + lor (0 lsl 11) (* Retransmit = false *) 33 + lor (0 lsl 9) (* FARM-B = 0 *) lor 50 (* N(R) = 50 *) 34 + in 35 + match Clcw.decode word with 36 + | Error e -> Alcotest.failf "decode failed: %a" Clcw.pp_error e 37 + | Ok t -> 38 + Alcotest.(check int) "version" 0 t.version; 39 + Alcotest.(check bool) 40 + "status active" true 41 + (Clcw.equal_status t.status Clcw.Active); 42 + Alcotest.(check int) "cop" 1 t.cop_in_effect; 43 + Alcotest.(check int) "vcid" 10 (Clcw.vcid_to_int t.vcid); 44 + Alcotest.(check int) "report_value" 50 t.report_value 45 + 46 + let test_flags () = 47 + let vcid = Clcw.vcid_exn 0 in 48 + let t = 49 + Clcw.v ~vcid ~report_value:0 ~no_rf_available:true ~no_bit_lock:true 50 + ~lockout:true ~wait:true ~retransmit:true () 51 + in 52 + let word = Clcw.encode t in 53 + match Clcw.decode word with 54 + | Error e -> Alcotest.failf "decode failed: %a" Clcw.pp_error e 55 + | Ok t' -> 56 + Alcotest.(check bool) "no_rf" true t'.flags.no_rf_available; 57 + Alcotest.(check bool) "no_bit_lock" true t'.flags.no_bit_lock; 58 + Alcotest.(check bool) "lockout" true t'.flags.lockout; 59 + Alcotest.(check bool) "wait" true t'.flags.wait; 60 + Alcotest.(check bool) "retransmit" true t'.flags.retransmit 61 + 62 + let test_vcid_bounds () = 63 + Alcotest.(check bool) "vcid 0 valid" true (Option.is_some (Clcw.vcid 0)); 64 + Alcotest.(check bool) "vcid 63 valid" true (Option.is_some (Clcw.vcid 63)); 65 + Alcotest.(check bool) "vcid -1 invalid" true (Option.is_none (Clcw.vcid (-1))); 66 + Alcotest.(check bool) "vcid 64 invalid" true (Option.is_none (Clcw.vcid 64)) 67 + 68 + let () = 69 + Alcotest.run "clcw" 70 + [ 71 + ( "clcw", 72 + [ 73 + Alcotest.test_case "roundtrip" `Quick test_roundtrip; 74 + Alcotest.test_case "decode_known" `Quick test_decode_known; 75 + Alcotest.test_case "flags" `Quick test_flags; 76 + Alcotest.test_case "vcid_bounds" `Quick test_vcid_bounds; 77 + ] ); 78 + ]