/* ds-velux-adapter: firmware for ATtiny85 to connect a digitalSTROM
                     GR-KL200 to a VELUX KLF 050.
  
  Copyright (C) 2018 Klaus.Schmidinger@tvdr.de
  http://tvdr.de/digitalstrom/ds-velux-adapter

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU 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 General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program. If not, see https://www.gnu.org/licenses/.
 */

/*
 Revision history:
 2018-08-13  Initial version.
 2018-08-23  Pins pUp and pDn are now set to INPUT in idle mode, and only
             switch to OUTPUT when actively driving the KLF 050.
             By doing so, the Schottky diodes D1 and D2 can be left out
             (replace with plain wires in the original PCB layout).
 */

// Version 1.1

// I/O pins:

#define pL1 PB0 // connects to L1' of the GR-KL200
#define pL2 PB2 // connects to L2' of the GR-KL200
#define pUp PB1 // connects to the orange wire of the KLF 050
#define pDn PB3 // connects to the red wire of the KLF 050

// States:

#define sNone 0
#define sIdle 1
#define sUp   2
#define sDn   3

// Timing:

#define PulseWidth 100 // the width (in ms) of a pulse sent to the KLF 050
#define L12Timeout  50 // the time (in ms) after which pL1/pL2 is considered "off"

// Globals:

volatile bool L1 = false;
volatile bool L2 = false;
volatile unsigned long LastInt = 0;
int State = sNone;

// Helpers:

#define b(n) (1 << (n))

void SetTimeout(void)
{
  LastInt = millis();
}

void CheckTimeout(void)
{
  noInterrupts();
  if (millis() - LastInt > L12Timeout)
     L1 = L2 = false;
  interrupts();
}

void Start(int Pin)
{
  pinMode(Pin, OUTPUT);
  digitalWrite(Pin, LOW);
  delay(PulseWidth);
  digitalWrite(Pin, HIGH);
  delay(PulseWidth);
  pinMode(Pin, INPUT);
}

int Stop(void)
{
  pinMode(pUp, OUTPUT);
  pinMode(pDn, OUTPUT);
  digitalWrite(pUp, LOW);
  digitalWrite(pDn, LOW);
  delay(PulseWidth);
  digitalWrite(pUp, HIGH);
  digitalWrite(pDn, HIGH);
  delay(PulseWidth);
  pinMode(pUp, INPUT);
  pinMode(pDn, INPUT);
  return sIdle;
}

int HandleIdle(void)
{
  if (L1) {
     Start(pUp);
     return sUp;
     }
  if (L2) {
     Start(pDn);
     return sDn;
     }
  return sIdle;
}

int HandleUp(void)
{
  CheckTimeout();
  if (!L1)
     return Stop();
  return sUp;
}

int HandleDn(void)
{
  CheckTimeout();
  if (!L2)
     return Stop();
  return sDn;
}

ISR(PCINT0_vect)
{
  if (digitalRead(pL1) == 0) {
     L1 = true;
     L2 = false;
     SetTimeout();
     }
  if (digitalRead(pL2) == 0) {
     L2 = true;
     L1 = false;
     SetTimeout();
     }
}

void setup()
{
  // I/O pins:
  pinMode(pL1, INPUT_PULLUP);
  pinMode(pL2, INPUT_PULLUP);
  pinMode(pUp, INPUT);
  pinMode(pDn, INPUT);
  // Pin Change Interrupts:
  GIMSK |= b(PCIE);         // turn on pin change interrupts
  PCMSK |= b(pL1) | b(pL2); // enable PCI on pins pL1 and pL2
  interrupts();             // enable interrupts
}

void loop()
{
  switch (State) {
    case sIdle: State = HandleIdle(); break;
    case sUp:   State = HandleUp();   break;
    case sDn:   State = HandleDn();   break;
    default:    State = Stop();
    }
}
