/*
* 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 .
*/
#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
#include
#include
#include
#ifdef ENABLE_UDP
#include
#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;
}