TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
AX25.h
Go to the documentation of this file.
1#pragma once
2
3#include <ctype.h>
4#include <stdint.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8
9#include "AudioTools.h"
10
11namespace tinymodem {
12
15enum class AX25Format {
21};
22
25enum class AX25Layer2 {
32};
33
44struct AX25Config : public audio_tools::AudioInfo {
46 sample_rate = 48000;
47 channels = 1;
48 bits_per_sample = 16;
49 }
50
52 float baud_rate = 1200.0f;
54 float mark_freq = 1200.0f;
56 float space_freq = 2200.0f;
58 float volume = 1.0f;
60 int tx_delay_ms = 300;
62 int tx_tail_ms = 50;
70 bool il2p_max_fec = true;
72 uint8_t channel = 0;
74 float rx_noise = 0.0f;
75
78 bool setMode(const char* mode) {
79 if (!isMode(mode)) return false;
80 baud_rate = 1200.0f;
81 mark_freq = 1200.0f;
82 space_freq = 2200.0f;
84 if (equalsIgnoreCase(mode, "il2p")) {
86 } else if (tolower(mode[0]) == 'f') {
88 fx25_check_bytes = mode[4] == 0 ? 16 : atoi(mode + 5);
89 }
90 return true;
91 }
92
95 static bool isMode(const char* mode) {
96 if (mode == nullptr) return false;
97 for (const char* name :
98 {"ax25", "aprs", "fx25", "fx25-16", "fx25-32", "fx25-64", "il2p"})
99 if (equalsIgnoreCase(mode, name)) return true;
100 return false;
101 }
102
104 bool isValid() const {
105 if (baud_rate <= 0 || mark_freq <= 0 || space_freq <= 0) {
106 LOGE("AX25: invalid baud rate or frequencies");
107 return false;
108 }
109 float max_freq = mark_freq > space_freq ? mark_freq : space_freq;
110 if (max_freq >= sample_rate / 2.0f) {
111 LOGE("AX25: frequencies must be below %.1f Hz",
112 (double)(sample_rate / 2.0f));
113 return false;
114 }
115 if (sample_rate / baud_rate < 4) {
116 LOGE("AX25: at least 4 samples per bit are needed");
117 return false;
118 }
119 if (!(fx25_check_bytes == 16 || fx25_check_bytes == 32 ||
120 fx25_check_bytes == 64)) {
121 LOGE("AX25: fx25_check_bytes must be 16, 32 or 64");
122 return false;
123 }
124 return true;
125 }
126
127 protected:
128 static bool equalsIgnoreCase(const char* a, const char* b) {
129 while (*a && *b) {
130 if (tolower(*a++) != tolower(*b++)) return false;
131 }
132 return *a == *b;
133 }
134};
135
142 public:
143 static constexpr int MAX_FRAME = 330; // 10 addresses + 256 info + extra
144 static constexpr int MIN_FRAME = 15; // 2 addresses + control (no FCS)
145 static constexpr uint8_t KISS_FEND = 0xC0;
146 static constexpr uint8_t KISS_FESC = 0xDB;
147 static constexpr uint8_t KISS_TFEND = 0xDC;
148 static constexpr uint8_t KISS_TFESC = 0xDD;
149
151 static uint16_t fcs(const uint8_t* data, size_t len) {
152 uint16_t crc = 0xFFFF;
153 for (size_t i = 0; i < len; i++) {
154 crc ^= data[i];
155 for (int b = 0; b < 8; b++)
156 crc = (crc & 1) ? (crc >> 1) ^ 0x8408 : (crc >> 1);
157 }
158 return crc ^ 0xFFFF;
159 }
160
163 static int fromText(const char* text, size_t len, uint8_t* frame) {
164 const char* end = text + len;
165 const char* gt = (const char*)memchr(text, '>', len);
166 const char* colon = (const char*)memchr(text, ':', len);
167 if (gt == nullptr || colon == nullptr || colon < gt) return 0;
168 int n = 0;
169 // destination first, then source, then the digipeaters
170 const char* dest = gt + 1;
171 const char* dest_end = dest;
172 while (dest_end < colon && *dest_end != ',') dest_end++;
173 if (!encodeAddress(dest, dest_end, frame + n, true, false)) return 0;
174 n += 7;
175 if (!encodeAddress(text, gt, frame + n, false, false)) return 0;
176 n += 7;
177 const char* digi = dest_end;
178 while (digi < colon) {
179 digi++; // skip the ','
180 const char* digi_end = digi;
181 while (digi_end < colon && *digi_end != ',') digi_end++;
182 if (n + 7 > 7 * 10) return 0; // max 8 digipeaters
183 if (!encodeAddress(digi, digi_end, frame + n, false, true)) return 0;
184 n += 7;
185 digi = digi_end;
186 }
187 frame[n - 1] |= 0x01; // last address
188 frame[n++] = 0x03; // UI frame
189 frame[n++] = 0xF0; // no layer 3
190 size_t info_len = end - (colon + 1);
191 if (n + info_len > MAX_FRAME) return 0;
192 memcpy(frame + n, colon + 1, info_len);
193 return n + info_len;
194 }
195
198 static int toText(const uint8_t* frame, size_t len, char* text,
199 size_t text_size) {
200 // find the end of the address field
201 size_t naddr = 0;
202 while (naddr < 10 && (naddr + 1) * 7 <= len) {
203 naddr++;
204 if (frame[naddr * 7 - 1] & 0x01) break;
205 }
206 if (naddr < 2 || !(frame[naddr * 7 - 1] & 0x01)) return 0;
207 size_t pos = naddr * 7;
208 if (pos >= len) return 0;
209 char call[12];
210 size_t n = 0;
211 auto append = [&](const char* str, size_t str_len) {
212 if (n + str_len < text_size) {
213 memcpy(text + n, str, str_len);
214 n += str_len;
215 }
216 };
217 decodeAddress(frame + 7, call); // source
218 append(call, strlen(call));
219 append(">", 1);
220 decodeAddress(frame, call); // destination
221 append(call, strlen(call));
222 // the last repeated digipeater is marked with a '*'
223 int last_repeated = -1;
224 for (size_t j = 2; j < naddr; j++)
225 if (frame[j * 7 + 6] & 0x80) last_repeated = j;
226 for (size_t j = 2; j < naddr; j++) {
227 append(",", 1);
228 decodeAddress(frame + j * 7, call);
229 append(call, strlen(call));
230 if ((int)j == last_repeated) append("*", 1);
231 }
232 append(":", 1);
233 // skip control and PID of UI frames
234 uint8_t control = frame[pos++];
235 if ((control & 0xEF) == 0x03 && pos < len) pos++;
236 append((const char*)frame + pos, len - pos);
237 text[n] = 0;
238 return n;
239 }
240
242 static size_t writeKiss(Print& out, const uint8_t* frame, size_t len) {
243 size_t n = out.write(KISS_FEND);
244 n += out.write((uint8_t)0x00);
245 for (size_t j = 0; j < len; j++) {
246 if (frame[j] == KISS_FEND) {
247 n += out.write(KISS_FESC);
248 n += out.write(KISS_TFEND);
249 } else if (frame[j] == KISS_FESC) {
250 n += out.write(KISS_FESC);
251 n += out.write(KISS_TFESC);
252 } else {
253 n += out.write(frame[j]);
254 }
255 }
256 n += out.write(KISS_FEND);
257 return n;
258 }
259
260 protected:
261 static bool encodeAddress(const char* start, const char* end, uint8_t* out,
262 bool command, bool digipeater) {
263 bool repeated = false;
264 if (end > start && end[-1] == '*') {
265 repeated = true;
266 end--;
267 }
268 const char* dash = start;
269 while (dash < end && *dash != '-') dash++;
270 int call_len = dash - start;
271 if (call_len < 1 || call_len > 6) return false;
272 for (int j = 0; j < 6; j++) {
273 char ch = j < call_len ? toupper(start[j]) : ' ';
274 if (!isalnum((unsigned char)ch) && ch != ' ') return false;
275 out[j] = ch << 1;
276 }
277 int ssid = 0;
278 if (dash < end) {
279 ssid = atoi(dash + 1);
280 if (ssid < 0 || ssid > 15) return false;
281 }
282 uint8_t flags = 0x60; // reserved bits
283 if (command || (digipeater && repeated)) flags |= 0x80;
284 out[6] = flags | (ssid << 1);
285 return true;
286 }
287
288 static void decodeAddress(const uint8_t* in, char* call) {
289 int n = 0;
290 for (int j = 0; j < 6; j++) {
291 char ch = (in[j] >> 1) & 0x7F;
292 if (ch != ' ') call[n++] = ch;
293 }
294 int ssid = (in[6] >> 1) & 0x0F;
295 if (ssid > 0) n += sprintf(call + n, "-%d", ssid);
296 call[n] = 0;
297 }
298};
299
300} // namespace tinymodem
Conversion between AX.25 frames and the monitor text format (SRC>DEST,DIGI1,DIGI2*:info) and the KISS...
Definition AX25.h:141
static constexpr uint8_t KISS_FESC
Definition AX25.h:146
static size_t writeKiss(Print &out, const uint8_t *frame, size_t len)
Writes the frame as KISS data frame for port 0.
Definition AX25.h:242
static bool encodeAddress(const char *start, const char *end, uint8_t *out, bool command, bool digipeater)
Definition AX25.h:261
static int toText(const uint8_t *frame, size_t len, char *text, size_t text_size)
Definition AX25.h:198
static uint16_t fcs(const uint8_t *data, size_t len)
AX.25 frame check sequence: CRC-16-CCITT with reflected bits.
Definition AX25.h:151
static constexpr uint8_t KISS_TFEND
Definition AX25.h:147
static constexpr uint8_t KISS_FEND
Definition AX25.h:145
static constexpr uint8_t KISS_TFESC
Definition AX25.h:148
static void decodeAddress(const uint8_t *in, char *call)
Definition AX25.h:288
static int fromText(const char *text, size_t len, uint8_t *frame)
Definition AX25.h:163
static constexpr int MAX_FRAME
Definition AX25.h:143
static constexpr int MIN_FRAME
Definition AX25.h:144
FX.25: AX.25 frames with Reed-Solomon forward error correction (as implemented by direwolf)....
Definition AX25FEC.h:20
IL2P (Improved Layer 2 Protocol, as implemented by direwolf): an AX.25 frame is sent as a scrambled h...
Definition AX25FEC.h:214
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11
AX25Format
Definition AX25.h:15
@ TEXT
one frame per line in the monitor format: SRC>DEST,DIGI1,DIGI2:info
Definition AX25.h:17
@ KISS
Definition AX25.h:20
AX25Layer2
Definition AX25.h:25
@ IL2P
IL2P: Improved Layer 2 Protocol with Reed-Solomon FEC.
Definition AX25.h:31
@ AX25
HDLC framing (standard AX.25).
Definition AX25.h:27
@ FX25
FX.25: HDLC frame with Reed-Solomon FEC (compatible with AX.25).
Definition AX25.h:29
float volume
transmit amplitude (0.0 - 1.0)
Definition AX25.h:58
AX25Format format
format of the written and decoded data
Definition AX25.h:64
float baud_rate
bits per second
Definition AX25.h:52
float space_freq
space frequency in Hz
Definition AX25.h:56
bool setMode(const char *mode)
Definition AX25.h:78
static bool isMode(const char *mode)
Definition AX25.h:95
bool il2p_max_fec
transmitter: IL2P with 16 parity bytes per block (otherwise 2 - 8)
Definition AX25.h:70
static bool equalsIgnoreCase(const char *a, const char *b)
Definition AX25.h:128
AX25Layer2 layer2
transmitter: frame format (AX.25, FX.25 or IL2P)
Definition AX25.h:66
uint8_t channel
receiver: input channel which is used for the decoding
Definition AX25.h:72
float mark_freq
mark frequency (1 = no tone change) in Hz
Definition AX25.h:54
int tx_tail_ms
transmitter: flags which are sent after the last frame (in ms)
Definition AX25.h:62
float rx_noise
receiver: (testing only) amplitude of added uniform noise
Definition AX25.h:74
bool isValid() const
Checks the configuration: returns false (and logs the reason) if invalid.
Definition AX25.h:104
int fx25_check_bytes
transmitter: FX.25 check bytes (16, 32 or 64)
Definition AX25.h:68
int tx_delay_ms
transmitter: flags which are sent before the first frame (in ms)
Definition AX25.h:60
AX25Config()
Definition AX25.h:45