/***************************************************************
*  Name    : BTcontrol.c                                       *
*  Author  : Fulvio                                            *
*  Date    : 9/13/2016                                         *
*  Version : 1.0                                               *
*  Notes   : Bluetooth controller for 8 outputs                *
*          : which allows still or timed outputs               *
****************************************************************

 The project allows to receive commands via serial trasmissions. For this project
 is meant to connect a Bluetooth module hc-06 which may control up to 8 output.
 The command string is defined as follow:
 One character of ABCDEFHI will turn each pin enable. NOTE the capital letter
 One character of abcdefhi will turn each pin disabled. Note the case.
 Character X will turn all output active    UPPER CASE
 Character x will turn all output inactive  LOWER CASE
 Character p will make play a tune over the pin number 2. Tunes are played
 sequentially as much as they're counted by mldcnt.
 Each direct action on an output will disable the corresponding timer if running.
 There is a define that will reverse the logic. So if it is commented
 ACTIVE_LOW the outputs will be ON at HIGH level.
 The string should be sent within a length of 6 characters, either first carriege
 return (CR) or first space character will end the input.

 So the input is terminated if one of the three conditions is met:
    o) buffer full (according to the definition of BUFLEN
    o) the first CR sent
    o) the first space character
 Non alphaNumeric characters are rejected.

 A second option allows to set a timer for each pin. The timer may last upto 9
 hours long. Longer is reduced to 9 hours.

 The string to be sent is compose as follow:
 First character is 't' case insensitive. To inform that we want a timed output.
 Second character is a digit from 0 to the number of timers choosen, pointing at
 the relative output pin.
 Zero will point to the first output, one for the second, and so forth upto 8
 outputs, if programmed so.
       there's an option to reduce the number of outputs, just modifying the
       definition on NUMTIMER into how many number of outputs to use. The output
       pins are defined on the port_pin array. So if needed, adjust it accordingly

 Third character is the time magnitude, that could be expressed by seconds,
 minutes or hours. I.E. 't3s10 ', means timed output on pin 3 for 10 seconds.
 To set minutes, the character is 'm' and for the hours is 'h'. Case insensitive.

 so 's' for seconds
    'm' for minutes
    'h' for hours
 The command string may have some different character after the time magnitude
 I.E. 'tNs=1 ' is valid too. Here 'N' means the timed output.
 The first 2 characters are fix positioned.

 system has a control over the Bluetooth device hc-06, in order to disconnect from
 any communication that is not communicating for 30 seconds long. This to prevent
 that only one communication hold up and leaving other without any chance to
 operate with the system.
 This can be overriden by changing INPUT_TMO, that's counting half second.
 The project is meant to serve for several users. So if one user will forget to
 disconnect after finish the use, the system will do it automatically.
 */

/* Setting a debug mode operations by defining a keyword */
//#define DEBUG

#ifdef DEBUG
    #include <SoftwareSerial.h>         // used for debug purposes
#endif
#include <TimerOne.h>
#include <avr/pgmspace.h>
#include <Tone.h>

#define BUFLEN 6                        // buffer length for the input
#define MELODYLEN 128                   // tune array counter. Maximum size
#define TUNENUM 10                      // number of ringtones stored in flash
#define BT_DLY_CNT 2                    // delay for keep the Bluetooth OFF
#define NUMTIMER 4                      // number of timer  used
#define TONEPIN 2                       // pin defined for the sound
#define TXDPIN 3                        // Debug serial trasmissions, TX pin
#define RXDPIN 4                        // Debug serial trasmissions RX pin
#define BTRST 5                         // Bluetooth reset pin
#define OUTPUTMASK 0b00001110           // Mask to which outputs shouldn't touch
#define LED 13                          // blinking LED, to show the run
#define INPUT_TMO 60                    // Input timeout counter in half sec time.

/* Definition that permits to have the outputs logic normal or reversed
 * Commenting this out will give normally high output. */
#define ACTIVE_LOW                      // Setting the level output, for all same

#ifdef ACTIVE_LOW
    #define OUT_LEVEL HIGH
#else
    #define OUT_LEVEL LOW
#endif

char inbuf[BUFLEN];
bool led_state = false;
bool bt_tmo = false;
uint8_t bt_delay_cnt = BT_DLY_CNT;
uint8_t bt_tmo_cnt = INPUT_TMO;
uint8_t mldcnt;
uint16_t timers[NUMTIMER];
uint8_t timers_state;
uint8_t port_state;
const char error PROGMEM = "\nError: ";
const char wrgprs PROGMEM = "parsing value\n";
const char wrgtmr PROGMEM = "what's the timer selected\n";

