3 * Copyright (C) 2021-2021 Radio Panik
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.
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.
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/>.
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
29 // include Arduino libraries
34 #include <EthernetUdp.h>
37 // include third party libraries
38 #include "WebServer.h"
40 // define SD and Ethernet select ports
42 #define ETHERNET_SELECT 10
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
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
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
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
72 typedef enum { RELAY_STATE_OPEN, RELAY_STATE_CLOSED } relayState_t;
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
79 const int ledsArray[] = { RELAY_GREEN_LEDS, RELAY_RED_LEDS, RELAY_YELLOW_LEDS };
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
85 int activeSelection = NONSTOP;
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;
93 // declaration of possible button states as enum type
94 typedef enum { nochange, pressed, released } buttonEvent_t;
96 // button event timers
97 unsigned long button1LastEventTime = 0, button2LastEventTime = 0;
99 // variables holding the state of each button
100 // these are used during the debouncing process
101 uint8_t button1State = LOW, button2State = LOW;
103 // variables holding the non-stop status from studios 1 & 2
104 bool nonstop_via_stud1, nonstop_via_stud2;
107 buttonEvent_t debounce( const uint8_t buttonPin,
108 uint8_t *buttonState,
109 unsigned long *buttonLastEventTime );
111 static uint8_t mac[] = MAC_ADDRESS;
114 // instanciate web server
115 WebServer webserver(PREFIX, 80);
118 // and EthernetUDP instance to send notifications
120 IPAddress udp_remote_ip(192, 168, 17, 246);
127 typedef enum responseStatus { NO_POST, POST_OK, POST_ERROR };
130 inline void notify_udp() // notify over UDP
133 char str_selection[20];
134 snprintf(str_selection, 19, "{\"active\": %d}", activeSelection);
136 Serial.println(F("Sending UDP... "));
137 Serial.println(str_selection);
139 Udp.beginPacket(udp_remote_ip, 1312);
140 Udp.write(str_selection);
146 void webCmd(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete)
149 server.httpSuccess("application/json");
151 if (type == WebServer::HEAD)
154 responseStatus response_status = NO_POST;
156 if (type == WebServer::POST)
160 char value[VALUELEN];
163 response_status = POST_ERROR;
165 while (server.readPOSTparam(name, NAMELEN, value, VALUELEN))
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;
178 server.println(F("{"));
180 server.print(" \"response\": ");
181 server.print(response_status);
183 server.print(",\n \"active\": ");
184 server.print(activeSelection);
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);
191 server.println(F("}"));
198 blinkingSelection = activeSelection = NONSTOP;
200 // open serial communication for debugging
202 Serial.println(F("Startup"));
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
210 // start SPI (because Ethernet shield needs it)
211 SPI.begin(); // start SPI
214 if (!(Ethernet.begin(mac) == 0)) // start network
215 Serial.println(Ethernet.localIP());
219 Serial.println(F("Network Error"));
224 if (! Udp.begin(1312)) {
226 Serial.println(F("Failed to initiate UDP"));
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);
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);
246 pinMode(BUTTON1, INPUT);
247 pinMode(BUTTON2, INPUT);
248 pinMode(NONSTOP_VIA_STUD1, INPUT);
249 pinMode(NONSTOP_VIA_STUD2, INPUT);
251 // configure web server pages
252 webserver.setDefaultCommand(&webCmd);
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;
267 nonstop_via_stud1 = 0;
268 nonstop_via_stud2 = digitalRead(NONSTOP_VIA_STUD2);
269 if (nonstop_via_stud2 == 0)
270 nonstop_via_stud2 = 1;
272 nonstop_via_stud2 = 0;
274 // check if we have a HTTP request (and respond)
275 webserver.processConnection();
277 // handle button 1 (select button)
278 switch ( debounce(BUTTON1, &button1State, &button1LastEventTime) )
284 Serial.println(F("Button 1 pressed"));
286 digitalWrite(ledsArray[blinkingSelection], RELAY_STATE_CLOSED);
288 if (blinkingSelection > STUDIO2) {
289 blinkingSelection = NONSTOP;
291 blinkingStartTime = millis(),
292 blinkingAbortTime = millis();
293 blinkingLedState = RELAY_STATE_OPEN;
297 Serial.println(F("Button 1 released"));
302 // handle button 2 (confirm button)
303 switch ( debounce(BUTTON2, &button2State, &button2LastEventTime) )
309 Serial.println(F("Button 2 pressed"));
311 blinkingAbortTime = 0; // disable blinking auto abort
315 Serial.println(F("Button 2 released"));
317 if (activeSelection != blinkingSelection)
319 digitalWrite(ledsArray[activeSelection], RELAY_STATE_CLOSED);
320 activeSelection = blinkingSelection; // relay states must be changed now
322 Serial.print(F("Active Selection: "));
323 Serial.println(activeSelection);
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;
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;
348 digitalWrite(RELAY_BUTTON_LEDS, RELAY_STATE_CLOSED);
351 digitalWrite(ledsArray[activeSelection], RELAY_STATE_OPEN);
353 // update audio channel relays
354 if (activeSelection == NONSTOP)
356 digitalWrite(RELAY_NONSTOP_OR_STUD, RELAY_STATE_CLOSED);
357 digitalWrite(RELAY_STUD1_OR_STUD2, RELAY_STATE_CLOSED);
359 else if (activeSelection == STUDIO1)
361 digitalWrite(RELAY_NONSTOP_OR_STUD, RELAY_STATE_OPEN);
362 digitalWrite(RELAY_STUD1_OR_STUD2, RELAY_STATE_CLOSED);
364 else if (activeSelection == STUDIO2)
366 digitalWrite(RELAY_NONSTOP_OR_STUD, RELAY_STATE_OPEN);
367 digitalWrite(RELAY_STUD1_OR_STUD2, RELAY_STATE_OPEN);
371 buttonEvent_t debounce( const uint8_t buttonPin,
372 uint8_t *buttonState,
373 unsigned long *buttonLastEventTime )
375 uint8_t buttonLastState = *buttonState;
376 uint8_t buttonReading = digitalRead(buttonPin);
378 if ( (*buttonLastEventTime == 0) &&
379 (*buttonState != buttonReading) ) {
380 // something happened, start the debouncing process
381 *buttonLastEventTime = millis();
384 if (*buttonLastEventTime != 0) {
385 long buttonEventTimer = millis() - *buttonLastEventTime;
387 if ( (buttonEventTimer < DEBOUNCE_TRESHOLD) &&
388 (*buttonState == buttonReading) ) {
389 // noise or bouncing, abort
390 *buttonLastEventTime = 0;
393 if ( (buttonEventTimer >= DEBOUNCE_TRESHOLD) &&
394 (buttonReading != *buttonState) ) {
395 // new button state was maintained
396 *buttonLastEventTime = 0;
397 *buttonState = buttonReading;
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;
411 if ( (*buttonState == LOW) &&
412 (buttonLastState == HIGH) ) {
413 // button just released
414 buttonLastState = *buttonState;