altos: Simplify BT communications
[fw/altos] / src / ao_btm.c
1 /*
2  * Copyright © 2011 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #include "ao.h"
19
20 uint8_t                 ao_btm_running;
21 int8_t                  ao_btm_stdio;
22 __xdata uint8_t         ao_btm_connected;
23 uint8_t                 ao_btm_chat;
24
25 __xdata char            ao_btm_buffer[1024];
26 int                     ao_btm_ptr;
27 char                    ao_btm_dir;
28
29 void
30 ao_btm_putchar(char c);
31
32 static void
33 ao_btm_add_char(char c)
34 {
35         if (ao_btm_ptr < sizeof (ao_btm_buffer))
36                 ao_btm_buffer[ao_btm_ptr++] = c;
37 }
38
39 static void
40 ao_btm_log_char(char c, char dir)
41 {
42         if (dir != ao_btm_dir) {
43                 ao_btm_add_char(dir);
44                 ao_btm_dir = dir;
45         }
46         ao_btm_add_char(c);
47 }
48
49 static void
50 ao_btm_log_out_char(char c)
51 {
52         ao_btm_log_char(c, '>');
53 }
54
55 static void
56 ao_btm_log_in_char(char c)
57 {
58         ao_btm_log_char(c, '<');
59 }
60
61 #define AO_BTM_MAX_REPLY        16
62 __xdata char            ao_btm_reply[AO_BTM_MAX_REPLY];
63
64 extern volatile __xdata struct ao_fifo  ao_usart1_rx_fifo;
65
66 /*
67  * Read a line of data from the serial port, truncating
68  * it after a few characters.
69  */
70
71 uint8_t
72 ao_btm_get_line(void)
73 {
74         uint8_t ao_btm_reply_len = 0;
75         char c;
76
77         for (;;) {
78
79                 while ((c = ao_serial_pollchar()) != AO_READ_AGAIN) {
80                         ao_btm_log_in_char(c);
81                         if (ao_btm_reply_len < sizeof (ao_btm_reply))
82                                 ao_btm_reply[ao_btm_reply_len++] = c;
83                         if (c == '\r' || c == '\n')
84                                 goto done;
85                 }
86                 for (c = 0; c < 10; c++) {
87                         ao_delay(AO_MS_TO_TICKS(10));
88                         if (!ao_fifo_empty(ao_usart1_rx_fifo))
89                                 break;
90                 }
91                 if (c == 10)
92                         goto done;
93         }
94 done:
95         for (c = ao_btm_reply_len; c < sizeof (ao_btm_reply);)
96                 ao_btm_reply[c++] = '\0';
97         return ao_btm_reply_len;
98 }
99
100 /*
101  * Drain the serial port completely
102  */
103 void
104 ao_btm_drain()
105 {
106         while (ao_btm_get_line())
107                 ;
108 }
109
110 /*
111  * Set the stdio echo for the bluetooth link
112  */
113 void
114 ao_btm_echo(uint8_t echo)
115 {
116         ao_stdios[ao_btm_stdio].echo = echo;
117 }
118
119 /*
120  * A command line pre-processor to detect connect/disconnect messages
121  * and update the internal state
122  */
123
124 uint8_t
125 ao_cmd_filter(void)
126 {
127         if (ao_cur_stdio != ao_btm_stdio)
128                 return 0;
129         ao_cmd_lex();
130         while (ao_cmd_lex_c != '\n') {
131                 if (ao_match_word("CONNECT"))
132                         return 1;
133                 if (ao_match_word("DISCONNECT"))
134                         return 1;
135                 if (ao_match_word("ERROR"))
136                         return 1;
137                 if (ao_match_word("OK"))
138                         return 1;
139                 ao_cmd_lex();
140         }
141         ao_cmd_status = 0;
142         return 0;
143 }
144
145 /*
146  * Delay between command charaters; the BT module
147  * can't keep up with 57600 baud
148  */
149
150 void
151 ao_btm_putchar(char c)
152 {
153         ao_btm_log_out_char(c);
154         ao_serial_putchar(c);
155         ao_delay(1);
156 }
157
158 /*
159  * Wait for the bluetooth device to return
160  * status from the previously executed command
161  */
162 uint8_t
163 ao_btm_wait_reply(void)
164 {
165         for (;;) {
166                 ao_btm_get_line();
167                 if (!strncmp(ao_btm_reply, "OK", 2))
168                         return 1;
169                 if (!strncmp(ao_btm_reply, "ERROR", 5))
170                         return -1;
171                 if (ao_btm_reply[0] == '\0')
172                         return 0;
173         }
174 }
175
176 void
177 ao_btm_string(__code char *cmd)
178 {
179         char    c;
180
181         while (c = *cmd++)
182                 ao_btm_putchar(c);
183 }
184
185 uint8_t
186 ao_btm_cmd(__code char *cmd)
187 {
188         ao_btm_drain();
189         ao_btm_string(cmd);
190         return ao_btm_wait_reply();
191 }
192
193 uint8_t
194 ao_btm_set_name(void)
195 {
196         char    sn[8];
197         char    *s = sn + 8;
198         char    c;
199         int     n;
200         ao_btm_string("ATN=TeleBT-");
201         *--s = '\0';
202         *--s = '\r';
203         n = ao_serial_number;
204         do {
205                 *--s = '0' + n % 10;
206         } while (n /= 10);
207         while ((c = *s++))
208                 ao_btm_putchar(c);
209         return ao_btm_wait_reply();
210 }
211
212 uint8_t
213 ao_btm_try_speed(uint8_t speed)
214 {
215         ao_serial_set_speed(speed);
216         ao_btm_drain();
217         (void) ao_btm_cmd("\rATE0\rATQ0\r");
218         if (ao_btm_cmd("AT\r") == 1)
219                 return 1;
220         return 0;
221 }
222
223 /*
224  * A thread to initialize the bluetooth device and
225  * hang around to blink the LED when connected
226  */
227 void
228 ao_btm(void)
229 {
230         /*
231          * Wait for the bluetooth device to boot
232          */
233         ao_delay(AO_SEC_TO_TICKS(3));
234
235         /*
236          * The first time we connect, the BTM-180 comes up at 19200 baud.
237          * After that, it will remember and come up at 57600 baud. So, see
238          * if it is already running at 57600 baud, and if that doesn't work
239          * then tell it to switch to 57600 from 19200 baud.
240          */
241         while (!ao_btm_try_speed(AO_SERIAL_SPEED_57600)) {
242                 ao_delay(AO_SEC_TO_TICKS(1));
243                 if (ao_btm_try_speed(AO_SERIAL_SPEED_19200))
244                         ao_btm_cmd("ATL4\r");
245                 ao_delay(AO_SEC_TO_TICKS(1));
246         }
247
248         /* Disable echo */
249         ao_btm_cmd("ATE0\r");
250
251         /* Enable flow control */
252         ao_btm_cmd("ATC1\r");
253
254         /* Set the reported name to something we can find on the host */
255         ao_btm_set_name();
256
257         /* Turn off status reporting */
258         ao_btm_cmd("ATQ1\r");
259
260         ao_btm_stdio = ao_add_stdio(ao_serial_pollchar,
261                                     ao_serial_putchar,
262                                     NULL);
263         ao_btm_echo(0);
264
265         ao_btm_running = 1;
266         for (;;) {
267                 while (!ao_btm_connected && !ao_btm_chat)
268                         ao_sleep(&ao_btm_connected);
269                 if (ao_btm_chat) {
270                         ao_btm_running = 0;
271                         while (ao_btm_chat) {
272                                 char    c;
273                                 c = ao_serial_pollchar();
274                                 if (c != AO_READ_AGAIN) {
275                                         ao_btm_log_in_char(c);
276                                         ao_usb_putchar(c);
277                                 } else {
278                                         ao_usb_flush();
279                                         ao_sleep(&ao_usart1_rx_fifo);
280                                 }
281                         }
282                         ao_btm_running = 1;
283                 }
284                 while (ao_btm_connected) {
285                         ao_led_for(AO_LED_GREEN, AO_MS_TO_TICKS(20));
286                         ao_delay(AO_SEC_TO_TICKS(3));
287                 }
288         }
289 }
290
291 __xdata struct ao_task ao_btm_task;
292
293 /*
294  * Connect directly to the bluetooth device, mostly
295  * useful for testing
296  */
297 static void
298 ao_btm_forward(void)
299 {
300         char c;
301
302         ao_btm_chat = 1;
303         ao_wakeup(&ao_btm_connected);
304         ao_usb_flush();
305         while ((c = ao_usb_getchar()) != '~') {
306                 if (c == '\n') c = '\r';
307                 ao_btm_putchar(c);
308         }
309         ao_btm_chat = 0;
310         while (!ao_btm_running) {
311                 ao_wakeup(&ao_usart1_rx_fifo);
312                 ao_delay(AO_MS_TO_TICKS(10));
313         }
314 }
315
316 /*
317  * Dump everything received from the bluetooth device during startup
318  */
319 static void
320 ao_btm_dump(void)
321 {
322         int i;
323         char c;
324
325         for (i = 0; i < ao_btm_ptr; i++) {
326                 c = ao_btm_buffer[i];
327                 if (c < ' ' && c != '\n')
328                         printf("\\%03o", ((int) c) & 0xff);
329                 else
330                         putchar(ao_btm_buffer[i]);
331         }
332         putchar('\n');
333 }
334
335 static void
336 ao_btm_speed(void)
337 {
338         ao_cmd_decimal();
339         if (ao_cmd_lex_u32 == 57600)
340                 ao_serial_set_speed(AO_SERIAL_SPEED_57600);
341         else if (ao_cmd_lex_u32 == 19200)
342                 ao_serial_set_speed(AO_SERIAL_SPEED_19200);
343         else
344                 ao_cmd_status = ao_cmd_syntax_error;
345 }
346
347 void
348 ao_btm_check_link() __critical
349 {
350         if (P2_1) {
351                 ao_btm_connected = 0;
352                 PICTL |= PICTL_P2ICON;
353         } else {
354                 ao_btm_connected = 1;
355                 PICTL &= ~PICTL_P2ICON;
356         }
357 }
358
359 void
360 ao_btm_isr(void)
361 {
362         if (P2IFG & (1 << 1)) {
363                 ao_btm_check_link();
364                 ao_wakeup(&ao_btm_connected);
365         }
366         P2IFG = 0;
367 }
368
369 __code struct ao_cmds ao_btm_cmds[] = {
370         { ao_btm_forward,       "B\0BTM serial link." },
371         { ao_btm_dump,          "d\0Dump btm buffer." },
372         { ao_btm_speed,         "s <19200,57600>\0Set btm serial speed." },
373         { 0, NULL },
374 };
375
376 void
377 ao_btm_init (void)
378 {
379         ao_serial_init();
380         ao_serial_set_speed(AO_SERIAL_SPEED_19200);
381
382         /*
383          * Configure link status line
384          */
385
386         /* Set P2_1 to input, pull-down */
387         P2DIR &= ~(1 << 1);
388         P2INP |= P2INP_MDP2_1_TRISTATE;
389
390         /* Enable P2 interrupts */
391         IEN2 |= IEN2_P2IE;
392         ao_btm_check_link();
393         PICTL |= PICTL_P2IEN;
394
395         ao_add_task(&ao_btm_task, ao_btm, "bt");
396         ao_cmd_register(&ao_btm_cmds[0]);
397 }