DCCL v5
Loading...
Searching...
No Matches
test.cpp
1// Copyright 2026:
2// GobySoft, LLC (2013-)
3// Community contributors (see AUTHORS file)
4// File authors:
5// Toby Schneider <toby@gobysoft.org>
6//
7//
8// This file is part of the Dynamic Compact Control Language Library
9// ("DCCL").
10//
11// DCCL is free software: you can redistribute it and/or modify
12// it under the terms of the GNU Lesser General Public License as published by
13// the Free Software Foundation, either version 2.1 of the License, or
14// (at your option) any later version.
15//
16// DCCL is distributed in the hope that it will be useful,
17// but WITHOUT ANY WARRANTY; without even the implied warranty of
18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19// GNU Lesser General Public License for more details.
20//
21// You should have received a copy of the GNU Lesser General Public License
22// along with DCCL. If not, see <http://www.gnu.org/licenses/>.
23
24// Tests the CCL (Compact Control Language) scalar conversions in WhoiUtil.
25//
26// CCL is an externally defined, effectively deprecated format that DCCL only
27// has to interoperate with. Its encodings are the specification, so this file
28// pins exact values rather than only round-tripping, and the oddities noted
29// below are recorded because they are the format, not because they are
30// waiting to be fixed. Changing any of them would break interoperability.
31
32#include <cassert>
33#include <cmath>
34#include <ctime>
35#include <iostream>
36
37#include "dccl/logger.h"
38
39#include "../../ccl/WhoiUtil.h"
40
41namespace
42{
43bool close(double a, double b, double tol)
44{
45 bool ok = std::abs(a - b) <= tol;
46 if (!ok)
47 std::cerr << "expected " << b << " (+/- " << tol << "), got " << a << std::endl;
48 return ok;
49}
50
51void check_latlon()
52{
53 // ~2 m resolution over the +/-180 degree range
54 const double tol = 3e-5;
55 for (double deg : {0.0, 1.0, -1.0, 42.35, -42.35, 179.9, -179.9, 89.999, -89.999})
56 assert(close(Decode_latlon(Encode_latlon(deg)), deg, tol));
57
58 // the sign extension on decode is the subtle part: negatives must not
59 // come back as large positives
60 assert(Decode_latlon(Encode_latlon(-120.0)) < 0);
61 assert(Decode_latlon(Encode_latlon(120.0)) > 0);
62 assert(close(Decode_latlon(Encode_latlon(0.0)), 0.0, 1e-9));
63}
64
65void check_heading()
66{
67 for (float deg : {0.0f, 45.0f, 90.0f, 180.0f, 270.0f, 359.0f})
68 assert(close(Decode_heading(Encode_heading(deg)), deg, 1.5));
69
70 assert(Encode_heading(0.0f) == 0);
71 assert(Encode_heading(360.0f) == 255);
72}
73
74void check_est_velocity()
75{
76 // 2.5 cm/sec resolution
77 for (float v : {0.0f, 0.5f, 1.0f, 2.5f, 5.0f})
78 assert(close(Decode_est_velocity(Encode_est_velocity(v)), v, 0.03));
79
80 // the +0.5 in the encoder only rounds positives, so negatives truncate
81 // toward zero and lose up to a full step; this asymmetry is part of the
82 // format
83 for (float v : {-1.0f, -2.5f, -5.0f})
84 assert(close(Decode_est_velocity(Encode_est_velocity(v)), v, 0.05));
85 assert(Encode_est_velocity(-1.0f) == -24);
86
87 assert(Encode_est_velocity(0.0f) == 0);
88}
89
90void check_salinity()
91{
92 // valid range is 20-45 ppt; anything below 20 encodes to 0
93 assert(Encode_salinity(19.9f) == 0);
94 assert(Encode_salinity(0.0f) == 0);
95 assert(Decode_salinity(0) == 0.0f);
96
97 for (float s : {20.1f, 25.0f, 33.5f, 45.0f})
98 assert(close(Decode_salinity(Encode_salinity(s)), s, 0.11));
99
100 // saturates rather than wrapping
101 assert(Encode_salinity(1000.0f) == 255);
102}
103
104void check_depth()
105{
106 assert(Encode_depth(-1.0f) == 0);
107 assert(Encode_depth(10000.0f) == 8100);
108
109 // each of the four resolution bands round trips within its own resolution
110 assert(close(Decode_depth(Encode_depth(0.0f)), 0.0, 0.05));
111 assert(close(Decode_depth(Encode_depth(50.0f)), 50.0, 0.05));
112 assert(close(Decode_depth(Encode_depth(99.0f)), 99.0, 0.05));
113 assert(close(Decode_depth(Encode_depth(150.0f)), 150.0, 0.1));
114 assert(close(Decode_depth(Encode_depth(500.0f)), 500.0, 0.25));
115 assert(close(Decode_depth(Encode_depth(3000.0f)), 3000.0, 0.5));
116
117 // decode masks to 13 bits, so anything above saturates at 6000
118 assert(close(Decode_depth(8100), 6000.0, 1e-3));
119}
120
121void check_temperature()
122{
123 // clamps at both ends of the -4 to +36 C range
124 assert(Encode_temperature(-100.0f) == 0);
125 assert(Encode_temperature(1000.0f) == 255);
126
127 for (float t : {-4.0f, 0.0f, 10.0f, 20.0f, 35.0f})
128 assert(close(Decode_temperature(Encode_temperature(t)), t, 0.09));
129}
130
131void check_sound_speed()
132{
133 for (float c : {1425.0f, 1450.0f, 1500.0f, 1550.0f})
134 assert(close(Decode_sound_speed(Encode_sound_speed(c)), c, 0.5));
135
136 assert(Encode_sound_speed(1425.0f) == 0);
137}
138
139void check_hires_altitude()
140{
141 assert(Encode_hires_altitude(-1.0f) == 0);
142 assert(Encode_hires_altitude(1000.0f) == 65535U); // saturates past ~655 m
143
144 for (float a : {0.0f, 1.5f, 100.0f, 655.0f})
145 assert(close(Decode_hires_altitude(Encode_hires_altitude(a)), a, 0.01));
146}
147
148void check_gfi_pitch_oil()
149{
150 float gfi = 0, pitch = 0, oil = 0;
151
152 Decode_gfi_pitch_oil(Encode_gfi_pitch_oil(50.0f, 30.0f, 75.0f), &gfi, &pitch, &oil);
153 assert(close(gfi, 50.0, 3.5));
154 assert(close(pitch, 30.0, 3.0));
155 assert(close(oil, 75.0, 3.5));
156
157 // negative pitch must survive the packed sign
158 Decode_gfi_pitch_oil(Encode_gfi_pitch_oil(0.0f, -45.0f, 0.0f), &gfi, &pitch, &oil);
159 assert(close(pitch, -45.0, 3.0));
160 assert(close(gfi, 0.0, 3.5));
161 assert(close(oil, 0.0, 3.5));
162
163 // gfi and oil clamp to their documented 0-100 range
164 Decode_gfi_pitch_oil(Encode_gfi_pitch_oil(1000.0f, 0.0f, 1000.0f), &gfi, &pitch, &oil);
165 assert(close(gfi, 100.0, 3.5));
166 assert(close(oil, 100.0, 3.5));
167
168 Decode_gfi_pitch_oil(Encode_gfi_pitch_oil(-100.0f, 0.0f, -100.0f), &gfi, &pitch, &oil);
169 assert(close(gfi, 0.0, 3.5));
170 assert(close(oil, 0.0, 3.5));
171
172 // pitch is good up to just inside the clamp endpoints
173 Decode_gfi_pitch_oil(Encode_gfi_pitch_oil(0.0f, 89.0f, 0.0f), &gfi, &pitch, &oil);
174 assert(close(pitch, 89.0, 3.0));
175 Decode_gfi_pitch_oil(Encode_gfi_pitch_oil(0.0f, -89.0f, 0.0f), &gfi, &pitch, &oil);
176 assert(close(pitch, -89.0, 3.0));
177
178 // The pitch field holds 6 signed bits (-32..31), but the clamp endpoints
179 // scale to +/-32, so both +90 and -90 encode to 0x8000 and come back as
180 // -91.4. Recorded as the format's behaviour, not as a bug to fix: any
181 // change here would put DCCL out of step with every other CCL implementation.
182 assert(Encode_gfi_pitch_oil(0.0f, 90.0f, 0.0f) == Encode_gfi_pitch_oil(0.0f, -90.0f, 0.0f));
183 Decode_gfi_pitch_oil(Encode_gfi_pitch_oil(0.0f, 90.0f, 0.0f), &gfi, &pitch, &oil);
184 assert(close(pitch, -91.4286, 1e-3));
185}
186
187void check_time_date()
188{
189 // 2021-03-04 05:06:08 UTC; seconds have 4-second resolution and the year
190 // is not encoded at all
191 struct tm when = {};
192 when.tm_year = 2021 - 1900;
193 when.tm_mon = 2;
194 when.tm_mday = 4;
195 when.tm_hour = 5;
196 when.tm_min = 6;
197 when.tm_sec = 8;
198 long secs = static_cast<long>(timegm(&when));
199
200 short mon = 0, day = 0, hour = 0, min = 0, sec = 0;
201 Decode_time_date(Encode_time_date(secs), &mon, &day, &hour, &min, &sec);
202
203 assert(mon == 3);
204 assert(day == 4);
205 assert(hour == 5);
206 assert(min == 6);
207 assert(sec == 8);
208
209 // the epoch itself, to exercise the all-zero-ish path
210 Decode_time_date(Encode_time_date(0), &mon, &day, &hour, &min, &sec);
211 assert(mon == 1);
212 assert(day == 1);
213 assert(hour == 0);
214 assert(min == 0);
215 assert(sec == 0);
216}
217
218void check_watts()
219{
220 assert(Encode_watts(0.0f, 0.0f) == 0);
221 assert(Encode_watts(-10.0f, 10.0f) == 0); // negative clamps to 0
222 assert(Encode_watts(1000.0f, 1000.0f) == 255); // saturates
223
224 assert(close(Decode_watts(Encode_watts(10.0f, 10.0f)), 100.0, 4.0));
225 assert(Decode_watts(255) == 1020.0f);
226}
227
228void check_speed()
229{
230 for (float rpm : {0.0f, 50.0f, 127.0f, -127.0f})
231 assert(close(Decode_speed(SPEED_MODE_RPM, Encode_speed(SPEED_MODE_RPM, rpm / 20.0f)), rpm,
232 20.0));
233
234 for (float msec : {0.0f, 1.0f, 2.5f, 4.2f, -2.0f})
235 assert(
236 close(Decode_speed(SPEED_MODE_MSEC, Encode_speed(SPEED_MODE_MSEC, msec)), msec, 0.04));
237
238 // both modes clamp to the signed byte range
239 assert(Encode_speed(SPEED_MODE_RPM, 1000.0f) == 127);
240 assert(Encode_speed(SPEED_MODE_RPM, -1000.0f) == -127);
241 assert(Encode_speed(SPEED_MODE_MSEC, 1000.0f) == 127);
242 assert(Encode_speed(SPEED_MODE_MSEC, -1000.0f) == -127);
243
244 // knots is accepted on encode but explicitly unsupported on decode
245 assert(Decode_speed(SPEED_MODE_KNOTS, 42) == 0);
246}
247
248void check_ranger_bcd()
249{
250 assert(close(DecodeRangerBCD(0x00), 0.0, 1e-9));
251 assert(close(DecodeRangerBCD(0x42), 42.0, 1e-9));
252 assert(close(DecodeRangerBCD(0x99), 99.0, 1e-9));
253
254 assert(close(DecodeRangerBCD2(0x12, 0x34), 1234.0, 1e-9));
255 assert(close(DecodeRangerBCD2(0x00, 0x07), 7.0, 1e-9));
256
257 // 41 deg 23.4567 min north
258 assert(close(DecodeRangerLL(0x04, 0x1e, 0x23, 0x45, 0x67), 41.0 + 23.4567 / 60.0, 1e-9));
259 // the 0x0c/0x0d sign nibbles mark a negative value
260 assert(close(DecodeRangerLL(0x04, 0x1c, 0x23, 0x45, 0x67), -(41.0 + 23.4567 / 60.0), 1e-9));
261 assert(close(DecodeRangerLL(0x04, 0x1d, 0x23, 0x45, 0x67), -(41.0 + 23.4567 / 60.0), 1e-9));
262 // three-digit degrees
263 assert(close(DecodeRangerLL(0x12, 0x3e, 0x00, 0x00, 0x00), 123.0, 1e-9));
264}
265} // namespace
266
267int main(int argc, char* argv[])
268{
269 bool verbose = false;
270 for (int i = 1; i < argc; ++i)
271 {
272 if (argv[i] && argv[i][0] == '-' && argv[i][1] == 'v' && argv[i][2] == '\0')
273 verbose = true;
274 }
275
276 dccl::dlog.connect(verbose ? dccl::logger::ALL : dccl::logger::WARN_PLUS, &std::cerr);
277
278 check_latlon();
279 check_heading();
280 check_est_velocity();
281 check_salinity();
282 check_depth();
283 check_temperature();
284 check_sound_speed();
285 check_hires_altitude();
286 check_gfi_pitch_oil();
287 check_time_date();
288 check_watts();
289 check_speed();
290 check_ranger_bcd();
291
292 std::cout << "all tests passed" << std::endl;
293 return 0;
294}
void connect(int verbosity_mask, Slot slot)
Connect the output of one or more given verbosities to a slot (function pointer or similar)
Definition logger.h:213
Dynamic Compact Control Language namespace.
Definition any.h:28