TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
AX25Encoder.h
Go to the documentation of this file.
1#pragma once
2
3#include "AX25.h"
4#include "AX25FEC.h"
5#include "AudioTools.h"
6#include "ModemBase.h"
7
8namespace tinymodem {
9
27class AX25Encoder : public ModemEncoder {
28 public:
29 AX25Encoder() = default;
30 AX25Encoder(Print& out) { setOutput(out); }
31
34 AX25Config result;
35 result.setMode("aprs");
36 return result;
37 }
38
40 void setAudioInfo(audio_tools::AudioInfo info) override {
41 cfg.sample_rate = info.sample_rate;
42 cfg.channels = info.channels;
43 cfg.bits_per_sample = info.bits_per_sample;
44 if (is_active) begin();
45 }
46
47 audio_tools::AudioInfo audioInfo() override { return cfg; }
48
49 using audio_tools::AudioEncoder::begin;
50
53 cfg = config;
54 return begin();
55 }
56
57 bool begin() override {
58 is_active = false;
59 if (!cfg.isValid()) return false;
60 if (cfg.bits_per_sample != 16) {
61 LOGE("AX25Encoder: bits_per_sample must be 16, not %d",
62 (int)cfg.bits_per_sample);
63 return false;
64 }
65 if (cfg.channels < 1) {
66 LOGE("AX25Encoder: invalid number of channels: %d", (int)cfg.channels);
67 return false;
68 }
69 if (!tones.begin(cfg, cfg.volume)) return false;
70 samples_per_bit = (double)cfg.sample_rate / cfg.baud_rate;
71 transmitting = false;
72 input_len = 0;
74 is_active = true;
75 return true;
76 }
77
78 void end() override {
80 is_active = false;
81 }
82
84 size_t write(const uint8_t* data, size_t len) override {
85 if (!is_active) return 0;
86 for (size_t j = 0; j < len; j++) {
87 if (cfg.format == AX25Format::KISS)
88 writeKiss(data[j]);
89 else
90 writeText(data[j]);
91 }
92 return len;
93 }
94
96 bool writeFrame(const uint8_t* frame, size_t len) {
97 if (!is_active || len < (size_t)AX25Frame::MIN_FRAME) return false;
98 if (cfg.layer2 == AX25Layer2::FX25) return writeFX25(frame, len);
99 if (cfg.layer2 == AX25Layer2::IL2P) return writeIL2P(frame, len);
101 sendFlag();
102 ones = 0;
103 for (size_t j = 0; j < len; j++) sendByte(frame[j]);
104 uint16_t fcs = AX25Frame::fcs(frame, len);
105 sendByte(fcs & 0xFF);
106 sendByte(fcs >> 8);
107 sendFlag();
108 frames++;
109 return true;
110 }
111
114 bool endTransmission(size_t silence_samples = 0) override {
115 if (!is_active || !transmitting) return false;
116 sendPreamble(cfg.tx_tail_ms);
117 if (silence_samples) tones.tone(0, silence_samples);
118 transmitting = false;
119 return true;
120 }
121
123 bool writeIdle(size_t nsamples) override {
124 if (!is_active) return false;
125 if (!transmitting) {
126 transmitting = true;
127 bit_index = 0;
128 nrzi_tone = true;
129 }
130 size_t n = nsamples / (samples_per_bit * 8) + 1;
131 for (size_t j = 0; j < n; j++) {
132 if (cfg.layer2 == AX25Layer2::IL2P)
134 else
135 sendFlag();
136 }
137 return true;
138 }
139
141 bool isTransmitting() override { return transmitting; }
142
144 float symbolRate() override { return cfg.baud_rate; }
145
147 size_t frameCount() { return frames; }
148
150 AX25Config& config() { return cfg; }
151
152 operator bool() override { return is_active; }
153
154 protected:
155 enum class KissState { IDLE, COMMAND, DATA, ESCAPE };
157 bool is_active = false;
158 bool transmitting = false;
159 double samples_per_bit = 40;
160 uint64_t bit_index = 0;
161 bool nrzi_tone = true; // true = mark
162 int ones = 0;
163 size_t frames = 0;
164 // input
166 int input_len = 0;
169 bool kiss_data_frame = false;
170
171 void writeText(uint8_t ch) {
172 if (ch == '\r') return;
173 if (ch != '\n') {
174 if (input_len < (int)sizeof(input)) input[input_len++] = ch;
175 return;
176 }
177 if (input_len > 0) {
179 if (len > 0)
180 writeFrame(frame, len);
181 else
182 LOGW("AX25Encoder: invalid frame: %.*s", input_len, input);
183 }
184 input_len = 0;
185 }
186
187 void writeKiss(uint8_t ch) {
188 switch (kiss_state) {
189 case KissState::IDLE:
191 break;
193 if (ch == AX25Frame::KISS_FEND) break; // repeated FEND
194 // only data frames (command 0) are sent
195 kiss_data_frame = (ch & 0x0F) == 0;
196 input_len = 0;
198 break;
199 case KissState::DATA:
200 if (ch == AX25Frame::KISS_FEND) {
201 if (kiss_data_frame && input_len > 0)
202 writeFrame((const uint8_t*)input, input_len);
204 } else if (ch == AX25Frame::KISS_FESC) {
206 } else {
207 addKiss(ch);
208 }
209 break;
214 break;
215 }
216 }
217
218 void addKiss(uint8_t ch) {
220 }
221
223 if (transmitting) return;
224 transmitting = true;
225 bit_index = 0;
226 nrzi_tone = true;
227 sendPreamble(cfg.tx_delay_ms);
228 }
229
231 void sendPreamble(int ms) {
232 int n = ms * cfg.baud_rate / 8000;
233 if (n < 1) n = 1;
234 for (int j = 0; j < n; j++) {
235 if (cfg.layer2 == AX25Layer2::IL2P)
237 else
238 sendFlag();
239 }
240 }
241
243 bool writeFX25(const uint8_t* frame, size_t len) {
244 uint8_t block[FX25::BLOCK_SIZE];
245 int block_len = 0;
246 int tag = FX25::encode(frame, len, cfg.fx25_check_bytes, block, block_len);
247 if (tag == 0) {
248 LOGW("AX25Encoder: frame is too long for FX.25");
249 return false;
250 }
252 uint64_t value = FX25::tag(tag).value;
253 for (int k = 0; k < 8; k++) sendBits((value >> (8 * k)) & 0xFF);
254 for (int j = 0; j < block_len; j++) sendBits(block[j]);
255 sendFlag();
256 frames++;
257 return true;
258 }
259
261 bool writeIL2P(const uint8_t* frame, size_t len) {
262 // a frame of up to MAX_FRAME bytes needs at most 2 payload blocks
263 uint8_t encoded[IL2P::HEADER_SIZE + IL2P::HEADER_PARITY +
264 AX25Frame::MAX_FRAME + 2 * 16];
265 int n = IL2P::encode(frame, len, cfg.il2p_max_fec, encoded);
266 if (n > 0) {
269 for (int k = 2; k >= 0; k--) sendRawByte((IL2P::SYNC_WORD >> (8 * k)) & 0xFF);
270 for (int j = 0; j < n; j++) sendRawByte(encoded[j]);
271 frames++;
272 } else {
273 LOGW("AX25Encoder: frame can't be encoded with IL2P");
274 }
275 return n > 0;
276 }
277
279 void sendBits(uint8_t value) {
280 for (int b = 0; b < 8; b++) sendBit((value >> b) & 1);
281 }
282
284 void sendRawByte(uint8_t value) {
285 for (int b = 7; b >= 0; b--) {
286 nrzi_tone = (value >> b) & 1;
287 sendTone();
288 }
289 }
290
292 void sendFlag() {
293 for (int b = 0; b < 8; b++) sendBit((0x7E >> b) & 1);
294 }
295
297 void sendByte(uint8_t value) {
298 for (int b = 0; b < 8; b++) {
299 int bit = (value >> b) & 1;
300 sendBit(bit);
301 if (bit) {
302 if (++ones == 5) {
303 sendBit(0);
304 ones = 0;
305 }
306 } else {
307 ones = 0;
308 }
309 }
310 }
311
313 void sendBit(int bit) {
314 if (bit == 0) nrzi_tone = !nrzi_tone;
315 sendTone();
316 }
317
319 void sendTone() {
320 size_t start = llround(bit_index * samples_per_bit);
321 size_t stop = llround((bit_index + 1) * samples_per_bit);
322 bit_index++;
323 tones.tone(nrzi_tone ? cfg.mark_freq : cfg.space_freq, stop - start);
324 }
325};
326
327} // namespace tinymodem
KissState kiss_state
Definition AX25Encoder.h:168
void sendBits(uint8_t value)
least significant bit first without bit stuffing (NRZI)
Definition AX25Encoder.h:279
AX25Encoder(Print &out)
Definition AX25Encoder.h:30
size_t write(const uint8_t *data, size_t len) override
Provide the frames as text lines or KISS frames.
Definition AX25Encoder.h:84
size_t frames
Definition AX25Encoder.h:163
int input_len
Definition AX25Encoder.h:166
bool begin(AX25Config config)
Starts the processing with the indicated configuration.
Definition AX25Encoder.h:52
size_t frameCount()
Number of sent frames.
Definition AX25Encoder.h:147
void setAudioInfo(audio_tools::AudioInfo info) override
Defines the sample rate and number of channels of the generated audio.
Definition AX25Encoder.h:40
bool transmitting
Definition AX25Encoder.h:158
bool begin() override
Definition AX25Encoder.h:57
bool nrzi_tone
Definition AX25Encoder.h:161
bool writeFX25(const uint8_t *frame, size_t len)
FX.25: correlation tag and Reed-Solomon code word (NRZI, no stuffing).
Definition AX25Encoder.h:243
void addKiss(uint8_t ch)
Definition AX25Encoder.h:218
AX25Config defaultConfig()
Provides a default configuration: APRS (1200 baud AFSK).
Definition AX25Encoder.h:33
void sendPreamble(int ms)
Flags (IL2P: 0x55) for the indicated time.
Definition AX25Encoder.h:231
int ones
Definition AX25Encoder.h:162
bool writeIL2P(const uint8_t *frame, size_t len)
IL2P: preamble, sync word and the encoded frame (no NRZI).
Definition AX25Encoder.h:261
KissState
Definition AX25Encoder.h:155
@ COMMAND
Definition AX25Encoder.h:155
@ IDLE
Definition AX25Encoder.h:155
@ ESCAPE
Definition AX25Encoder.h:155
@ DATA
Definition AX25Encoder.h:155
bool writeFrame(const uint8_t *frame, size_t len)
Sends a frame (without FCS).
Definition AX25Encoder.h:96
uint8_t frame[AX25Frame::MAX_FRAME]
Definition AX25Encoder.h:167
bool is_active
Definition AX25Encoder.h:157
audio_tools::AudioInfo audioInfo() override
Definition AX25Encoder.h:47
double samples_per_bit
Definition AX25Encoder.h:159
bool kiss_data_frame
Definition AX25Encoder.h:169
void end() override
Definition AX25Encoder.h:78
bool endTransmission(size_t silence_samples=0) override
Definition AX25Encoder.h:114
void sendFlag()
0x7E without bit stuffing
Definition AX25Encoder.h:292
void sendTone()
sends the current tone for one bit
Definition AX25Encoder.h:319
void sendByte(uint8_t value)
least significant bit first with bit stuffing
Definition AX25Encoder.h:297
void sendBit(int bit)
NRZI: a 0 changes the tone, a 1 keeps it.
Definition AX25Encoder.h:313
void startTransmission()
Definition AX25Encoder.h:222
void writeKiss(uint8_t ch)
Definition AX25Encoder.h:187
void sendRawByte(uint8_t value)
most significant bit first without NRZI: 1 = mark
Definition AX25Encoder.h:284
float symbolRate() override
Bits per second.
Definition AX25Encoder.h:144
bool writeIdle(size_t nsamples) override
Sends flags for the indicated number of samples.
Definition AX25Encoder.h:123
void writeText(uint8_t ch)
Definition AX25Encoder.h:171
uint64_t bit_index
Definition AX25Encoder.h:160
AX25Config cfg
Definition AX25Encoder.h:156
char input[AX25Frame::MAX_FRAME+100]
Definition AX25Encoder.h:165
AX25Config & config()
The configuration which was provided in begin().
Definition AX25Encoder.h:150
bool isTransmitting() override
true between the first frame and endTransmission()
Definition AX25Encoder.h:141
static constexpr uint8_t KISS_FESC
Definition AX25.h:146
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 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
static const Tag & tag(int index)
The tag 1 - 11.
Definition AX25FEC.h:39
static constexpr int BLOCK_SIZE
Definition AX25FEC.h:31
static int encode(const uint8_t *frame, int len, int check_bytes, uint8_t *block, int &block_len)
Definition AX25FEC.h:80
static constexpr uint8_t PREAMBLE
Definition AX25FEC.h:216
static constexpr int HEADER_PARITY
Definition AX25FEC.h:219
static int encode(const uint8_t *frame, int len, bool max_fec, uint8_t *out)
Definition AX25FEC.h:226
static constexpr uint32_t SYNC_WORD
Definition AX25FEC.h:217
static constexpr int HEADER_SIZE
Definition AX25FEC.h:218
Common API of the modulators (FSKEncoder, MFSKEncoder): the written data is converted to 16 bit PCM a...
Definition ModemBase.h:286
void setOutput(Print &out) override
Definition ModemBase.h:288
ToneOutput tones
Definition ModemBase.h:334
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11
@ KISS
Definition AX25.h:20
@ IL2P
IL2P: Improved Layer 2 Protocol with Reed-Solomon FEC.
Definition AX25.h:31
@ FX25
FX.25: HDLC frame with Reed-Solomon FEC (compatible with AX.25).
Definition AX25.h:29
Configuration of the AX.25 packet radio modem (AX25Encoder, AX25Decoder): Bell 202 AFSK with 1200 bau...
Definition AX25.h:44
bool setMode(const char *mode)
Definition AX25.h:78
uint64_t value
Definition AX25FEC.h:24