// Assignment pins array. Here can program a different pattern.
const uint8_t port_pin[] = {9, 10, 11, 12, A0, A1, A2, A3};

/* including new tones here, it's necessary to modify the melody counter. Also
 * one melody  must not exceed 127 words.
 * The number of tunes might be more than 50, just flash memory permitting.
 */

const uint16_t melody[][MELODYLEN] PROGMEM = {
// Exorcist theme
    {1318,187,1760,187,1318,187,1975,187,1318,187,1568,187,1760,187,1318,187,\
1760,187,2349,187,1318,187,1975,187,2092,187,1318,187,1975,187,1318,187,\
1318,187,1975,187,1568,187,1760,187,1318,187,2092,187,1318,187,2349,187,\
1975,187,2092,187,1318,187,1975,187,1318,187,1760,187,1318,187,1975,187,\
1568,187,1760,750,0,0},
// Wagner (Valkirie Ride)
{880,375,659,46,880,187,1046,375,0,187,880,375,0,187,1046,375,880,46,1046,187,\
0,187,1046,375,0,187,1318,375,1046,46,1318,187,1568,375,0,187,784,375,0,187,\
784,46,1046,187,1318,750,0,375,0,0},
// Axel theme
{1568,600,1864,450,1568,150,0,150,1568,150,2092,300,1568,300,1396,300,1568,600,\
1568,150,0,150,1568,150,2488,300,2349,300,1864,300,1568,300,2349,300,3136,300,\
1396,150,0,150,1396,150,1174,300,1760,300,1568,1200,0,0},
// Bach Toccata
{880,93,784,93,880,1500,784,93,698,93,587,93,659,93,554,750,0,93,587,562,0,187,\
0,750,880,93,784,93,880,1500,659,281,698,281,554,281,587,750,0,0},
// Mission Impossible
{1568,133,0,266,1568,133,0,266,1396,133,0,133,1480,133,0,133,1568,133,0,266,\
0,266,1864,133,0,133,2092,133,0,133,1568,133,0,266,1568,133,0,266,1396,133,\
1480,133,0,133,1568,133,0,266,1568,133,0,266,1864,133,0,133,2092,133,0,133,\
1568,133,1174,1066,0,66,1864,133,1568,133,1108,1066,0,66,1864,133,1568,133,\
0,133,932,133,1046,133,0,0},
// Pink Panther
{659,187,698,187,0,750,784,187,830,187,0,750,659,187,698,187,0,93,784,187,830,187,\
1108,187,1046,187,0,93,659,187,698,187,0,93,1046,187,987,750,0,750,932,93,\
698,93,622,93,698,562,0,0},
// Italian Anthem
{784,600,784,450,880,150,784,1200,1318,600,1318,450,1396,150,1318,1200,1318,600,\
1396,150,1318,1200,1174,600,1318,450,1174,150,1046,1200,1318,600,1318,600,\
987,225,1046,450,1174,150,1046,300,987,300,880,1200,1046,600,987,300,1046,300,\
784,600,1318,1200,1396,600,0,0},
// Malaysian Anthem
{523,450,0,150,523,600,0,150,880,600,0,150,698,1800,0,150,698,450,0,150,698,600,\
1046,600,0,150,880,450,0,150,698,600,0,150,659,900,0,600,698,300,0,150,784,1800,\
523,600,0,150,659,600,0,150,698,600,0,150,784,1800,0,150,880,600,0,150,932,600,\
1174,600,0,600,1046,900,0,150,932,300,0,150,880,2400,0,300,523,600,0,150,698,600,\
880,600,0,600,1046,1800,0,150,880,600,0,0},
// Final Count Down
{0,480,0,240,987,120,880,120,987,480,659,480,0,480,0,240,1046,120,987,120,1046,240,\
880,480,0,480,0,240,1046,120,987,120,1046,480,659,480,0,480,0,240,880,120,\
880,240,784,240,740,240,880,240,784,720,740,120,784,120,880,720,784,120,880,120,\
880,240,784,240,740,240,659,480,1046,480,987,1440,987,120,1046,120,987,120,\
987,1920,0,0},
// Indiana Johnes
{0,30,659,240,0,120,698,120,784,120,0,120,1046,960,0,120,587,240,0,120,659,120,\
0,240,784,240,0,120,880,120,987,120,0,120,1396,960,0,240,880,240,0,120,987,120,\
1174,480,1318,480,659,240,0,120,698,120,784,120,0,120,1046,960,0,240,1174,240,\
1318,120,0,0}
};

