give the project a proper name (and license)
[PanikSwitch.git] / PanikSwitch.ino
1 /*
2  * PanikSwitch
3  * Copyright (C) 2021-2021 Radio Panik
4  *
5  * This program is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU Affero General Public License as published
7  * by the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Affero General Public License for more details.
14  *
15  * You should have received a copy of the GNU Affero General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #define SERIAL_DEBUG
20 #define ENABLE_UDP
21
22
23 // define network constants
24 #define MAC_DAVID { 0x90, 0xA2, 0xDA, 0x0D, 0xF4, 0x63 }
25 #define MAC_PANIK { 0x90, 0xA2, 0xDA, 0x00, 0x9A, 0x94 }
26 #define MAC_PANIK2 { 0x90, 0xA2, 0xDA, 0x0E, 0xF3, 0x77 }
27 #define MAC_ADDRESS MAC_PANIK2
28
29 // include Arduino libraries
30 #include <Wire.h>
31 #include <SPI.h>
32 #include <SD.h>
33 #include <Ethernet.h>
34 #ifdef ENABLE_UDP
35 #include <EthernetUdp.h>
36 #endif
37
38 // include third party libraries
39 #include "WebServer.h"
40
41 // define SD and Ethernet select ports
42 #define  SD_SELECT            4
43 #define  ETHERNET_SELECT     10
44
45 // define machine variables
46 #define DEBOUNCE_TRESHOLD     10   // 10 milliseconds
47 #define ABORT_TRESHOLD     3000L   // 3 seconds
48 #define PULSE_TRESHOLD        50   // 50 milliseconds
49 #define BLINK_TRESHOLD       100   // 100 milliseconds
50
51 // output pins
52 #define RELAY_BUTTON_LEDS        5   // Button LED indicator
53 #define RELAY_GREEN_LEDS         6   // ``Non-Stop`` LED indicator
54 #define RELAY_RED_LEDS           8   // ``Studio 1`` LED indicator
55 #define RELAY_YELLOW_LEDS        7   // ``Studio 2`` LED indicator
56
57 #define RELAY_NONSTOP_OR_STUD    4   // Relay : Non-stop or Studio
58 #define RELAY_STUD1_OR_STUD2     9   // Relay : Studio 1 or Studio 2
59
60 // input pins
61 #define BUTTON1                  2   // ``Select`` button
62 #define BUTTON2                  3   // ``Confirm`` button
63 #define NONSTOP_VIA_STUD1       14   // is nonstop coming out of studio 1
64 #define NONSTOP_VIA_STUD2       15   // is nonstop coming out of studio 2
65
66 /* status */
67 #define NONSTOP 0
68 #define STUDIO1 1
69 #define STUDIO2 2
70
71
72 /* relay positions */
73 typedef enum { RELAY_STATE_OPEN, RELAY_STATE_CLOSED } relayState_t;
74
75
76 /* define an array with led pins,
77    indexes in that array correspond to the values of possible selections
78    i.e.: RELAY_GREEN_LEDS: nonstop, RELAY_RED_LEDS: studio1, RELAY_YELLOW_LEDS: studio2
79 */
80 const int ledsArray[] = { RELAY_GREEN_LEDS, RELAY_RED_LEDS, RELAY_YELLOW_LEDS };
81
82
83 /* activeSelection is the variable that holds the active output of the switch
84    it is declared as an attribute that keeps its value between arduino resets
85 */
86 int activeSelection = NONSTOP;
87
88 // variables and timers for blinking selection (selected but not confirmed)
89 int blinkingSelection = NONSTOP;
90 bool blinkingLedState = RELAY_STATE_CLOSED;
91 unsigned long blinkingStartTime = 0, blinkingAbortTime = 0;
92
93
94 // declaration of possible button states as enum type
95 typedef enum { nochange, pressed, released } buttonEvent_t;
96
97 // button event timers
98 unsigned long button1LastEventTime = 0, button2LastEventTime = 0;
99
100 // variables holding the state of each button
101 // these are used during the debouncing process
102 uint8_t button1State = LOW, button2State = LOW;
103
104 // variables holding the non-stop status from studios 1 & 2
105 bool nonstop_via_stud1, nonstop_via_stud2;
106
107 // declare functions
108 buttonEvent_t debounce( const uint8_t buttonPin,
109                         uint8_t *buttonState,
110                         unsigned long *buttonLastEventTime );
111
112 static uint8_t mac[] = MAC_ADDRESS;
113 #define PREFIX ""
114
115 // instanciate web server
116 WebServer webserver(PREFIX, 80);
117
118 #ifdef ENABLE_UDP
119 // and EthernetUDP instance to send notifications
120 EthernetUDP Udp;
121 IPAddress udp_remote_ip(192, 168, 17, 224);
122 #endif
123
124
125 #define NAMELEN 4
126 #define VALUELEN 4
127
128 typedef enum responseStatus { NO_POST, POST_OK, POST_ERROR };
129
130
131 inline void notify_udp()  // notify over UDP
132 {
133 #ifdef ENABLE_UDP
134   char str_selection[20];
135   snprintf(str_selection, 19, "{\"active\": %d}", activeSelection);
136   #ifdef SERIAL_DEBUG
137   Serial.println(F("Sending UDP... "));
138   Serial.println(str_selection);
139   #endif
140   Udp.beginPacket(udp_remote_ip, 1312);
141   Udp.write(str_selection);
142   Udp.endPacket();
143 #endif
144 }
145
146 // web resource
147 void webCmd(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete)
148 {
149
150   server.httpSuccess("application/json");
151
152   if (type == WebServer::HEAD)
153     return;
154
155   responseStatus response_status = NO_POST;
156
157   if (type == WebServer::POST)
158   {
159     char name[NAMELEN];
160     int  name_len;
161     char value[VALUELEN];
162     int value_len;
163
164     response_status = POST_ERROR;
165
166     while (server.readPOSTparam(name, NAMELEN, value, VALUELEN))
167     {
168       if (strcmp(name, "s") == 0) {
169         digitalWrite(ledsArray[activeSelection], RELAY_STATE_CLOSED);
170         blinkingSelection = atoi(value);
171         activeSelection = atoi(value);
172         response_status = POST_OK;
173
174         notify_udp();
175       }
176     }
177   }
178
179   server.println(F("{"));
180
181   server.print(" \"response\": ");
182   server.print(response_status);
183
184   server.print(",\n \"active\": ");
185   server.print(activeSelection);
186
187   server.print(",\n \"nonstop-via-stud1\": ");
188   server.print(nonstop_via_stud1);
189   server.print(",\n \"nonstop-via-stud2\": ");
190   server.println(nonstop_via_stud2);
191
192   server.println(F("}"));
193 }
194
195
196 // setup function
197 void setup()
198 {
199   blinkingSelection = activeSelection = NONSTOP;
200
201   // open serial communication for debugging
202   Serial.begin(9600);
203   Serial.println(F("Startup"));
204
205   // disable SD and Ethernet ports before setup
206   pinMode(SD_SELECT, OUTPUT);
207   digitalWrite(SD_SELECT, HIGH);                  // disable SD card
208   pinMode(ETHERNET_SELECT, OUTPUT);
209   digitalWrite(ETHERNET_SELECT, HIGH);           // disable Ethernet
210
211   // start SPI (because Ethernet shield needs it)
212   SPI.begin();                                          // start SPI
213
214   // start Ethernet
215   if (!(Ethernet.begin(mac) == 0))                    // start network
216     Serial.println(Ethernet.localIP());
217   else
218   {
219     #ifdef SERIAL_DEBUG
220     Serial.println(F("Network Error"));
221     #endif
222     while (1) ;
223   }
224   #ifdef ENABLE_UDP
225   if (! Udp.begin(1312)) {
226     #ifdef SERIAL_DEBUG
227     Serial.println(F("Failed to initiate UDP"));
228     #endif
229   }
230   #endif
231
232   // set mode for used pins
233   pinMode(RELAY_RED_LEDS, OUTPUT);
234   digitalWrite(RELAY_RED_LEDS, RELAY_STATE_CLOSED);
235   pinMode(RELAY_YELLOW_LEDS, OUTPUT);
236   digitalWrite(RELAY_YELLOW_LEDS, RELAY_STATE_CLOSED);
237   pinMode(RELAY_GREEN_LEDS, OUTPUT);
238   digitalWrite(RELAY_GREEN_LEDS, RELAY_STATE_CLOSED);
239   pinMode(RELAY_BUTTON_LEDS, OUTPUT);
240   digitalWrite(RELAY_BUTTON_LEDS, RELAY_STATE_CLOSED);
241
242   pinMode(RELAY_NONSTOP_OR_STUD, OUTPUT);
243   digitalWrite(RELAY_NONSTOP_OR_STUD, RELAY_STATE_CLOSED);
244   pinMode(RELAY_STUD1_OR_STUD2, OUTPUT);
245   digitalWrite(RELAY_STUD1_OR_STUD2, RELAY_STATE_CLOSED);
246
247   pinMode(BUTTON1, INPUT);
248   pinMode(BUTTON2, INPUT);
249   pinMode(NONSTOP_VIA_STUD1, INPUT);
250   pinMode(NONSTOP_VIA_STUD2, INPUT);
251
252   // configure web server pages
253   webserver.setDefaultCommand(&webCmd);
254
255   // start web server
256   webserver.begin();
257 }
258
259
260 // loop function
261 void loop()
262 {
263   // update variables indicating if non-stop is selected in studios 1&2
264   nonstop_via_stud1 = digitalRead(NONSTOP_VIA_STUD1);
265   if (nonstop_via_stud1 == 0)
266     nonstop_via_stud1 = 1;
267   else
268     nonstop_via_stud1 = 0;
269   nonstop_via_stud2 = digitalRead(NONSTOP_VIA_STUD2);
270   if (nonstop_via_stud2 == 0)
271     nonstop_via_stud2 = 1;
272   else
273     nonstop_via_stud2 = 0;
274
275   // check if we have a HTTP request (and respond)
276   webserver.processConnection();
277
278   // handle button 1 (select button)
279   switch ( debounce(BUTTON1, &button1State, &button1LastEventTime) )
280   {
281     case nochange:
282       break;
283     case pressed:
284       #ifdef SERIAL_DEBUG
285       Serial.println(F("Button 1 pressed"));
286       #endif
287       digitalWrite(ledsArray[blinkingSelection], RELAY_STATE_CLOSED);
288       blinkingSelection++;
289       if (blinkingSelection > STUDIO2) {
290         blinkingSelection = NONSTOP;
291       }
292       blinkingStartTime = millis(),
293       blinkingAbortTime = millis();
294       blinkingLedState = RELAY_STATE_OPEN;
295       break;
296     case released:
297       #ifdef SERIAL_DEBUG
298       Serial.println(F("Button 1 released"));
299       #endif
300       break;
301   }
302
303   // handle button 2 (confirm button)
304   switch ( debounce(BUTTON2, &button2State, &button2LastEventTime) )
305   {
306     case nochange:
307       break;
308     case pressed:
309       #ifdef SERIAL_DEBUG
310       Serial.println(F("Button 2 pressed"));
311       #endif
312       blinkingAbortTime = 0;        // disable blinking auto abort
313       break;
314     case released:
315       #ifdef SERIAL_DEBUG
316       Serial.println(F("Button 2 released"));
317       #endif
318       if (activeSelection != blinkingSelection)
319       {
320         digitalWrite(ledsArray[activeSelection], RELAY_STATE_CLOSED);
321         activeSelection = blinkingSelection;  // relay states must be changed now
322         #ifdef SERIAL_DEBUG
323         Serial.print(F("Active Selection: "));
324         Serial.println(activeSelection);
325         #endif
326         notify_udp();
327         break;
328       }
329   }
330
331   // handle auto abort for blinking led (if any)
332   if ( (blinkingAbortTime != 0) &&
333        ((millis() - blinkingAbortTime) >= ABORT_TRESHOLD) ) {
334     digitalWrite(ledsArray[blinkingSelection], RELAY_STATE_CLOSED);
335     blinkingAbortTime = 0;
336     blinkingSelection = activeSelection;
337   }
338
339   // handle blinking led (if selected is not current state)
340   if (activeSelection != blinkingSelection) {
341     if ((millis() - blinkingStartTime) >= BLINK_TRESHOLD) {
342       digitalWrite(ledsArray[blinkingSelection], blinkingLedState);
343       digitalWrite(RELAY_BUTTON_LEDS, !blinkingLedState);
344       blinkingStartTime = millis();
345       blinkingLedState = !blinkingLedState;
346     }
347   }
348   else {
349     digitalWrite(RELAY_BUTTON_LEDS, RELAY_STATE_CLOSED);
350   }
351
352   digitalWrite(ledsArray[activeSelection], RELAY_STATE_OPEN);
353
354   // update audio channel relays
355   if (activeSelection == NONSTOP)
356   {
357     digitalWrite(RELAY_NONSTOP_OR_STUD, RELAY_STATE_CLOSED);
358     digitalWrite(RELAY_STUD1_OR_STUD2, RELAY_STATE_CLOSED);
359   }
360   else if (activeSelection == STUDIO1)
361   {
362     digitalWrite(RELAY_NONSTOP_OR_STUD, RELAY_STATE_OPEN);
363     digitalWrite(RELAY_STUD1_OR_STUD2, RELAY_STATE_CLOSED);
364   }
365   else if (activeSelection == STUDIO2)
366   {
367     digitalWrite(RELAY_NONSTOP_OR_STUD, RELAY_STATE_OPEN);
368     digitalWrite(RELAY_STUD1_OR_STUD2, RELAY_STATE_OPEN);
369   }
370 }
371
372 buttonEvent_t debounce( const uint8_t buttonPin,
373                         uint8_t *buttonState,
374                         unsigned long *buttonLastEventTime )
375 {
376   uint8_t buttonLastState = *buttonState;
377   uint8_t buttonReading = digitalRead(buttonPin);
378
379   if ( (*buttonLastEventTime == 0) &&
380        (*buttonState != buttonReading) ) {
381     // something happened, start the debouncing process
382     *buttonLastEventTime = millis();
383   }
384
385   if (*buttonLastEventTime != 0) {
386     long buttonEventTimer = millis() - *buttonLastEventTime;
387
388     if ( (buttonEventTimer < DEBOUNCE_TRESHOLD) &&
389          (*buttonState == buttonReading) ) {
390       // noise or bouncing, abort
391       *buttonLastEventTime = 0;
392     }
393
394     if ( (buttonEventTimer >= DEBOUNCE_TRESHOLD) &&
395          (buttonReading != *buttonState) ) {
396       // new button state was maintained
397       *buttonLastEventTime = 0;
398       *buttonState = buttonReading;
399     }
400   }
401
402   // debouncing finished, return button state
403   if (*buttonLastEventTime == 0) {
404     // comparing buttonState and buttonLastState tells whether the
405     // button is pressed or released
406     if ( (*buttonState == HIGH) &&
407          (buttonLastState == LOW) ) {
408       // button just pressed
409       buttonLastState = *buttonState;
410       return pressed;
411     }
412     if ( (*buttonState == LOW) &&
413          (buttonLastState == HIGH) ) {
414       // button just released
415       buttonLastState = *buttonState;
416       return released;
417     }
418   }
419   return nochange;
420 }