Terminal program for MailStation devices
1/*
2 * msTERM
3 *
4 * Copyright (c) 2019 joshua stein <jcs@jcs.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <stdio.h>
20#include <string.h>
21#include <stdlib.h>
22
23#include "mailstation.h"
24#include "logo.h"
25
26unsigned char last_key;
27unsigned char esc;
28unsigned char old_minutes;
29unsigned char obuf_sent_pos;
30unsigned char last_modem_msr;
31
32#define MODEM_MSR_DCD (1 << 7)
33
34int process_keyboard(void);
35void process_input(unsigned char b);
36void update_clock(void);
37void update_f1(void);
38void obuf_flush(void);
39void wifi_hangup(void);
40
41unsigned char source;
42
43enum {
44 STATUSBAR_F1,
45 STATUSBAR_F2,
46 STATUSBAR_F3,
47 STATUSBAR_F4,
48 STATUSBAR_F5,
49 STATUSBAR_INIT,
50};
51
52void
53obuf_queue(unsigned char *c)
54{
55 unsigned char x;
56
57 for (x = 0; c[x] != '\0'; x++)
58 obuf[obuf_pos++] = c[x];
59}
60
61int
62main(void)
63{
64 unsigned char ms[10];
65 unsigned char shown_logo;
66 int b, j;
67
68 /* ignore first peekkey() if it returns power button */
69 last_key = KEY_POWER;
70 esc = 0;
71 putchar_sgr = 0;
72 in_csi = 0;
73 csibuflen = 0;
74 obuf_pos = 0;
75 obuf_sent_pos = 0;
76 debug0 = 0;
77 shown_logo = 0;
78
79 patch_isr();
80
81 settings_read();
82 source = setting_default_source;
83 if (source >= SOURCE_LAST)
84 source = SOURCE_WIFI;
85
86 clear_screen_bufs();
87 clear_screen();
88 update_statusbar(STATUSBAR_INIT, NULL);
89
90begin:
91 if (source == SOURCE_WIFI)
92 /* call this early to sleep while we draw the logo */
93 wifi_init();
94
95 if (!shown_logo) {
96 /* - 1 to ignore final null byte */
97 for (b = 0; b < sizeof(logo) - 1; b++) {
98 if (b == 0 || logo[b - 1] == '\n') {
99 /* center without wasting space in logo[] */
100 for (j = 0; j < 14; j++)
101 putchar(' ');
102 }
103
104 putchar(logo[b]);
105 }
106 printf(" v%u\n\n", msTERM_version);
107 shown_logo = 1;
108 }
109
110 old_minutes = 0xff;
111 update_clock();
112
113 update_f1();
114
115 switch (source) {
116 case SOURCE_MODEM:
117 modem_init();
118
119 /* Restore factory configuration 0 */
120 obuf_queue("AT&F0");
121 /* Select modulation - V.34, automode, min_rate */
122 obuf_queue("+MS=11,1,300,");
123 /* max_rate */
124 itoa(setting_modem_speed, ms, 10);
125 obuf_queue(ms);
126 /* Turn speaker on */
127 obuf_queue("M1");
128 /* Set low speaker volume */
129 obuf_queue("L0");
130 /* Allow result codes to DTE */
131 obuf_queue("Q0");
132 /* Enable transparent XON/XOFF flow control */
133 obuf_queue("&K5\r");
134 break;
135 case SOURCE_WIFI:
136 obuf_queue("\rAT\r");
137 obuf_flush();
138 break;
139 }
140
141 obuf_flush();
142
143 for (;;) {
144 b = process_keyboard();
145 if (b == KEY_F4)
146 /* we changed sources */
147 goto begin;
148
149 switch (source) {
150 case SOURCE_MODEM:
151 if (modem_msr() != last_modem_msr) {
152 update_f1();
153 last_modem_msr = modem_curmsr;
154 }
155 if (modem_lsr() & (1 << 0)) {
156 process_input(modem_read());
157 continue;
158 }
159 break;
160 case SOURCE_LPT:
161 b = lptrecv();
162 if (b <= 0xff) {
163 process_input(b & 0xff);
164 continue;
165 }
166 break;
167 case SOURCE_WIFI:
168 b = wifi_read();
169 if (b != -1) {
170 process_input(b & 0xff);
171 continue;
172 }
173 break;
174 }
175
176 if (obuf_sent_pos != obuf_pos)
177 obuf_flush();
178
179 update_clock();
180 }
181
182 return 0;
183}
184
185void
186update_statusbar(char which, char *status, ...)
187{
188 va_list args;
189 char tstatus[64];
190 char *result = NULL;
191 unsigned char i, l;
192
193 if (which == STATUSBAR_INIT) {
194 for (i = 0; i < LCD_COLS; i++)
195 putchar_attr(LCD_ROWS - 1, i,
196 ((i + 1) % 13 == 0 ? '|' : ' '), ATTR_REVERSE);
197 return;
198 }
199
200 va_start(args, status);
201 l = vsprintf(tstatus, status, args);
202 va_end(args);
203
204 if (l > sizeof(tstatus)) {
205 new_mail(1);
206 panic();
207 }
208
209 for (i = 0; i < l; i++) {
210 putchar_attr(LCD_ROWS - 1, (which * 13) + i, tstatus[i],
211 ATTR_REVERSE);
212 }
213}
214
215void
216update_clock(void)
217{
218 static const char modem_s[] = "Modem";
219 static const char wifi_s[] = "WiFi ";
220
221 if (rtcminutes == old_minutes)
222 return;
223
224 update_statusbar(STATUSBAR_F4, " %s ",
225 (source == SOURCE_MODEM ? modem_s: wifi_s));
226 update_statusbar(STATUSBAR_F5, " %02d:%02d ",
227 (rtc10hours * 10) + rtchours,
228 (rtc10minutes * 10) + rtcminutes);
229
230 old_minutes = rtcminutes;
231}
232
233void
234update_f1(void)
235{
236 unsigned char s = 0;
237
238 if (source == SOURCE_MODEM) {
239 if (modem_curmsr & MODEM_MSR_DCD)
240 update_statusbar(STATUSBAR_F1, " Hangup ");
241 else
242 update_statusbar(STATUSBAR_F1, " No Carrier");
243 } else if (source == SOURCE_WIFI) {
244 update_statusbar(STATUSBAR_F1, " Hangup ");
245 }
246}
247
248int
249process_keyboard(void)
250{
251 unsigned char b;
252
253 b = peekkey();
254
255 /* this breaks key-repeat, but it's needed to debounce */
256 if (b == 0)
257 last_key = 0;
258 else if (b == last_key)
259 b = 0;
260 else
261 last_key = b;
262
263 if (b == 0)
264 return 0;
265
266 switch (b) {
267 case KEY_POWER:
268 /* not sure why the delay is needed to actually power down */
269 clear_screen();
270 delay(200);
271 powerdown();
272 break;
273 case KEY_F1:
274 if (source == SOURCE_MODEM) {
275 if (modem_curmsr & MODEM_MSR_DCD)
276 modem_hangup();
277 } else if (source == SOURCE_WIFI) {
278 wifi_hangup();
279 }
280 break;
281 case KEY_F4:
282 if (source == SOURCE_MODEM) {
283 printf("\nHanging up modem...\n");
284 modem_hangup();
285 modem_powerdown();
286 printf("\nSwitching to WiFiStation...\n");
287 source = SOURCE_WIFI;
288 } else if (source == SOURCE_WIFI) {
289 printf("\nDisconnecting WiFiStation...\n");
290 wifi_hangup();
291 printf("\nSwitching to modem...\n");
292 source = SOURCE_MODEM;
293 }
294 setting_default_source = source;
295 settings_write();
296 break;
297 case KEY_MAIN_MENU:
298 /* send escape */
299 obuf[obuf_pos++] = ESC;
300 break;
301 case KEY_EMAIL:
302 reboot();
303 break;
304 case KEY_PAGE_UP:
305 obuf[obuf_pos++] = ESC;
306 obuf[obuf_pos++] = '[';
307 obuf[obuf_pos++] = '5';
308 obuf[obuf_pos++] = '~';
309 break;
310 case KEY_PAGE_DOWN:
311 obuf[obuf_pos++] = ESC;
312 obuf[obuf_pos++] = '[';
313 obuf[obuf_pos++] = '6';
314 obuf[obuf_pos++] = '~';
315 break;
316 case KEY_UP:
317 obuf[obuf_pos++] = ESC;
318 obuf[obuf_pos++] = '[';
319 obuf[obuf_pos++] = 'A';
320 break;
321 case KEY_DOWN:
322 obuf[obuf_pos++] = ESC;
323 obuf[obuf_pos++] = '[';
324 obuf[obuf_pos++] = 'B';
325 break;
326 case KEY_LEFT:
327 obuf[obuf_pos++] = ESC;
328 obuf[obuf_pos++] = '[';
329 obuf[obuf_pos++] = 'D';
330 break;
331 case KEY_RIGHT:
332 obuf[obuf_pos++] = ESC;
333 obuf[obuf_pos++] = '[';
334 obuf[obuf_pos++] = 'C';
335 break;
336 case KEY_SIZE:
337 redraw_screen();
338 update_f1();
339 break;
340 default:
341 if (b >= META_KEY_BEGIN)
342 return 0;
343
344 if (b == '\n')
345 b = '\r';
346
347 obuf[obuf_pos++] = b;
348 }
349
350 return b;
351}
352
353void
354process_input(unsigned char b)
355{
356 if (in_csi) {
357 if (csibuflen >= sizeof(csibuf) - 1) {
358 /* going to overflow, dump */
359 parseCSI();
360 in_csi = 0;
361 esc = 0;
362 csibuf[0] = '\0';
363 csibuflen = 0;
364 }
365
366 if (b == ESC) {
367 /* esc, maybe new csi, dump previous */
368 parseCSI();
369 in_csi = 0;
370 esc = 1;
371 csibuf[0] = '\0';
372 csibuflen = 0;
373 } else {
374 csibuf[csibuflen] = b;
375 csibuflen++;
376 parseCSI();
377 }
378
379 return;
380 }
381
382 switch (b) {
383 case 7: /* visual bell, ping 'new mail' light */
384 new_mail(1);
385 delay(150);
386 new_mail(0);
387 break;
388 case 9: /* tab */
389 while ((cursorx + 1) % 8 != 0)
390 putchar(' ');
391 break;
392 case 26: /* ^Z end of ansi */
393 break;
394 case ESC: /* esc */
395 if (esc)
396 /* our previous esc is literal */
397 putchar(b);
398 esc = 1;
399 break;
400 case 91: /* [ */
401 if (esc) {
402 esc = 0;
403 in_csi = 1;
404 break;
405 }
406 /* fall through */
407 default:
408 putchar(b);
409 }
410}
411
412void
413obuf_flush(void)
414{
415 int b;
416
417 while (obuf_sent_pos != obuf_pos) {
418 switch (source) {
419 case SOURCE_MODEM:
420 if (modem_lsr() & (1 << 5))
421 /* Transmitter Holding Register Empty */
422 modem_write(obuf[obuf_sent_pos++]);
423 break;
424 case SOURCE_LPT:
425 lptsend(obuf[obuf_sent_pos++]);
426 break;
427 case SOURCE_ECHO:
428 putchar(obuf[obuf_sent_pos++]);
429 break;
430 case SOURCE_WIFI:
431 if (wifi_write(obuf[obuf_sent_pos]) == -1) {
432 if ((b = wifi_read()) != -1)
433 process_input(b & 0xff);
434 } else
435 obuf_sent_pos++;
436 break;
437 }
438 }
439}
440
441void
442wifi_hangup(void)
443{
444 obuf[obuf_pos++] = '+';
445 obuf[obuf_pos++] = '+';
446 obuf[obuf_pos++] = '+';
447 obuf_flush();
448 delay(800);
449 obuf[obuf_pos++] = 'A';
450 obuf[obuf_pos++] = 'T';
451 obuf[obuf_pos++] = 'H';
452 obuf[obuf_pos++] = '\r';
453 obuf_flush();
454}