]> git.0d.be Git - PanikSwitch.git/blobdiff - PanikSwitch.ino
give the project a proper name (and license)
[PanikSwitch.git] / PanikSwitch.ino
diff --git a/PanikSwitch.ino b/PanikSwitch.ino
new file mode 100644 (file)
index 0000000..8c92cb1
--- /dev/null
@@ -0,0 +1,420 @@
+/*
+ * PanikSwitch
+ * Copyright (C) 2021-2021 Radio Panik
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define SERIAL_DEBUG
+#define ENABLE_UDP
+
+
+// define network constants
+#define MAC_DAVID { 0x90, 0xA2, 0xDA, 0x0D, 0xF4, 0x63 }
+#define MAC_PANIK { 0x90, 0xA2, 0xDA, 0x00, 0x9A, 0x94 }
+#define MAC_PANIK2 { 0x90, 0xA2, 0xDA, 0x0E, 0xF3, 0x77 }
+#define MAC_ADDRESS MAC_PANIK2
+
+// include Arduino libraries
+#include <Wire.h>
+#include <SPI.h>
+#include <SD.h>
+#include <Ethernet.h>
+#ifdef ENABLE_UDP
+#include <EthernetUdp.h>
+#endif
+
+// include third party libraries
+#include "WebServer.h"
+
+// define SD and Ethernet select ports
+#define  SD_SELECT            4
+#define  ETHERNET_SELECT     10
+
+// define machine variables
+#define DEBOUNCE_TRESHOLD     10   // 10 milliseconds
+#define ABORT_TRESHOLD     3000L   // 3 seconds
+#define PULSE_TRESHOLD        50   // 50 milliseconds
+#define BLINK_TRESHOLD       100   // 100 milliseconds
+
+// output pins
+#define RELAY_BUTTON_LEDS        5   // Button LED indicator
+#define RELAY_GREEN_LEDS         6   // ``Non-Stop`` LED indicator
+#define RELAY_RED_LEDS           8   // ``Studio 1`` LED indicator
+#define RELAY_YELLOW_LEDS        7   // ``Studio 2`` LED indicator
+
+#define RELAY_NONSTOP_OR_STUD    4   // Relay : Non-stop or Studio
+#define RELAY_STUD1_OR_STUD2     9   // Relay : Studio 1 or Studio 2
+
+// input pins
+#define BUTTON1                  2   // ``Select`` button
+#define BUTTON2                  3   // ``Confirm`` button
+#define NONSTOP_VIA_STUD1       14   // is nonstop coming out of studio 1
+#define NONSTOP_VIA_STUD2       15   // is nonstop coming out of studio 2
+
+/* status */
+#define NONSTOP 0
+#define STUDIO1 1
+#define STUDIO2 2
+
+
+/* relay positions */
+typedef enum { RELAY_STATE_OPEN, RELAY_STATE_CLOSED } relayState_t;
+
+
+/* define an array with led pins,
+   indexes in that array correspond to the values of possible selections
+   i.e.: RELAY_GREEN_LEDS: nonstop, RELAY_RED_LEDS: studio1, RELAY_YELLOW_LEDS: studio2
+*/
+const int ledsArray[] = { RELAY_GREEN_LEDS, RELAY_RED_LEDS, RELAY_YELLOW_LEDS };
+
+
+/* activeSelection is the variable that holds the active output of the switch
+   it is declared as an attribute that keeps its value between arduino resets
+*/
+int activeSelection = NONSTOP;
+
+// variables and timers for blinking selection (selected but not confirmed)
+int blinkingSelection = NONSTOP;
+bool blinkingLedState = RELAY_STATE_CLOSED;
+unsigned long blinkingStartTime = 0, blinkingAbortTime = 0;
+
+
+// declaration of possible button states as enum type
+typedef enum { nochange, pressed, released } buttonEvent_t;
+
+// button event timers
+unsigned long button1LastEventTime = 0, button2LastEventTime = 0;
+
+// variables holding the state of each button
+// these are used during the debouncing process
+uint8_t button1State = LOW, button2State = LOW;
+
+// variables holding the non-stop status from studios 1 & 2
+bool nonstop_via_stud1, nonstop_via_stud2;
+
+// declare functions
+buttonEvent_t debounce( const uint8_t buttonPin,
+                        uint8_t *buttonState,
+                        unsigned long *buttonLastEventTime );
+
+static uint8_t mac[] = MAC_ADDRESS;
+#define PREFIX ""
+
+// instanciate web server
+WebServer webserver(PREFIX, 80);
+
+#ifdef ENABLE_UDP
+// and EthernetUDP instance to send notifications
+EthernetUDP Udp;
+IPAddress udp_remote_ip(192, 168, 17, 224);
+#endif
+
+
+#define NAMELEN 4
+#define VALUELEN 4
+
+typedef enum responseStatus { NO_POST, POST_OK, POST_ERROR };
+
+
+inline void notify_udp()  // notify over UDP
+{
+#ifdef ENABLE_UDP
+  char str_selection[20];
+  snprintf(str_selection, 19, "{\"active\": %d}", activeSelection);
+  #ifdef SERIAL_DEBUG
+  Serial.println(F("Sending UDP... "));
+  Serial.println(str_selection);
+  #endif
+  Udp.beginPacket(udp_remote_ip, 1312);
+  Udp.write(str_selection);
+  Udp.endPacket();
+#endif
+}
+
+// web resource
+void webCmd(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete)
+{
+
+  server.httpSuccess("application/json");
+
+  if (type == WebServer::HEAD)
+    return;
+
+  responseStatus response_status = NO_POST;
+
+  if (type == WebServer::POST)
+  {
+    char name[NAMELEN];
+    int  name_len;
+    char value[VALUELEN];
+    int value_len;
+
+    response_status = POST_ERROR;
+
+    while (server.readPOSTparam(name, NAMELEN, value, VALUELEN))
+    {
+      if (strcmp(name, "s") == 0) {
+        digitalWrite(ledsArray[activeSelection], RELAY_STATE_CLOSED);
+        blinkingSelection = atoi(value);
+        activeSelection = atoi(value);
+        response_status = POST_OK;
+
+        notify_udp();
+      }
+    }
+  }
+
+  server.println(F("{"));
+
+  server.print(" \"response\": ");
+  server.print(response_status);
+
+  server.print(",\n \"active\": ");
+  server.print(activeSelection);
+
+  server.print(",\n \"nonstop-via-stud1\": ");
+  server.print(nonstop_via_stud1);
+  server.print(",\n \"nonstop-via-stud2\": ");
+  server.println(nonstop_via_stud2);
+
+  server.println(F("}"));
+}
+
+
+// setup function
+void setup()
+{
+  blinkingSelection = activeSelection = NONSTOP;
+
+  // open serial communication for debugging
+  Serial.begin(9600);
+  Serial.println(F("Startup"));
+
+  // disable SD and Ethernet ports before setup
+  pinMode(SD_SELECT, OUTPUT);
+  digitalWrite(SD_SELECT, HIGH);                  // disable SD card
+  pinMode(ETHERNET_SELECT, OUTPUT);
+  digitalWrite(ETHERNET_SELECT, HIGH);           // disable Ethernet
+
+  // start SPI (because Ethernet shield needs it)
+  SPI.begin();                                          // start SPI
+
+  // start Ethernet
+  if (!(Ethernet.begin(mac) == 0))                    // start network
+    Serial.println(Ethernet.localIP());
+  else
+  {
+    #ifdef SERIAL_DEBUG
+    Serial.println(F("Network Error"));
+    #endif
+    while (1) ;
+  }
+  #ifdef ENABLE_UDP
+  if (! Udp.begin(1312)) {
+    #ifdef SERIAL_DEBUG
+    Serial.println(F("Failed to initiate UDP"));
+    #endif
+  }
+  #endif
+
+  // set mode for used pins
+  pinMode(RELAY_RED_LEDS, OUTPUT);
+  digitalWrite(RELAY_RED_LEDS, RELAY_STATE_CLOSED);
+  pinMode(RELAY_YELLOW_LEDS, OUTPUT);
+  digitalWrite(RELAY_YELLOW_LEDS, RELAY_STATE_CLOSED);
+  pinMode(RELAY_GREEN_LEDS, OUTPUT);
+  digitalWrite(RELAY_GREEN_LEDS, RELAY_STATE_CLOSED);
+  pinMode(RELAY_BUTTON_LEDS, OUTPUT);
+  digitalWrite(RELAY_BUTTON_LEDS, RELAY_STATE_CLOSED);
+
+  pinMode(RELAY_NONSTOP_OR_STUD, OUTPUT);
+  digitalWrite(RELAY_NONSTOP_OR_STUD, RELAY_STATE_CLOSED);
+  pinMode(RELAY_STUD1_OR_STUD2, OUTPUT);
+  digitalWrite(RELAY_STUD1_OR_STUD2, RELAY_STATE_CLOSED);
+
+  pinMode(BUTTON1, INPUT);
+  pinMode(BUTTON2, INPUT);
+  pinMode(NONSTOP_VIA_STUD1, INPUT);
+  pinMode(NONSTOP_VIA_STUD2, INPUT);
+
+  // configure web server pages
+  webserver.setDefaultCommand(&webCmd);
+
+  // start web server
+  webserver.begin();
+}
+
+
+// loop function
+void loop()
+{
+  // update variables indicating if non-stop is selected in studios 1&2
+  nonstop_via_stud1 = digitalRead(NONSTOP_VIA_STUD1);
+  if (nonstop_via_stud1 == 0)
+    nonstop_via_stud1 = 1;
+  else
+    nonstop_via_stud1 = 0;
+  nonstop_via_stud2 = digitalRead(NONSTOP_VIA_STUD2);
+  if (nonstop_via_stud2 == 0)
+    nonstop_via_stud2 = 1;
+  else
+    nonstop_via_stud2 = 0;
+
+  // check if we have a HTTP request (and respond)
+  webserver.processConnection();
+
+  // handle button 1 (select button)
+  switch ( debounce(BUTTON1, &button1State, &button1LastEventTime) )
+  {
+    case nochange:
+      break;
+    case pressed:
+      #ifdef SERIAL_DEBUG
+      Serial.println(F("Button 1 pressed"));
+      #endif
+      digitalWrite(ledsArray[blinkingSelection], RELAY_STATE_CLOSED);
+      blinkingSelection++;
+      if (blinkingSelection > STUDIO2) {
+        blinkingSelection = NONSTOP;
+      }
+      blinkingStartTime = millis(),
+      blinkingAbortTime = millis();
+      blinkingLedState = RELAY_STATE_OPEN;
+      break;
+    case released:
+      #ifdef SERIAL_DEBUG
+      Serial.println(F("Button 1 released"));
+      #endif
+      break;
+  }
+
+  // handle button 2 (confirm button)
+  switch ( debounce(BUTTON2, &button2State, &button2LastEventTime) )
+  {
+    case nochange:
+      break;
+    case pressed:
+      #ifdef SERIAL_DEBUG
+      Serial.println(F("Button 2 pressed"));
+      #endif
+      blinkingAbortTime = 0;        // disable blinking auto abort
+      break;
+    case released:
+      #ifdef SERIAL_DEBUG
+      Serial.println(F("Button 2 released"));
+      #endif
+      if (activeSelection != blinkingSelection)
+      {
+        digitalWrite(ledsArray[activeSelection], RELAY_STATE_CLOSED);
+        activeSelection = blinkingSelection;  // relay states must be changed now
+        #ifdef SERIAL_DEBUG
+        Serial.print(F("Active Selection: "));
+        Serial.println(activeSelection);
+        #endif
+        notify_udp();
+        break;
+      }
+  }
+
+  // handle auto abort for blinking led (if any)
+  if ( (blinkingAbortTime != 0) &&
+       ((millis() - blinkingAbortTime) >= ABORT_TRESHOLD) ) {
+    digitalWrite(ledsArray[blinkingSelection], RELAY_STATE_CLOSED);
+    blinkingAbortTime = 0;
+    blinkingSelection = activeSelection;
+  }
+
+  // handle blinking led (if selected is not current state)
+  if (activeSelection != blinkingSelection) {
+    if ((millis() - blinkingStartTime) >= BLINK_TRESHOLD) {
+      digitalWrite(ledsArray[blinkingSelection], blinkingLedState);
+      digitalWrite(RELAY_BUTTON_LEDS, !blinkingLedState);
+      blinkingStartTime = millis();
+      blinkingLedState = !blinkingLedState;
+    }
+  }
+  else {
+    digitalWrite(RELAY_BUTTON_LEDS, RELAY_STATE_CLOSED);
+  }
+
+  digitalWrite(ledsArray[activeSelection], RELAY_STATE_OPEN);
+
+  // update audio channel relays
+  if (activeSelection == NONSTOP)
+  {
+    digitalWrite(RELAY_NONSTOP_OR_STUD, RELAY_STATE_CLOSED);
+    digitalWrite(RELAY_STUD1_OR_STUD2, RELAY_STATE_CLOSED);
+  }
+  else if (activeSelection == STUDIO1)
+  {
+    digitalWrite(RELAY_NONSTOP_OR_STUD, RELAY_STATE_OPEN);
+    digitalWrite(RELAY_STUD1_OR_STUD2, RELAY_STATE_CLOSED);
+  }
+  else if (activeSelection == STUDIO2)
+  {
+    digitalWrite(RELAY_NONSTOP_OR_STUD, RELAY_STATE_OPEN);
+    digitalWrite(RELAY_STUD1_OR_STUD2, RELAY_STATE_OPEN);
+  }
+}
+
+buttonEvent_t debounce( const uint8_t buttonPin,
+                        uint8_t *buttonState,
+                        unsigned long *buttonLastEventTime )
+{
+  uint8_t buttonLastState = *buttonState;
+  uint8_t buttonReading = digitalRead(buttonPin);
+
+  if ( (*buttonLastEventTime == 0) &&
+       (*buttonState != buttonReading) ) {
+    // something happened, start the debouncing process
+    *buttonLastEventTime = millis();
+  }
+
+  if (*buttonLastEventTime != 0) {
+    long buttonEventTimer = millis() - *buttonLastEventTime;
+
+    if ( (buttonEventTimer < DEBOUNCE_TRESHOLD) &&
+         (*buttonState == buttonReading) ) {
+      // noise or bouncing, abort
+      *buttonLastEventTime = 0;
+    }
+
+    if ( (buttonEventTimer >= DEBOUNCE_TRESHOLD) &&
+         (buttonReading != *buttonState) ) {
+      // new button state was maintained
+      *buttonLastEventTime = 0;
+      *buttonState = buttonReading;
+    }
+  }
+
+  // debouncing finished, return button state
+  if (*buttonLastEventTime == 0) {
+    // comparing buttonState and buttonLastState tells whether the
+    // button is pressed or released
+    if ( (*buttonState == HIGH) &&
+         (buttonLastState == LOW) ) {
+      // button just pressed
+      buttonLastState = *buttonState;
+      return pressed;
+    }
+    if ( (*buttonState == LOW) &&
+         (buttonLastState == HIGH) ) {
+      // button just released
+      buttonLastState = *buttonState;
+      return released;
+    }
+  }
+  return nochange;
+}