Skip to content

Commit 1ae66f7

Browse files
committed
add SeaTalk datagram decoder
1 parent c727ef2 commit 1ae66f7

1 file changed

Lines changed: 157 additions & 0 deletions

File tree

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#pragma once
2+
3+
#include <stdint.h>
4+
5+
#include "seatalk_frame.hpp"
6+
7+
namespace seatalk {
8+
9+
enum class SeaTalkDecodedKind : uint8_t {
10+
none,
11+
depth,
12+
apparent_wind_angle,
13+
apparent_wind_speed,
14+
speed_through_water,
15+
water_temperature,
16+
heading_magnetic,
17+
rudder_angle,
18+
trip_distance,
19+
total_distance
20+
};
21+
22+
template<typename Real = float>
23+
struct SeaTalkDecoded {
24+
SeaTalkDecodedKind kind = SeaTalkDecodedKind::none;
25+
Real value = Real{};
26+
Real secondary_value = Real{};
27+
bool secondary_valid = false;
28+
bool alarm = false;
29+
uint8_t command = 0;
30+
};
31+
32+
inline float seatalk_kn_to_ms(float kn) {
33+
return kn * 0.514444f;
34+
}
35+
36+
inline float seatalk_wrap_360(float deg) {
37+
while (deg < 0.0f) deg += 360.0f;
38+
while (deg >= 360.0f) deg -= 360.0f;
39+
return deg;
40+
}
41+
42+
template<typename Real = float>
43+
bool decode_seatalk_frame(const SeaTalkFrame& frame, SeaTalkDecoded<Real>& out) {
44+
out = SeaTalkDecoded<Real>{};
45+
out.command = frame.command();
46+
const uint8_t* dg = frame.bytes;
47+
48+
switch (frame.command()) {
49+
case 0x00: { // Depth: value is feet * 10 in bytes 3..4, little endian by byte order used in references.
50+
if (frame.length < 5) return false;
51+
const uint16_t raw_feet_x10 = static_cast<uint16_t>(dg[4]) << 8 | dg[3];
52+
const float depth_m = (static_cast<float>(raw_feet_x10) / 10.0f) * 0.3048f;
53+
out.kind = SeaTalkDecodedKind::depth;
54+
out.value = static_cast<Real>(depth_m);
55+
out.alarm = (dg[2] & 0x80) != 0;
56+
return true;
57+
}
58+
case 0x10: { // Apparent wind angle: half degrees.
59+
if (frame.length < 4) return false;
60+
const uint16_t raw_half_deg = static_cast<uint16_t>(dg[2]) << 8 | dg[3];
61+
out.kind = SeaTalkDecodedKind::apparent_wind_angle;
62+
out.value = static_cast<Real>(seatalk_wrap_360(static_cast<float>(raw_half_deg) / 2.0f));
63+
return true;
64+
}
65+
case 0x11: { // Apparent wind speed in knots.
66+
if (frame.length < 4) return false;
67+
const float speed_kn = static_cast<float>(dg[2] & 0x7f) + static_cast<float>(dg[3]) / 10.0f;
68+
out.kind = SeaTalkDecodedKind::apparent_wind_speed;
69+
out.value = static_cast<Real>(speed_kn);
70+
out.secondary_value = static_cast<Real>(seatalk_kn_to_ms(speed_kn));
71+
out.secondary_valid = true;
72+
return true;
73+
}
74+
case 0x20: { // Speed through water, knots * 10.
75+
if (frame.length < 4) return false;
76+
const uint16_t raw_kn_x10 = static_cast<uint16_t>(dg[3]) << 8 | dg[2];
77+
const float speed_kn = static_cast<float>(raw_kn_x10) / 10.0f;
78+
out.kind = SeaTalkDecodedKind::speed_through_water;
79+
out.value = static_cast<Real>(speed_kn);
80+
out.secondary_value = static_cast<Real>(seatalk_kn_to_ms(speed_kn));
81+
out.secondary_valid = true;
82+
return true;
83+
}
84+
case 0x23: { // Water temperature, direct Celsius in byte 2 in common hardware observations.
85+
if (frame.length < 4) return false;
86+
out.kind = SeaTalkDecodedKind::water_temperature;
87+
out.value = static_cast<Real>(static_cast<int8_t>(dg[2]));
88+
out.alarm = (dg[1] & 0x04) != 0;
89+
return true;
90+
}
91+
case 0x26: { // Log speed: knots * 100, plus optional average/second speed.
92+
if (frame.length < 7) return false;
93+
const uint16_t raw_kn_x100 = static_cast<uint16_t>(dg[3]) << 8 | dg[2];
94+
const uint16_t raw_avg_x100 = static_cast<uint16_t>(dg[5]) << 8 | dg[4];
95+
const float speed_kn = static_cast<float>(raw_kn_x100) / 100.0f;
96+
out.kind = SeaTalkDecodedKind::speed_through_water;
97+
out.value = static_cast<Real>(speed_kn);
98+
out.secondary_value = static_cast<Real>(static_cast<float>(raw_avg_x100) / 100.0f);
99+
out.secondary_valid = true;
100+
return true;
101+
}
102+
case 0x27: { // Water temperature, (C + 100) * 10 little-endian.
103+
if (frame.length < 4) return false;
104+
const uint16_t raw_c = static_cast<uint16_t>(dg[3]) << 8 | dg[2];
105+
out.kind = SeaTalkDecodedKind::water_temperature;
106+
out.value = static_cast<Real>((static_cast<float>(raw_c) - 100.0f) / 10.0f);
107+
return true;
108+
}
109+
case 0x89: { // Compass heading magnetic. Attribute high nibble carries U.
110+
if (frame.length < 3) return false;
111+
const uint8_t u = dg[1] >> 4;
112+
const uint8_t vw = dg[2];
113+
float heading = static_cast<float>(u & 0x03) * 90.0f;
114+
heading += static_cast<float>(vw & 0x3f) * 2.0f;
115+
heading += static_cast<float>((u >> 2) & 0x03) * 0.5f;
116+
out.kind = SeaTalkDecodedKind::heading_magnetic;
117+
out.value = static_cast<Real>(seatalk_wrap_360(heading));
118+
return true;
119+
}
120+
case 0x9c: { // Rudder datagram: heading plus signed rudder angle.
121+
if (frame.length < 4) return false;
122+
const uint8_t u = dg[1] >> 4;
123+
const uint8_t vw = dg[2];
124+
const int8_t rudder = static_cast<int8_t>(dg[3]);
125+
float heading = static_cast<float>(u & 0x03) * 90.0f;
126+
heading += static_cast<float>(vw & 0x3f) * 2.0f;
127+
heading += static_cast<float>((u >> 2) & 0x03) * 0.5f;
128+
out.kind = SeaTalkDecodedKind::rudder_angle;
129+
out.value = static_cast<Real>(rudder);
130+
out.secondary_value = static_cast<Real>(seatalk_wrap_360(heading));
131+
out.secondary_valid = true;
132+
return true;
133+
}
134+
case 0x21: { // Trip distance, nautical miles * 100.
135+
if (frame.length < 5) return false;
136+
uint32_t raw = static_cast<uint32_t>(dg[4] & 0x0f) << 16;
137+
raw |= static_cast<uint32_t>(dg[3]) << 8;
138+
raw |= dg[2];
139+
out.kind = SeaTalkDecodedKind::trip_distance;
140+
out.value = static_cast<Real>(static_cast<float>(raw) / 100.0f);
141+
return true;
142+
}
143+
case 0x22: { // Total log, nautical miles * 10.
144+
if (frame.length < 5) return false;
145+
uint32_t raw = static_cast<uint32_t>(dg[4]) << 16;
146+
raw |= static_cast<uint32_t>(dg[3]) << 8;
147+
raw |= dg[2];
148+
out.kind = SeaTalkDecodedKind::total_distance;
149+
out.value = static_cast<Real>(static_cast<float>(raw) / 10.0f);
150+
return true;
151+
}
152+
default:
153+
return false;
154+
}
155+
}
156+
157+
} // namespace seatalk

0 commit comments

Comments
 (0)