-
Notifications
You must be signed in to change notification settings - Fork 1
/
rusp_firmware.ino
327 lines (288 loc) · 8 KB
/
rusp_firmware.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
#include <EnableInterrupt.h>
#include "pins.h"
#include "lara.h"
#include "oled.h"
#include "sd.h"
// length of dial buffer (max 255 right now since dial_idx is unsigned char)
#define DIAL_BUF_LEN 30
// subtracted from pulse count to get number dialed
#define PULSE_FUDGE 1
// ms to debounce rotary switch by
#define ROTARY_DEBOUNCE_MS 30
// ms after no pulses have been received when we decide the number is done
#define PULSES_DONE_MS 500
// https://en.wikipedia.org/wiki/X_macro
#define FOR_CONTACTS(DO) \
DO('1', CONTACT1) \
DO('2', CONTACT2) \
DO('3', CONTACT3) \
DO('4', CONTACT4) \
DO('5', CONTACT5) \
DO('6', CONTACT6) \
DO('7', CONTACT7) \
DO('8', CONTACT8) \
DO('9', CONTACT9) \
DO('0', CONTACT0)
// ugly global variables
// should we be counting pulses?
bool pulsing = false;
// how many pulses have we counted since we started counting?
char pulses = 0;
// what was the time of the last pulse?
unsigned long pulse_last = 0;
// buffer to store the phone number (or other thing) being dialed
char dial_buf[DIAL_BUF_LEN];
// index of the dial string we're on
unsigned char dial_idx = 0;
// whether or not the hook was recently pressed
// TODO: i tried just putting all the hook-handling code in the ISR but somehow
// the chip got mad at me
bool hook = false;
// for debouncing
unsigned long hook_last = 0;
// are we ringing the bell
bool ringing = false;
// when did ringing start
unsigned long ringing_start = 0;
// one of SW_ALT, SW_LOCAL, SW_NONLOCAL (or 0 for uninitialized)
int prev_mode = 0;
// Interrupt Service Routines for the rotary dial's "pulse" switch, which is a
// tied-to-GND normally-open limit switch that rolls against a cam
void isr_rotary()
{
// only count if the hall triggered
if (!pulsing) return;
// debounce
unsigned long pulse_cur = millis();
if (pulse_cur - pulse_last > ROTARY_DEBOUNCE_MS) {
digitalWrite(LED_STAT, HIGH);
digitalWrite(LED5A, HIGH);
digitalWrite(LED5R, HIGH);
pulse_last = pulse_cur;
pulses += 1;
// TODO: we really should not be stopping everything for the LED
// flashes lol
delay(10);
digitalWrite(LED_STAT, LOW);
digitalWrite(LED5A, LOW);
digitalWrite(LED5R, LOW);
}
}
void isr_hall()
{
digitalWrite(LED_FILAMENT, HIGH);
pulsing = true;
pulses = 0;
delay(10); // TODO: see above
digitalWrite(LED_FILAMENT, LOW);
}
void isr_hook()
{
unsigned long hook_cur = millis();
if (hook_cur - hook_last > 30) {
hook_last = hook_cur;
hook = true;
}
}
void isr_clear()
{
// if in local mode, refuse to delete into the prepend
if (prev_mode == SW_LOCAL && dial_idx <= strlen(sd_PREPEND()))
return;
// Decrement the dial index without underflow.
if (dial_idx > 0) dial_idx -= 1;
else dial_idx = 0;
// Null-terminate the dial buffer.
dial_buf[dial_idx] = 0;
// Print the new dial buffer.
oled_print(dial_buf, 0, 30);
}
char pulse2ascii(char pulse_count)
{
pulse_count -= PULSE_FUDGE;
if (pulse_count == 10) pulse_count = 0;
if ((unsigned char)pulse_count < 10) return pulse_count + 0x30;
else return '?';
}
void setup()
{
Serial.begin(115200);
pinMode(LED_STAT, OUTPUT);
pinMode(LED_FILAMENT, OUTPUT);
pinMode(LED_BELL, OUTPUT);
pinMode(LED1A, OUTPUT);
pinMode(LED2A, OUTPUT);
pinMode(LED2R, OUTPUT);
pinMode(LED3A, OUTPUT);
pinMode(LED3R, OUTPUT);
pinMode(LED4A, OUTPUT);
pinMode(LED4R, OUTPUT);
pinMode(LED5A, OUTPUT);
pinMode(LED5R, OUTPUT);
pinMode(RINGER_P, OUTPUT);
pinMode(RINGER_N, OUTPUT);
pinMode(RELAY_OFF, OUTPUT);
pinMode(LL_OE, OUTPUT);
pinMode(EN_3V3, OUTPUT);
pinMode(EN_12V, OUTPUT);
pinMode(EN_OUTAMP, OUTPUT);
pinMode(CELL_ON, OUTPUT);
pinMode(CHIPSELECT, OUTPUT);
pinMode(SW_ROTARY, INPUT_PULLUP);
pinMode(SW_C, INPUT_PULLUP);
pinMode(SW_HOOK, INPUT_PULLUP);
pinMode(SW_ALPHA, INPUT_PULLUP);
pinMode(SW_BETA, INPUT_PULLUP);
pinMode(SW_LAMBDA, INPUT_PULLUP);
pinMode(SW_FN, INPUT_PULLUP);
pinMode(SW_LOCAL, INPUT_PULLUP);
pinMode(SW_ALT, INPUT_PULLUP);
pinMode(SW_NONLOCAL, INPUT_PULLUP);
pinMode(SW_HALL, INPUT_PULLUP);
pinMode(OFFSIGNAL, INPUT_PULLUP);
pinMode(CHG_STAT, INPUT);
digitalWrite(LED_BELL, HIGH);
// call ISR on rotary switch falling edge (internally pulled up)
enableInterrupt(SW_ROTARY, isr_rotary, FALLING);
enableInterrupt(SW_HALL, isr_hall, FALLING);
enableInterrupt(SW_HOOK, isr_hook, FALLING);
enableInterrupt(SW_C, isr_clear, FALLING);
digitalWrite(EN_12V, HIGH);
digitalWrite(EN_3V3, HIGH);
digitalWrite(LED_BELL, HIGH);
digitalWrite(LL_OE, HIGH);
// this needs to be before oled_init because it sets the SPI speed to
// very low
sd_init(&Serial);
oled_init();
oled_print("STARTING", 0, 40);
Serial.println("hello! turning LARA on");
digitalWrite(LED_STAT, HIGH);
lara_on(&Serial1, &Serial, 10000);
digitalWrite(LED_STAT, LOW);
oled_clear();
digitalWrite(LED_BELL, LOW);
}
void loop()
{
unsigned long t = millis();
if (digitalRead(OFFSIGNAL) == LOW) shutdown();
// TODO: this is for debug purposes
if (digitalRead(SW_ALPHA) == LOW) ringing = true;
// figure out which throw the 1p3t switch is on
int cur_mode;
if (digitalRead(SW_ALT) == LOW) cur_mode = SW_ALT;
else if (digitalRead(SW_LOCAL) == LOW) cur_mode = SW_LOCAL;
else cur_mode = SW_NONLOCAL;
if (cur_mode != prev_mode) {
// clear dial buffer if switched to new mode
dial_idx = 0;
dial_buf[dial_idx] = 0;
prev_mode = cur_mode;
oled_clear();
}
lara_unsolicited(&ringing);
if (ringing) {
oled_print("INCOMING CALL", 0, 20);
if (t - ringing_start < 2000) {
if (t & 0b00100000) {
digitalWrite(RINGER_P, HIGH);
digitalWrite(RINGER_N, LOW);
digitalWrite(LED_BELL, HIGH);
} else {
digitalWrite(RINGER_P, LOW);
digitalWrite(RINGER_N, HIGH);
digitalWrite(LED_BELL, LOW);
}
} else {
ringing = false;
}
} else {
digitalWrite(RINGER_P, LOW);
digitalWrite(RINGER_N, LOW);
digitalWrite(LED_BELL, LOW);
ringing_start = t;
}
// if it's been a while since the last pulse we counted, assume that
// number is done being entered
if (pulses && t - pulse_last > PULSES_DONE_MS) {
disableInterrupt(SW_ROTARY);
disableInterrupt(SW_HALL);
// alt (contacts) mode
if (digitalRead(SW_ALT) == LOW) {
// Get the number the user entered.
char contact_idx = pulse2ascii(pulses);
// Match the number to the contact list.
switch (contact_idx) {
#define CASE_CONTACT(c, f) \
case c: \
strcpy(dial_buf, sd_##f()); \
oled_print(dial_buf, 0, 30); \
dial_idx = strlen(sd_##f()); \
break;
FOR_CONTACTS(CASE_CONTACT)
default:
oled_print("NOT FOUND", 0, 30);
}
// Check whether the dial index is below maximum capacity.
} else if (dial_idx < DIAL_BUF_LEN - 1) {
// first digit in local (prepend) mode
if (digitalRead(SW_LOCAL) == LOW
&& dial_idx < strlen(sd_PREPEND())) {
strcpy(dial_buf, sd_PREPEND());
oled_print(dial_buf, 0, 30);
dial_idx = strlen(sd_PREPEND());
}
// Get the number the user entered.
dial_buf[dial_idx] = pulse2ascii(pulses);
// Increment the dial index, and set the next value to
// the null byte.
dial_buf[++dial_idx] = 0;
// Print the dial buffer.
oled_print(dial_buf, 0, 30);
Serial.print("entered: ");
Serial.println(dial_buf);
}
// Reset the rotary dial variables.
pulsing = false;
pulses = 0;
enableInterrupt(SW_ROTARY, isr_rotary, FALLING);
enableInterrupt(SW_HALL, isr_hall, FALLING);
}
if (hook) {
Serial.println("hook pressed");
lara_activity stat = lara_status();
switch (stat) {
case LARA_RINGING:
oled_print("ANSWERING", 0, 30);
Serial.println("answering");
lara_answer();
break;
case LARA_CALLING:
oled_print("HANGING UP", 0, 30);
Serial.println("hanging up");
lara_hangup();
break;
case LARA_READY:
oled_print("DIALING", 0, 30);
Serial.println("dialing");
lara_dial(dial_buf);
break;
default:
Serial.print("don't know how to handle status ");
Serial.write(stat);
Serial.println();
}
hook = false;
}
}
void shutdown()
{
oled_print("GOODBYE", 0, 30);
Serial.println("shutdown called; waiting for cell powerdown");
lara_off(5000);
Serial.end();
digitalWrite(EN_12V, LOW);
digitalWrite(RELAY_OFF, HIGH);
// POWER KILLED
}