altos: Solidify BT connections
[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  * A wrapper for ao_serial_pollchar that
147  * doesn't return any characters while we're
148  * initializing the bluetooth device
149  */
150 char
151 ao_btm_pollchar(void)
152 {
153         char    c;
154         if (!ao_btm_running)
155                 return AO_READ_AGAIN;
156         c = ao_serial_pollchar();
157         if (c != AO_READ_AGAIN)
158                 ao_btm_log_in_char(c);
159         return c;
160 }
161
162 void
163 ao_btm_putchar(char c)
164 {
165         ao_btm_log_out_char(c);
166         ao_serial_putchar(c);
167         if (!ao_btm_running)
168                 ao_delay(1);
169 }
170
171 /*
172  * Wait for the bluetooth device to return
173  * status from the previously executed command
174  */
175 uint8_t
176 ao_btm_wait_reply(void)
177 {
178         for (;;) {
179                 ao_btm_get_line();
180                 if (!strncmp(ao_btm_reply, "OK", 2))
181                         return 1;
182                 if (!strncmp(ao_btm_reply, "ERROR", 5))
183                         return -1;
184                 if (ao_btm_reply[0] == '\0')
185                         return 0;
186         }
187 }
188
189 void
190 ao_btm_string(__code char *cmd)
191 {
192         char    c;
193
194         while (c = *cmd++)
195                 ao_btm_putchar(c);
196 }
197
198 uint8_t
199 ao_btm_cmd(__code char *cmd)
200 {
201         ao_btm_drain();
202         ao_btm_string(cmd);
203         return ao_btm_wait_reply();
204 }
205
206 uint8_t
207 ao_btm_set_name(void)
208 {
209         char    sn[8];
210         char    *s = sn + 8;
211         char    c;
212         int     n;
213         ao_btm_string("ATN=TeleBT-");
214         *--s = '\0';
215         *--s = '\r';
216         n = ao_serial_number;
217         do {
218                 *--s = '0' + n % 10;
219         } while (n /= 10);
220         while ((c = *s++))
221                 ao_btm_putchar(c);
222         return ao_btm_wait_reply();
223 }
224
225 uint8_t
226 ao_btm_try_speed(uint8_t speed)
227 {
228         ao_serial_set_speed(speed);
229         ao_btm_drain();
230         (void) ao_btm_cmd("\rATE0\rATQ0\r");
231         if (ao_btm_cmd("AT\r") == 1)
232                 return 1;
233         return 0;
234 }
235
236 /*
237  * A thread to initialize the bluetooth device and
238  * hang around to blink the LED when connected
239  */
240 void
241 ao_btm(void)
242 {
243         /*
244          * Wait for the bluetooth device to boot
245          */
246         ao_delay(AO_SEC_TO_TICKS(3));
247
248         /*
249          * The first time we connect, the BTM-180 comes up at 19200 baud.
250          * After that, it will remember and come up at 57600 baud. So, see
251          * if it is already running at 57600 baud, and if that doesn't work
252          * then tell it to switch to 57600 from 19200 baud.
253          */
254         while (!ao_btm_try_speed(AO_SERIAL_SPEED_57600)) {
255                 ao_delay(AO_SEC_TO_TICKS(1));
256                 if (ao_btm_try_speed(AO_SERIAL_SPEED_19200))
257                         ao_btm_cmd("ATL4\r");
258                 ao_delay(AO_SEC_TO_TICKS(1));
259         }
260
261         /* Disable echo */
262         ao_btm_cmd("ATE0\r");
263
264         /* Enable flow control */
265         ao_btm_cmd("ATC1\r");
266
267         /* Set the reported name to something we can find on the host */
268         ao_btm_set_name();
269
270         /* Turn off status reporting */
271         ao_btm_cmd("ATQ1\r");
272
273         ao_btm_stdio = ao_add_stdio(ao_btm_pollchar,
274                                     ao_btm_putchar,
275                                     NULL);
276         ao_btm_echo(0);
277
278         ao_btm_running = 1;
279         for (;;) {
280                 while (!ao_btm_connected && !ao_btm_chat)
281                         ao_sleep(&ao_btm_connected);
282                 if (ao_btm_chat) {
283                         ao_btm_running = 0;
284                         while (ao_btm_chat) {
285                                 char    c;
286                                 c = ao_serial_pollchar();
287                                 if (c != AO_READ_AGAIN) {
288                                         ao_btm_log_in_char(c);
289                                         ao_usb_putchar(c);
290                                 } else {
291                                         ao_usb_flush();
292                                         ao_sleep(&ao_usart1_rx_fifo);
293                                 }
294                         }
295                         ao_btm_running = 1;
296                 }
297                 while (ao_btm_connected) {
298                         ao_led_for(AO_LED_GREEN, AO_MS_TO_TICKS(20));
299                         ao_delay(AO_SEC_TO_TICKS(3));
300                 }
301         }
302 }
303
304 __xdata struct ao_task ao_btm_task;
305
306 /*
307  * Connect directly to the bluetooth device, mostly
308  * useful for testing
309  */
310 static void
311 ao_btm_forward(void)
312 {
313         char c;
314
315         ao_btm_chat = 1;
316         ao_wakeup(&ao_btm_connected);
317         ao_usb_flush();
318         while ((c = ao_usb_getchar()) != '~') {
319                 if (c == '\n') c = '\r';
320                 ao_btm_putchar(c);
321         }
322         ao_btm_chat = 0;
323         while (!ao_btm_running) {
324                 ao_wakeup(&ao_usart1_rx_fifo);
325                 ao_delay(AO_MS_TO_TICKS(10));
326         }
327 }
328
329 /*
330  * Dump everything received from the bluetooth device during startup
331  */
332 static void
333 ao_btm_dump(void)
334 {
335         int i;
336         char c;
337
338         for (i = 0; i < ao_btm_ptr; i++) {
339                 c = ao_btm_buffer[i];
340                 if (c < ' ' && c != '\n')
341                         printf("\\%03o", ((int) c) & 0xff);
342                 else
343                         putchar(ao_btm_buffer[i]);
344         }
345         putchar('\n');
346 }
347
348 static void
349 ao_btm_speed(void)
350 {
351         ao_cmd_decimal();
352         if (ao_cmd_lex_u32 == 57600)
353                 ao_serial_set_speed(AO_SERIAL_SPEED_57600);
354         else if (ao_cmd_lex_u32 == 19200)
355                 ao_serial_set_speed(AO_SERIAL_SPEED_19200);
356         else
357                 ao_cmd_status = ao_cmd_syntax_error;
358 }
359
360 void
361 ao_btm_check_link() __critical
362 {
363         if (P2_1) {
364                 ao_btm_connected = 0;
365                 PICTL |= PICTL_P2ICON;
366         } else {
367                 ao_btm_connected = 1;
368                 PICTL &= ~PICTL_P2ICON;
369         }
370 }
371
372 void
373 ao_btm_isr(void)
374 {
375         if (P2IFG & (1 << 1)) {
376                 ao_btm_check_link();
377                 ao_wakeup(&ao_btm_connected);
378         }
379         P2IFG = 0;
380 }
381
382 __code struct ao_cmds ao_btm_cmds[] = {
383         { ao_btm_forward,       "B\0BTM serial link." },
384         { ao_btm_dump,          "d\0Dump btm buffer." },
385         { ao_btm_speed,         "s <19200,57600>\0Set btm serial speed." },
386         { 0, NULL },
387 };
388
389 void
390 ao_btm_init (void)
391 {
392         ao_serial_init();
393         ao_serial_set_speed(AO_SERIAL_SPEED_19200);
394
395         /*
396          * Configure link status line
397          */
398
399         /* Set P2_1 to input, pull-down */
400         P2DIR &= ~(1 << 1);
401         P2INP |= P2INP_MDP2_1_TRISTATE;
402
403         /* Enable P2 interrupts */
404         IEN2 |= IEN2_P2IE;
405         ao_btm_check_link();
406         PICTL |= PICTL_P2IEN;
407
408         ao_add_task(&ao_btm_task, ao_btm, "bt");
409         ao_cmd_register(&ao_btm_cmds[0]);
410 }