Initial public release of WII5 Buoy firmware

Firmware for an autonomous wave-measurement buoy (ATmega2560-based
WII5 v2 board). Reads wave motion from a Sparton AHRS-M1/M2 IMU,
samples GPS and battery state, and reports back over Iridium SBD
satellite telemetry. Originally developed 2012-2024.

This is the first public release. Code, documentation, and field-tested
operating modes (Capture, Sleep, Position, ManualTest, SelfTest,
LowBattery) are licensed under Apache 2.0 — see LICENSE and NOTICE.

See README.md for an overview and build instructions, CONTRIBUTING.md
for how to contribute, and DEPLOYMENTS.md for the field-deployment log.
This commit is contained in:
2026-05-07 16:00:21 +10:00
commit 295abb37ee
122 changed files with 38142 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
# External References
Vendor documentation that the WII5 buoy firmware was developed against.
These documents are not redistributed in this repository (their licensing is
unclear); refer to the vendor sites for current copies.
## Iridium 9602 / 9603 SBD modems
Used for satellite telemetry. Key reference is the **Iridium 9602 / 9603
SBD Transceiver Developer's Guide** (AT-command set, SBD message lifecycle,
binary message framing for `+SBDWB` / `+SBDI`).
- Iridium developer portal: https://www.iridium.com/services/iridium-sbd/
- Search the Iridium partner network for the current "9602/9603 Developer
Guide" PDF.
Relevant code: `WII5Iridium.cpp`, `WII5SerialManager.cpp` (`+SBDWB`,
`+SBDI`, `+CSQ` parsing), `WII5BinData.*` (340-byte message format).
## Sparton AHRS-M1 / AHRS-M2
Used as the primary IMU / wave-motion sensor. Key references:
- AHRS-M1 / AHRS-M2 Software Interface Control Document
- AHRS-M1 / AHRS-M2 Hardware ICD
- NorthTek Programming Manual (Sparton's Forth-like configuration language;
the `programLine()` helpers in `WII5SerialManager.cpp` emit NorthTek
commands).
- Sparton (now Bel Power Solutions / part of Bel Fuse) documentation:
https://www.belfuse.com/
Relevant code: `WII5Sparton.cpp`, `WII5Setup.cpp` (compass/AHRS init).
## Other components
- **Dallas DS18B20** — temperature sensor:
https://www.analog.com/en/products/ds18b20.html
(Used in `WII5Weather_18B20.cpp` via the OneWire / DallasTemperature
Arduino libraries.)
- **TinyGPS++** — NMEA parser library:
https://github.com/mikalhart/TinyGPSPlus
- **u-blox NEO-6M / NEO-7M** — GPS module datasheets (NMEA reference):
https://www.u-blox.com/
+218
View File
@@ -0,0 +1,218 @@
Final Modes for WII5 Tasmania Release
* Incidental ones - used to transition the modes in the state machine
* BOOT
* START
* Demo / Testing modes - these must not be used in prod, but will have to have just timeouts
(these modes do not have Iridium and are unreliable, they must exit)
* DEMO
* MANUALTEST
* SELFTEST
* Fully functional modes - each of these has full control, metadata, and Iridium
* SLEEP - as deep as possible, basically off. But will come on every 12 hours
* LOWBATTERY - Automatically selected, no matter what the mode
* POSITION - Send the position over and over
* CAPTURE - Full controlled and capture mode
Original Notes:
Top Level Modes
Most of the buoy uses a lot of memory. But can be rebooted to other states.
And most of the memory is using MALLOC
Boot up stages
WII5MODE_BOOT - Boot up only once
. Loading configuration from EEPROM etc only needs to be done once
. Long processes, like reading the blocks from the SD Cards
SET - NA
AUTO - Boot up
WII5MODE_START - Probably jump here each time waking from sleep etc
. GPS, RTC, and other updates that need to be regularly checked can be done here
SET - NA
AUTO - Start ?
WII5MODE_OFF, - OFF - Disable everything and go into deep sleep, don't wake, back to sleep
. Really just a mode that allows us to stay here - (e.g. switch is off)
SET - NA
AUTO - Switch position
WII5MODE_SLEEP, - Same as OFF, but will wake with button, alarm, and every 12 hours
. Sleep - wake can be from button, network, time is up, alarm hit (RTC), every 12 hours and more
. Allowing more complicated decisions, e.g. long press to move to MANUAL TEST mode or Force Capture test
-> REPORT - If 12 hoursly updates
-> TemporaryMode - If still not expired
-> mode would be - POSITION or CAPTURE or SLEEP
-> DefaultMode - Otherwise
-> mode would be - POSITION or CAPTURE or SLEEP
SET - expires
val1 = ReportPeriod - How many minutes between reports. Default is 720 (12 hours)
NOTE: No way to go back to temporary mode right now. Consider that?
AUTO - NA
WII5MODE_LOWBATTERY, - Automatically set mode based on Battery Management
. A command mode change can force to MANUALTEST, or others but only for a maximum time period
SET - NA
AUTO - From Battery Module - not saved, recalculated all the time
WII5MODE_POSITION, - Position mode - light weight, no Maths, regular updates
SET - expires
val1 = ReportPeriod - How many minutes between sending position (default 15 minutes)
val2 = StrobeModes - e.g. Turn on strobe never, 5 minutes per hour, 1 minute at every send, etc
NOTE: No way to go back to temporary mode right now. Consider that?
AUTO - NA
WII5MODE_REPORT, - Report - this is like position, but a single report, and back to sleep
MINI Self Test or Requested Self Test
SET - NA
AUTO - Done from sleep, returns back to SLEEP - which will test etc
WII5MODE_CAPTURE, - Normal full capture mode.
SET - expires
val1 - RunPeriod - Minutes between - 15 (every 15 mins), 60 (once an hour), 120 (every 2 hours)
val2 - Records - How many records do we want (needs to be what is needed by maths)
val3 - Predefined sets of data to send back for Iridium - this is going to be a
bit mask. So we can have 32 sets of data we can add. Some might be contray to
each other. e.g. 1 might be low resolution temperature, but 2 high resolution,
so no point adding both.
New simpler Binary module - since now we only need to know this 32 bit number,
to know what is in the rest of the packet - and new types of data get added to the
end..... maybe.
AUTO - NA
WII5MODE_MANUALTEST - Manual test - each power button, module etc, can be toggled.
. Leaving this mode - we should consider a software reboot as the modes and hardware will be in an unknown state
SET - (restrict to set from Serial and not Iridium)
AUTO - NA
WII5MODE_SELFTEST - Self Test mode allows running through all the self tests automatically and rebooting
SET - (restrict to set from Serial and not Iridium)
val1 - ?
AUTO - NA
Changing Modes:
* Code:
- wii5Controler.setMode(WII5MODE_MANUALTEST); // Forever - write to config
- wii5Controler.setTemporaryMode(WII5MODE_MANUALTEST); // Until reboot (ie, not to config)
- wii5Controler.setTemporaryMode(WII5MODE_MANUALTEST, 300); // 5 minutes
* Iridium
- mode:ModeName:Seconds(0 forever):option1,option2,option3
(NOTE: Could we support all serial commands here?)
(NOTE: Could we make it a specific one line reply is always made)
@WII5!,mode,... e.g. it is WII5! for ACK and then the first string, e.g. mode, rest is optional
@WII5,hello,From,Here would be therefore @WII5!,hello,My,Reply
* Serial
- @WII5,mode,ModeName,Seconds,option1,option2,option3
* Modules Logic
- WII5Battery - force to LOWBATTERY mode (never saved), switch back to Capture when it can (or what ever default)
Boot Mode: (how do we know where to start)
(first one to match)
* Check the OFF Switch - if it is active, WII5MODE_OFF
. We don't actually need to do this one, if OFF switch active in loop, it will set it no matter what the mode.
* Check Battery
. Special option in config to ignore the battery for a period of time.
. If battery too low, switch to low power mode (and not ignored)
* Sleeping
. Read the sleeping configuration from memory or EEPROM
. Check the date / time for it has not yet expired (check time is synced)
. Back to sleep
* TemporaryMode
. Read Temporary mode - from memory or EEPROM
. Check the date / time for it has not yet expired (check time is synced)
. Use this mode
* DefaultMode
. Back to default mode
Configuration Options Per mode:
* WII5MODE_BOOT
. NA
* WII5MODE_START
. NA
* WII5MODE_OFF
. NA
* WII5MODE_SLEEP
. When to wake up (time in future, or number of seconds from now)
* WII5MODE_WAKE
. NA
* WII5MODE_REPORT
. NA
* WII5MODE_LOWBATTERY
. NA
* WII5MODE_POSITION
. Frequency (how many minutes apart)
. Strobe Rules - instead of just on/off - strobe can have rules in here
* WII5MODE_CAPTURE
. Number of seconds/minutes/records to caputre
. Process flags - Pass in some optional extra flags on processing and what you want returned via Iridium
. When to start (minute in hour or some way of multiple per hour, half hour, quarter hour)
. Hours between too
. Examples:
. Start 5 minutes past the hour, ever 3 hours (ie. 00, 03, 06, 09 ...)
. Run every 15 minutes for one hour, then sleep for an hour (00:00, 00:15, 00:30, 00:45, sleep... 02:00, 02:15, ...)
. etc.
* WII5MODE_MANUALTEST
. NA
* WII5MODE_SELFTEST
. NA - might have some subsets in the future
Main Modes:
* OFF
* A rare mode where the device is completely off.
* Exit = ? Physical butto?
* Time?
* Sleep
* Deep down sleep, don't take up much power
* WakeUp
* Check why we got woken up?
* User input button
* Back to old mode after sleep (period hit)
* Iridium input
* Period wake up - send status, che
* Exit - always does end of this, back to sleep
* LowPower
* Special mode, keep everything really short
* Send Data
* Sleep
* Position
* Capture
* Capture Start will define what is checked, e.g. It might not allow iridium
* In that case, Iridium NMEA commands should return correct error to wait
* NOTE: This is not capture itself
* ManualTest
* Stay in this mode (max tme, maybe 4 hours) to test
* Allow manual start/stop of processes
Exclusive vs Together:
* Classic example...
* Start capture
* In the background the Maths processor is still dealing with the old data.
* And it sends Iridium commands
* And capture needs metadata such as GPS.
* So Sparton serial in, SD writes, Iridium sending data, GPS on. All not kind of related.
Sequence:
Boot
Load up configuration from EEPROM, start devices etc
SelfTest
A built in self test that takes under 1 minutes. It will just set a Simple
error we can use to update the LED
Run
Sleep
Run Modes
+448
View File
@@ -0,0 +1,448 @@
New reordered array
process1.part1float
0..3 hz_max
4..7 hcm_max
8..11 htm_max
12..15 tz_mean
16 tp
17 hmod
18..72 psd
process2.parse2float
0..7 moment
8..16 ...
// 4
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->hz_max[i]);
// 4
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->hcm_max[i]);
// 4
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->htm_max[i]);
// 4 16
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->tz_mean[i]);
// 1
fprintf(ido_o,"%LF\n",return_data->tp);
// 1 18
fprintf(ido_o,"%LF\n",return_data->hmod);
// n_psd = 55 73
for (i=0;i<n_psd;i++) fprintf(ido_o,"%LF\n",return_data->psd[i]);
// n_mom = 7
for (i=0;i<n_mom;i++) fprintf(ido_o,"%LF\n",return_data->moment[i]);
// 8
fprintf(ido_o,"%d\n",return_data->qflg_open_water);
fprintf(ido_o,"%d\n",return_data->qflg_kist);
fprintf(ido_o,"%d\n",return_data->qflg_imu);
fprintf(ido_o,"%d\n",return_data->qflg_pkist);
fprintf(ido_o,"%d\n",return_data->qflg_accel);
fprintf(ido_o,"%d\n",return_data->qflg_gyro);
fprintf(ido_o,"%d\n",return_data->qflg_magno);
fprintf(ido_o,"%d\n",return_data->qflg_head);
// 54 62
155 lines
On average these are all floats - 4 Bytes
620 bytes - which does not fit into one block
OR:
101 * 4
54 * 2
= 512 - exactly the block size!
psd 55 = 220
Normal output from WII3/4
* All 4 byte float unless specified here
* direction - 54 * Int (2 byte) - total 108
* psd - 55 * float (4 byte) - total 220
hz_max: [1, 4], // Maximum wave height (trough - crest) in a 10 min segment
Double
4 * 4
= 16
hcm_max: [5, 8], // Maximum height of a crest above mean water level in a 10 min segment
Double
4 * 4
= 16
htm_max: [9, 12], // Maximum depth of a trough below mean water level in a 10 min segment
Double
4 * 4
= 16
tz_max: [13, 16], // Mean wave period in a 10 min segment
Double
4 * 4
= 16
tp: 17, // Peak period from the full (non averaged) spectra
Double
4
= 4
hmo: 18, // 4*standard deviation of elevation
Double
4
= 4
psd: [19, 73], // Using 1/f_avg = frequency
Double
55 * 4
= 220
moments: [74, 80], // Spectral moments
Double
7 * 4
= 28
theta: 81, // Mean wave direction
Double
4
= 4
dp: 82, // Peak wave direction
Double
4
= 4
s: 83, // Wave spread
Double
4
= 4
r: 84, // Ratio term to evaluate quality of wave direction approximation
Double
4
= 4
hs_dir:85, // Hs calculated using direction analysis
Double
4
= 4
a: 86, // Noise slope
Double
4
= 4
b: 87, // Noise intercept
Double
4
= 4
nstd: 88, // Noise standard deviation
Double
4
= 4
f2: 89, // Frequency cut-off
Double
4
= 4
qf_mvar: 90, // Quality flag: Mean vertical acceleration removed
Double
4
= 4
qf_kist: 91, // Quality flat: Kistler passed (0), Kistler failed (1)
Int
2
= 2
qf_imu: 92, // Quality flat: IMU passed (0), IMU failed (1)
Int
2
= 2
qf_p_kist: 93, // Quality flag: Percentage of flagged Kistler samples
Int
2
= 2
qf_p_accel: 94, // Quality flag: Percentage of flagged acceleration samples (averaged across 3 axis)
Int
2
= 2
qf_p_gyro: 95, // Quality flag: Percentage of flagged gyro samples (averaged across 3 axis)
Int
2
= 2
qf_p_mag: 96, // Quality flag: Percentage of flagged magno samples (averaged across 3 axis)
Int
2
= 2
qf_head: 97, // Quality flag: GPS heading passed (0), GPS heading failed (1)
Int
2
= 2
power_diff: 98, // Difference between power in the time domain and frequency domain
Double
4
= 4
yaw_std: 99, // Yaw standard deviation
Double
4
= 4
open_water: 100, // Open Water flag
Int
2
= 2
direction: [101, 154], // direction data
Int
54 * 2
= 108
492 bytes
11.787015
9.582090
12.528253
14.153369
7.735189
5.657926
4.293090
5.485378
-7.106659
-5.864575
-8.348605
-8.667991
29.714286
28.750000
28.055556
30.400000
32.000000
10.224887
11.823206
46.393283
50.172329
45.776413
96.298192
69.859577
37.341004
29.351810
17.013856
24.406505
10.531730
6.074609
4.671587
3.752939
6.758200
5.808995
5.183176
4.280694
1.921353
2.080787
1.444816
1.249582
1.329185
0.709623
1.501197
1.624132
0.683771
1.638818
0.252953
0.567005
0.321616
0.659857
0.243726
0.682648
0.268455
0.297780
0.281510
0.218103
0.302124
0.219463
0.273396
0.091317
0.122532
0.145382
0.038717
0.057687
0.044266
0.035344
0.020552
0.013517
0.007770
0.006380
0.004311
0.002205
0.001864
5740.408809
186.498311
6.548624
0.253406
0.011854
0.000831
0.000111
55.514691
14.862172
75.469394
32.156745
3.584171
-4.368659
-10.127936
410.341584
0.031250
10.716096
1
0
99
1
99
0
2
4.583661
11165.160686
1
5.154564
214
149
245
815
687
659
204
455
253
786
371
692
550
430
490
358
201
440
431
388
644
676
389
521
855
550
728
583
863
635
818
863
795
567
674
482
884
429
787
299
619
787
328
742
572
712
798
130
650
780
336
680
547
434
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->hz_max[i]);
4 Float
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->hcm_max[i]);
4 Float
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->htm_max[i]);
4 Float
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->tz_mean[i]);
4 Float
fprintf(ido_o,"%LF\n",return_data->tp);
1 Float
fprintf(ido_o,"%LF\n",return_data->hmod);
1 Float
for (i=0;i<n_psd;i++) fprintf(ido_o,"%LF\n",return_data->psd[i]);
55 Float
for (i=0;i<n_mom;i++) fprintf(ido_o,"%LF\n",return_data->moment[i]);
7 Float
fprintf(ido_o,"%LF\n",return_data->direction*180.0/pi);
1 Float
fprintf(ido_o,"%LF\n",return_data->peak_direction*180.0/pi);
1 Float
fprintf(ido_o,"%LF\n",return_data->spread*180.0/pi);
1 Float
fprintf(ido_o,"%LF\n",return_data->ratio);
1 Float
fprintf(ido_o,"%LF\n",return_data->hs_dir);
1 Float
fprintf(ido_o,"%LF\n",return_data->a);
1 Float
fprintf(ido_o,"%LF\n",return_data->b);
1 Float
fprintf(ido_o,"%LF\n",return_data->nstd);
1 Float
fprintf(ido_o,"%LF\n",return_data->f2);
1 Float
fprintf(ido_o,"%LF\n",return_data->qflg_mean_removed_imu);
1 Float
fprintf(ido_o,"%d\n",return_data->qflg_kist);
1 Int
fprintf(ido_o,"%d\n",return_data->qflg_imu);
1 Int
fprintf(ido_o,"%d\n",return_data->qflg_pkist);
1 Int
fprintf(ido_o,"%d\n",return_data->qflg_accel);
1 Int
fprintf(ido_o,"%d\n",return_data->qflg_gyro);
1 Int
fprintf(ido_o,"%d\n",return_data->qflg_magno);
1 Int
fprintf(ido_o,"%d\n",return_data->qflg_head);
1 Int
fprintf(ido_o,"%LF\n",return_data->qflg_tot_pow_imu);
1 Float
fprintf(ido_o,"%LF\n",return_data->std_yaw*180.0/pi);
1 Float
fprintf(ido_o,"%d\n",return_data->qflg_open_water);
1 Int
fprintf(ido_o,"%LF\n",return_data->hz_mean[0]);
1 Float
direction
54 Int
4 Float
4 Float
4 Float
4 Float
1 Float
1 Float
55 Float
73
= 292 Bytes
7 Float
1 Float
1 Float
1 Float
1 Float
1 Float
1 Float
1 Float
1 Float
1 Float
1 Float
17
= 68 bytes
1 Int
1 Int
1 Int
1 Int
1 Int
1 Int
1 Int
7
= 14 bytes
1 Float
1 Float
= 8 bytes
1 Int
= 2 bytes
1 Float
= 4 bytres
54 Int
= 108
= 204 total
+39
View File
@@ -0,0 +1,39 @@
Sparton Programming
Standard:
'chan0TriggerDivisor 0 set drop',
'accelrange 2 set drop',
'chan0Enables array[ 0 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]array set drop',
'chan0Format 3 set drop',
'chan0EnableBit cputime dvid@ set drop',
'chan0EnableBit magp dvid@ set drop',
'chan0EnableBit accelp dvid@ set drop',
'chan0EnableBit roll dvid@ set drop',
'chan0EnableBit pitch dvid@ set drop',
'chan0EnableBit yaw dvid@ set drop',
'chan0EnableBit gyrop dvid@ set drop',
'chan0Trigger 6 set drop',
'chanTimerX100us 156 set drop',
// Trigger on delay?
'chan0TriggerDivisor 1 set drop',
Change from 64Hz to 8Hz
Problem with X and Y Data
centripetalCompensation 0 set drop
magAiding 1 set drop
Log some sample Data
name di.
serialnumber di.
VERSION di.
VERSION_M4 di.
gyroSampleRate di.
save_mask d.on
db.print