#ifdef DEBUG
    SoftwareSerial Debug(RXDPIN, TXDPIN); // RX, TX
    long now_time;
#endif
Tone doorbell;

void play_tune(uint8_t pointer) {
    uint8_t tunepnt = 0;
    uint16_t note;
    uint16_t duration;
    do {
        note = pgm_read_word(&melody[pointer][tunepnt]);
        tunepnt++;
        duration = pgm_read_word(&melody[pointer][tunepnt]);
        tunepnt++;
        if(note) {
            doorbell.play(note);
            delay(duration);
            doorbell.stop();
        }
        else {
        delay(duration);
        }
        if (tunepnt > MELODYLEN) break;
    } while (note || duration);
}

uint8_t settimers() {
    uint16_t y = 0;
    uint8_t i = 0;
    uint8_t x = 0;
    uint16_t k;
    bool first_found = false;       // Flag used to signal the fist digit found
    uint8_t lngt = BUFLEN;
    while (lngt) {                  // reading the digits from the right side
        x = inbuf[lngt] - 48;       // converting in a binary value
        --lngt;
        if (i > 2) break;           // if it counted 3 digits will return
        if ((x < 0) || (x > 9)) {   // any non digits will stop to calculate
           if (first_found) break;  // more digits not found  then it's over
           else continue;
        }
        first_found = true;
        switch (i) {                // reading from the right side backward
           case 0:                  // First case just count the value
           y = y + x;
           break;
           case 1:                  // Second case add the ten
           y = y + x * 10;
           break;
           case 2:
           y = y + x * 100;         // Last case add the hundred
           break;
        }
        ++i;
    }
    if (!lngt) return 8;
    i = inbuf[2] & 95;
    switch (i) {
        case 'H': {
            if (y > 9) y = 9;
            k = 3600;
            break;
        }
        case 'M': {
            k = 60;
            break;
        }
        case 'S': {
            k = 1;
            break;
        }
        default: {
            k = 0;
            break;
        }
    }
    x = inbuf[1] - 48;
    if ((x < 0) || (x > (NUMTIMER -1))) {
        return 16;
    }
    bitWrite(port_state, x, HIGH);
    bitWrite(timers_state, x, HIGH);
    k = y * k;
    timers[x] = k * 2;
    Serial.println();
    Serial.print(F("Timer "));
    Serial.print(x,DEC);
    Serial.print(" = ");
    Serial.println(k,DEC);
    return 255;
}

void set_outputs(uint8_t y, uint8_t i) {
#ifdef DEBUG
        Debug.print("\r\ny = ");
        Debug.println(y,DEC);
        delay(200);
#endif

    if (y) {
        y -= 1;
        bitWrite(port_state,y,i);
        digitalWrite(port_pin[y], i ^ OUT_LEVEL);
        bitWrite(timers_state,y,0);
        #ifdef DEBUG
        // used on the simulator to start the time counter
        digitalWrite(TONEPIN, LOW);
        #endif
    }
}

void wait4buf() {
    uint8_t i = 0;
    uint8_t x = 0;
    bt_tmo_cnt = INPUT_TMO;                 // load the timeout counter
    while (i < BUFLEN) {                    // wait until the buffer is full
        if (Serial.available() > 0) {
            x = Serial.read();
            if ((x == 13) || (x == 32)) {   // or wait for a space or CR
                inbuf[i] = 0;               // put a NULL termination
                bt_tmo_cnt = INPUT_TMO;     // and reset the timeout counter
                break;
            }
            inbuf[i] = x;
            if (isAlphaNumeric(x)) {        // anything not printable is holding
                i++;                        // else put one more char in buffer
                bt_tmo_cnt = INPUT_TMO;     // and reset the timeout counter
            }
        }
        if (!bt_tmo_cnt) {                  // if the TimerOne is zero
            bt_tmo = true;                  // Flag it to reset Bluetooth
            break;                          // and leave
        }
    }
}

