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