1
|
/*
|
2
|
* Send a tone and write received data to a file on SIMCom SIM7100E.
|
3
|
*
|
4
|
* Format is PCM 16bit, 8kHz, Mono, LE
|
5
|
* https://lists.freedesktop.org/archives/modemmanager-devel/2016-April/002904.html
|
6
|
*
|
7
|
* Send 640 bytes every 40ms, as per
|
8
|
* http://simcom.ee/documents/SIM5360/SIM5360_USB_AUDIO_Application_Note_V1.01.pdf
|
9
|
*
|
10
|
* Copyright 2018 Purism SPC
|
11
|
*
|
12
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
13
|
*
|
14
|
* Written by Bob Ham <bob.ham@puri.sm>
|
15
|
*/
|
16
|
|
17
|
/*
|
18
|
* To compile:
|
19
|
*
|
20
|
* make simcom-tone
|
21
|
*
|
22
|
* To use, first on the AT command TTY, /dev/ttyUSB2:
|
23
|
*
|
24
|
* ATD0123456789;
|
25
|
* OK
|
26
|
* AT+CPCMREG=1
|
27
|
* OK
|
28
|
*
|
29
|
* Then run this program:
|
30
|
*
|
31
|
* ./simcom-tone /dev/ttyUSB4 recording.raw
|
32
|
*
|
33
|
* Answer the call, speak a bit, hang up. Hit Ctrl+C to end
|
34
|
* simcom-tone.
|
35
|
*
|
36
|
* To listen to the recording, something like this is expected to
|
37
|
* work:
|
38
|
*
|
39
|
* aplay -r 8000 -f S16_LE -c 1 recording.raw
|
40
|
*
|
41
|
*/
|
42
|
|
43
|
#include <stdio.h>
|
44
|
#include <errno.h>
|
45
|
#include <string.h>
|
46
|
#include <unistd.h>
|
47
|
#include <sys/types.h>
|
48
|
#include <sys/stat.h>
|
49
|
#include <fcntl.h>
|
50
|
#include <stdint.h>
|
51
|
#include <sys/time.h>
|
52
|
#include <stdbool.h>
|
53
|
|
54
|
#define FRAME_LEN 640
|
55
|
#define US_PER_MS 1000
|
56
|
#define PERIOD_US (40 * US_PER_MS)
|
57
|
|
58
|
static const struct timeval TIME_PERIOD = { 0, PERIOD_US };
|
59
|
static const struct timeval TIME_ZERO = { 0, 0 };
|
60
|
|
61
|
static int prepare_frame (void *frame, size_t len)
|
62
|
{
|
63
|
static const uint16_t TONE_SAMPLES[2] = { 0, 0xFFFF };
|
64
|
const size_t FRAME_SAMPLES = len / sizeof(uint16_t);
|
65
|
size_t i;
|
66
|
uint16_t *samples = frame;
|
67
|
|
68
|
for (i = 0; i < FRAME_SAMPLES; ++i)
|
69
|
{
|
70
|
samples[i] = TONE_SAMPLES[i % 2];
|
71
|
}
|
72
|
}
|
73
|
|
74
|
static ssize_t checked_write(int fd, const void *buf,
|
75
|
size_t count, const char *desc)
|
76
|
{
|
77
|
ssize_t write_len;
|
78
|
|
79
|
write_len = write (fd, buf, count);
|
80
|
if (write_len == -1)
|
81
|
{
|
82
|
fprintf (stderr, "Error writing to %s: %s\n",
|
83
|
desc, strerror (errno));
|
84
|
return -1;
|
85
|
}
|
86
|
if (write_len != count)
|
87
|
{
|
88
|
fprintf (stderr,
|
89
|
"Short write of %zi bytes to %s (should be %i)\n",
|
90
|
write_len, desc, count);
|
91
|
return -1;
|
92
|
}
|
93
|
|
94
|
return 0;
|
95
|
}
|
96
|
|
97
|
static int write_frame (int fd, void *frame)
|
98
|
{
|
99
|
ssize_t err;
|
100
|
|
101
|
err = checked_write (fd, frame, FRAME_LEN, "audio port");
|
102
|
|
103
|
if (err == 0)
|
104
|
{
|
105
|
fprintf (stderr, "Wrote frame\n");
|
106
|
}
|
107
|
|
108
|
return err;
|
109
|
}
|
110
|
|
111
|
static int read_block (int device, int recording)
|
112
|
{
|
113
|
uint8_t block[1600];
|
114
|
ssize_t read_len;
|
115
|
|
116
|
read_len = read (device, block, sizeof(block));
|
117
|
if (read_len == -1)
|
118
|
{
|
119
|
fprintf (stderr, "Error reading: %s\n",
|
120
|
strerror (errno));
|
121
|
return -1;
|
122
|
}
|
123
|
|
124
|
fprintf (stderr, "Read %zi (block size %zu)\n",
|
125
|
read_len, sizeof(block));
|
126
|
|
127
|
return (int) checked_write (recording, block, read_len,
|
128
|
"recording file");
|
129
|
}
|
130
|
|
131
|
static struct timeval calc_wait (struct timeval *next_write)
|
132
|
{
|
133
|
struct timeval now;
|
134
|
|
135
|
gettimeofday (&now, NULL);
|
136
|
|
137
|
if (timercmp (next_write, &now, >))
|
138
|
{
|
139
|
struct timeval wait;
|
140
|
timersub (next_write, &now, &wait);
|
141
|
return wait;
|
142
|
}
|
143
|
|
144
|
return TIME_ZERO;
|
145
|
}
|
146
|
|
147
|
static int do_wait(int fd, struct timeval *wait_time,
|
148
|
bool *read_ready, bool *write_ready)
|
149
|
{
|
150
|
bool need_write;
|
151
|
fd_set rfds, wfds;
|
152
|
fd_set *wfdsp;
|
153
|
int retval;
|
154
|
|
155
|
FD_ZERO(&rfds);
|
156
|
FD_SET(fd, &rfds);
|
157
|
|
158
|
need_write = !timercmp(wait_time, &TIME_ZERO, !=);
|
159
|
if (need_write)
|
160
|
{
|
161
|
fprintf (stderr, "Waiting indefinitely (wait time: %u secs, %u usecs)\n",
|
162
|
(unsigned) wait_time->tv_sec,
|
163
|
(unsigned) wait_time->tv_usec);
|
164
|
wait_time = NULL;
|
165
|
wfdsp = &wfds;
|
166
|
FD_ZERO(wfdsp);
|
167
|
FD_SET(fd, wfdsp);
|
168
|
}
|
169
|
else
|
170
|
{
|
171
|
wfdsp = NULL;
|
172
|
fprintf (stderr, "Waiting %u secs, %u usecs\n",
|
173
|
(unsigned) wait_time->tv_sec,
|
174
|
(unsigned) wait_time->tv_usec);
|
175
|
}
|
176
|
|
177
|
retval = select (fd + 1, &rfds, wfdsp, NULL, wait_time);
|
178
|
if (retval == -1)
|
179
|
{
|
180
|
fprintf (stderr, "Error waiting for audio data: %s\n",
|
181
|
strerror (errno));
|
182
|
return -1;
|
183
|
}
|
184
|
|
185
|
if (FD_ISSET (fd, &rfds))
|
186
|
{
|
187
|
*read_ready = true;
|
188
|
}
|
189
|
|
190
|
if (need_write && FD_ISSET (fd, wfdsp))
|
191
|
{
|
192
|
*write_ready = true;
|
193
|
}
|
194
|
|
195
|
return retval;
|
196
|
}
|
197
|
|
198
|
static void add_period (struct timeval *next_write)
|
199
|
{
|
200
|
struct timeval old = *next_write;
|
201
|
timeradd (&old, &TIME_PERIOD, next_write);
|
202
|
}
|
203
|
|
204
|
static int loop(int device, int recording)
|
205
|
{
|
206
|
uint8_t frame[FRAME_LEN];
|
207
|
struct timeval next_write, wait_time;
|
208
|
int ret;
|
209
|
bool read_ready, write_ready;
|
210
|
|
211
|
prepare_frame (frame, FRAME_LEN);
|
212
|
|
213
|
ret = write_frame (device, frame);
|
214
|
if (ret == -1)
|
215
|
{
|
216
|
return -1;
|
217
|
}
|
218
|
|
219
|
gettimeofday (&next_write, NULL);
|
220
|
add_period (&next_write);
|
221
|
|
222
|
for (;;)
|
223
|
{
|
224
|
wait_time = calc_wait (&next_write);
|
225
|
|
226
|
write_ready = read_ready = false;
|
227
|
ret = do_wait (device, &wait_time, &read_ready, &write_ready);
|
228
|
|
229
|
if (ret == -1)
|
230
|
{
|
231
|
return -1;
|
232
|
}
|
233
|
|
234
|
if (read_ready)
|
235
|
{
|
236
|
ret = read_block (device, recording);
|
237
|
if (ret == -1)
|
238
|
{
|
239
|
return -1;
|
240
|
}
|
241
|
}
|
242
|
|
243
|
if (write_ready)
|
244
|
{
|
245
|
ret = write_frame (device, frame);
|
246
|
if (ret == -1)
|
247
|
{
|
248
|
return -1;
|
249
|
}
|
250
|
add_period (&next_write);
|
251
|
}
|
252
|
}
|
253
|
}
|
254
|
|
255
|
int checked_open (const char *filename, int flags)
|
256
|
{
|
257
|
int fd;
|
258
|
|
259
|
fd = open (filename, flags, 0644);
|
260
|
if (fd == -1)
|
261
|
{
|
262
|
fprintf (stderr, "Error opening `%s': %s",
|
263
|
filename, strerror (errno));
|
264
|
}
|
265
|
|
266
|
return fd;
|
267
|
}
|
268
|
|
269
|
int main(int argc, char **argv)
|
270
|
{
|
271
|
const char *device_name, *recording_name;
|
272
|
int device, recording;
|
273
|
|
274
|
if (argc < 3)
|
275
|
{
|
276
|
fprintf (stderr, "Usage: %s <device> <recording file>\n", argv[0]);
|
277
|
return 1;
|
278
|
}
|
279
|
|
280
|
device_name = argv[1];
|
281
|
recording_name = argv[2];
|
282
|
|
283
|
device = checked_open (device_name, O_RDWR);
|
284
|
if (device == -1)
|
285
|
{
|
286
|
return 1;
|
287
|
}
|
288
|
|
289
|
recording = checked_open (recording_name, O_WRONLY|O_CREAT|O_TRUNC);
|
290
|
if (recording == -1)
|
291
|
{
|
292
|
return 1;
|
293
|
}
|
294
|
|
295
|
loop (device, recording);
|
296
|
|
297
|
|
298
|
close (device);
|
299
|
close (recording);
|
300
|
return 0;
|
301
|
}
|