void setup() {
    uint8_t i;
    uint8_t x;
    doorbell.begin(TONEPIN);              // Tone output, for any sound or alarms

#ifdef DEBUG
    Debug.begin(57600);
    Debug.println(F("Tone started "));
    Debug.println(F("Debug monitor active"));
#endif
    port_state = 0;
    pinMode(LED, OUTPUT);
    pinMode(BTRST, OUTPUT);
    digitalWrite(LED, OUT_LEVEL);
    digitalWrite(BTRST, LOW);                  // starting the Bluetooth device
    for (i=0; i < NUMTIMER ; i++) {
        x = bitRead(port_state, x);
        pinMode(port_pin[i], OUTPUT);
        digitalWrite(port_pin[i], x ^ OUT_LEVEL);// Setting all outputs off
    };
    Timer1.initialize(500000);              // 1/2 second period
    Timer1.attachInterrupt( timerIsr );
    Serial.begin(9600);                     // starting Bluetooth
    Serial.println("You're connected");     // send a msg that gives confirmation
}

void loop() {
    uint8_t i;
    uint8_t x;
    uint8_t y;
    for (i=0; i < BUFLEN; i++) inbuf[i] = 0;
        #ifdef DEBUG
        now_time = millis();
        #endif
    wait4buf();                         // waiting for input
    #ifdef DEBUG
    if (bt_tmo) {                       // Debug check if the TimerOne happens
        Debug.print("\r\nTimed out! ");
        Debug.print((millis()- now_time)/1000);
        now_time = millis();
        Debug.println();
    }
    #endif
    x = inbuf[0];
    if (bitRead(x, 5)) i = LOW;         // defining the output level if is Upper
    else i = HIGH;                      // case
    x &= 95;                            // converting to upper case
    y = (x - 64);                       // indexing the timers within ->
    if (y > NUMTIMER) y = 0;            // the selected number of timers
    switch (x) {
        case 'A': set_outputs(y, i);
        break;
        case 'B': set_outputs(y, i);
        break;
        case 'C': set_outputs(y, i);
        break;
        case 'D': set_outputs(y, i);
        break;
        case 'E': set_outputs(y, i);
        break;
        case 'F': set_outputs(y, i);
        break;
        case 'G': set_outputs(y, i);
        break;
        case 'H': set_outputs(y, i);
        break;
        case 'P': {
            play_tune(mldcnt);
            if (mldcnt < TUNENUM) ++mldcnt; // cycling for the number of melodies
            else mldcnt = 0;
        }
        break;
        case 'X': {
            for (x=0; x < NUMTIMER; x++) {
                timers[x] = 0;
                port_state &= OUTPUTMASK;
                set_outputs(x+1 , i);
            }
        }
        break;
        case 'T': {
            x = settimers();
            if (x == 8) {
                Serial.print(error);
                Serial.print(wrgprs);
                break;
            }
            else if (x == 16) {
                Serial.print(error);
                Serial.print(wrgtmr);
                break;
            }
        }
        break;
    }  //End switch
#ifdef DEBUG
        Debug.print("\r\nport = ");
        Debug.println(port_state,BIN);
        Debug.print("\r\ntimerstate = ");
        Debug.println(timers_state,BIN);
        delay(200);
#endif
}

void timerIsr() {
    bool i;
    uint8_t x;
    // blinking the LED to show the controller is ready.
    if (led_state) {
        digitalWrite(LED, led_state);
        led_state = false;
    }
    else {
        digitalWrite(LED, led_state);
        led_state = true;
    }
    if (bt_tmo) {
#ifdef DEBUG
    Debug.print("reset BT ");
#endif

        digitalWrite(BTRST, HIGH);
        if (bt_delay_cnt) bt_delay_cnt--;   // hold down for 2 seconds.
        else {
            bt_tmo = false;
            digitalWrite(BTRST, LOW);
            bt_delay_cnt = BT_DLY_CNT;
            bt_tmo_cnt = INPUT_TMO;     // and reset the timeout counter
        }
    }
    // Setting the output according the wanted level
    if (bt_tmo_cnt) bt_tmo_cnt--;
    for (x = 0; x < NUMTIMER; x++) {
        if (bitRead(timers_state, x)) {
            if (timers[x]) {
                --timers[x];
                bitWrite(port_state, x, HIGH);
            }
            else {
                bitWrite(port_state, x, LOW);
                bitWrite(timers_state, x, LOW);
                #ifdef DEBUG
                // used in the simulator to count the elapsed time
                digitalWrite(TONEPIN, LOW);
                #endif
            }
        }
        i = bitRead(port_state, x);
        digitalWrite(port_pin[x], i ^ OUT_LEVEL);
    }
}
