/*
 =============================================================================
 FireFly Morse Throwie 
 - a light controled (LED as Sensor) morseblinker throwie with ATTiny45/85
 ============================================================================= 
 
 Project definitions, sources
 ----------------------------------------------------------------------------- 
 Version: 0.5 - Attiny45 Version
 Date   : 23.02.2014

 -----------------------------------------------------------------------------

 Inspired by Karl Lunt's FireFly project I wrote some code to make this
   Throwie lasting longer, blinking only at low light levels and morse also
   some text.
   http://www.seanet.com/~karllunt/fireflyLED.html

 How it works:
   LED detects light level, using bleeding out time of LED pn-capacitor.
   If dark enough blink a text in morsecode (ATTiny45 = 22 Chars, text will 
   be defined in line 191/192 of this file)
   To save energy go to powersave mode after blinking or light level is
   over threshold. Tests this every 8s (maximum time for watchdog timer 
   possible for ATTiny). Repeat this endless
   
 Programming:
   by Arduino IDE + ATTiny extension:
     http://highlowtech.org/?p=1695
     
   an USB Programmer for ATMELs or you can use an Arduino UNO to do this.
     http://highlowtech.org/?p=1706

 Partslist:  
  
  1x ATTiny45 (or Attiny85 but I had have a couple of 45er left, not tryed
     out at a *85),
     
  1x LED (all typ will work, but you have to adjust the light sensity
     (see --> "darknessThreshold"). Clear plastic LED types  are more light
     sensitiv,
     
  1x 100 Ohm Resistor
  
  1x CR2032 Battery other batterytypes also good. everything was provide a
     Voltage from 3V...6V. 
     I have also one version working with a 1F Goldcap and a small 5V solar 
     module.
  1x rare earth magnet, because it is a throwie ;-), but not need for working
  
  ~x some tape or other stuff to protect the throwie
     that's up to you. 

     The wiring, it's very simple:
     
                     +---+      +------\/------+
                         |      | ATTINY45 /85 |
                     >   |     -+1=PB5    VCC=8+-> to battery Vcc 3V
             100 Ohm <   |      |              |
                     >   +------+2=PB3    PB2=7+-
                     |          |              |
                   ----- +------+3=PB4    PB1=6+-
             LED    / \  |      |              |
                   ----- |   +--+4=GND    PB0=5+-
                     |   |   |  |              |
                     +---+   |  +--------------+
                             |
                             +---------------------> to battery GND
            
  You can use other pins if necessary, theoretical up to 3 LEDs, as example I
  tried out to connect another one at PB2&PB1. working fine. Basic wiring:
 
          + LED_N_Side), wired to a digital pin, NOT Vcc
          |
          <
          > 100 ohm resistor
          <
          |
          |
        -----
         / \  LED, maybe a 5mm, clear plastic is good
        -----
          |
          |
          + LED_P_Side), wired to a digital pin, NOT Gnd

 -----------------------------------------------------------------------------
 Im just a newbe in ATTiny / Arduino programming, so many thanks to:
 
 Programming ATTiny with Arduino IDE:
   http://highlowtech.org/?p=1695 
 
 LED Lightsensor, Arduino Playground:
   http://playground.arduino.cc/Learning/LEDSensor
  
 Powersaving Mode for Attiny45/85:
   A good description by Martin Nawrath nawrath@khm.de and the folks at
   http://www.insidegadgets.com/2011/02/05/reduce-attiny-power-consumption-by-sleeping-with-the-watchdog-timer/
   
   With this description I got ~5uA during sleep mode.
   in Morse-Mode with LED OFF: 1.7mA 
                      LED ON:  6.9mA
   --> I hope to run this throwies ~40days with one CR2032.

 Morsecode
   To translate a text into morsecode, there are several ways. You can use
   this fine online translator:
   http://morsecode.scphillips.com/jtranslator.html
 
   There is also a great morse coder / encoder from Matthias Esterl aka madc.
   Works fine for Arduino, but not for ATTiny because of RAM limitations. But
   maybe this is helpful if your project works with an Arduino.
   https://gist.github.com/madc/4474559

   For this project I use a simple case structur. Looks arkward, but the
   ATTiny45 has only 256byte RAM but 4kFlash. A data structure is better programming
   but needs a lot of RAM we not have. Therefore this ugly case structure.
   This will waste the flash memory but we have enough and saving RAM for
   our payload - the ASCII-string with the morsetext.
 
 BTW - Morsecode itself:
   
     'A', ".-"      'B', "-..."    'C', "-.-."
     'D', "-.."     'E', "."       'F', "..-."
     'G', "--."     'H', "...."    'I', ".."
     'J', ".---"    'K', ".-.-"    'L', ".-.."
     'M', "--"      'N', "-."      'O', "---"
     'P', ".--."    'Q', "--.-"    'R', ".-."
     'S', "..."     'T', "-"       'U', "..-"
     'V', "...-"    'W', ".--"     'X', "-..-"
     'Y', "-.--"    'Z', "--.."    
 
     '1', ".----"   '2', "..---"   '3', "...--"
     '4', "....-"   '5', "....."   '6', "-...."
     '7', "--..."   '8', "---.."   '9', "----."
     '0', "-----"
   
     '.', ".-.-.-"  ',', "--..--" '?', "..--.."
     '!', "-.-.--"  ':', "---..." ';', "-.-.-."
     '(', "-.--."   ')', "-.--.-" '"', ".-..-."
     '@', ".--.-."  '&', ".-..." 

 -----------------------------------------------------------------------------
 Legal stuff / Copyright: 
   Creative Commons Attribution ShareAlike 3.0.:
   http://creativecommons.org/licenses/by-sa/3.0/legalcode
 -----------------------------------------------------------------------------
*/


// ===========================================================================
// Ok folks, let's start
// ===========================================================================
// for sleep Mode / Powersave we need some additional stuff. Its alredy there,
// if you installed the Attiny extension for Arduino IDE, no additional
// installing needed.
#include <avr/sleep.h>
#include <avr/wdt.h>

// ===========================================================================
// Some definitions for powersave depending of ATTiny type.
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

// ===========================================================================
// for ATTiny45
// Unit length < 150 will hard to be read for optical morse code
//             > 300 are too slow in my feeling for longer text
//
// for ATTiny85
// Unit length < 5   will hard to be read for optical morse code
//             > 10  are too slow in my feeling for longer text
//

#define unitLength  200  

// ---------------------------------------------------------------------------
// LED Pin definitions, 
// theoretical you can use up to 3 LED. For example you can use it for a
// landmark beacon. So if you point this LEDs to different directions you can 
// detect the position in dependence of your location to the bacon. 
#define LED1_N_SIDE 3 
#define LED1_P_SIDE 4

// ===========================================================================
// Variables
// ===========================================================================
// ###########################################################################
// # and now the text, but be aware, only 256Byte RAM for Attiny45           #
// # so there are only 22 Chars left for your message.                       #
// # BTW with an Attiny85 you will have additional 256byte                   #
// # => more than for twitter :-)                                            #

String morseText =
     "ICL Q1-16";
// # "....5....1....5....2.."; just a ruler, remember 22 Chars for ATTiny45  #
//   IT IS USELESS
//   ALL HAIL THE GLOW CLOUD
//   ALL MY VICES ARE DEVICES
//   I HATE YOU SO MUCH
//   THE INSIDES OF THINGS ARE BEAUTIFUL
//   LIBERTAS PER SCIENTIAM (Freedom through Knowledge)
//   http://en.wikipedia.org/wiki/List_of_mottos
// ###########################################################################

// Define light trigger threshold. best way to set it on dusk / dawn level
// diffuse red ones are less sensitive than clear green ones...
int darknessThreshold = 17000;

// Interrupt Flag, should be volatile, means: read from RAM, not register
// because registers are used for interrupt handling, it have to be volatile.
volatile boolean f_wdt = 1;

// ===========================================================================
// Setup watchdog
// 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms
// 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec
// ===========================================================================
void setup()
{
  setup_watchdog(9);
}

// ===========================================================================
// Main
// ===========================================================================

void loop(){

  if (f_wdt==1) {  // wait for timed out watchdog 
                   // flag is set when a watchdog timeout occurs
    f_wdt=0;       // reset flag  
  
    // if it's dark enough [>darknesThreshold] morse
    if (sensDarkness(LED1_N_SIDE, LED1_P_SIDE) > darknessThreshold){
      // looks like it's dark, so do your job
      morse(LED1_N_SIDE, LED1_P_SIDE); 
    }
  }
  // set all used port to intput to save power
  pinMode(LED1_N_SIDE,INPUT);
  pinMode(LED1_P_SIDE,INPUT);
  system_sleep();
  f_wdt=0;          // 2nd time sleep
  system_sleep();
}

// ===========================================================================
// Subroutines for Sleeping
// ===========================================================================
// set system into the sleep state 
// system wakes up when wtchdog is timed out
void system_sleep() {
  cbi(ADCSRA,ADEN);                    // switch Analog to 
                                       // Digitalconverter OFF

  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
  sleep_enable();

  sleep_mode();                        // System sleeps here

  sleep_disable();                     // System continues execution here when
                                       // watchdog timed out 
  sbi(ADCSRA,ADEN);                    // switch Analog to Digitalconverter ON
}

// ----------------------------------------------------------------------------
// Setup for watchdog. Parameter ii for sleeping time:
//      0 =  16ms, 1 =  32ms, 2 =  64ms, 
//      3 = 128ms, 4 = 250ms, 5 = 500ms
//      6 = 1 sec, 7 = 2 sec, 8 = 4 sec,
//      9 = 8 sec
void setup_watchdog(int ii) {

  byte bb;
  int ww;
  if (ii > 9 ) ii=9;
  bb=ii & 7;
  if (ii > 7) bb|= (1<<5);
  bb|= (1<<WDCE);
  ww=bb;

  MCUSR &= ~(1<<WDRF);
  
  WDTCR |= (1<<WDCE) | (1<<WDE);       // start timed sequence

  WDTCR = bb;                          // set new watchdog timeout value
  WDTCR |= _BV(WDIE);
}
// -----------------------------------------------------------------------------
// Watchdog Interrupt Service / is executed when watchdog timed out
ISR(WDT_vect) {
  f_wdt=1;                             // set global flag
}

// =============================================================================
// Subroutines for light sensor
// =============================================================================
// Function sensDarkness
// Usage: sensDarkness(Pin-No. N-Side of LED, Pin-No. P-Side of LED):
// will result "darkness-level" - the higher the darker:
//      30000 = pitch black
//      0     = sunshine
int sensDarkness(int LED_N, int LED_P){

  unsigned int i;                        //Parameter bleed-out LED capacitor

  // charge the capacitor of LED  
  pinMode(LED_N,OUTPUT);
  pinMode(LED_P,OUTPUT);
  digitalWrite(LED_N,HIGH);
  digitalWrite(LED_P,LOW);

  // Isolate the N end of the diode and turn off internal pull-up resistor
  pinMode(LED_N,INPUT);
  digitalWrite(LED_N,LOW);

  // Count how long it takes the diode to bleed back down to a logic zero
  for ( i = 0; i < 30000; i++) {
    if ( digitalRead(LED_N)==0) break;
  }
  // thats it, return result
  return i;
}

// =============================================================================
// Subroutines for morse text
// =============================================================================
// -----------------------------------------------------------------------------
// Function dit = ".", a dit is as long as a unitLength, used in morse()
//
void dit(int LED_P){
  digitalWrite( LED_P, HIGH ); delay( unitLength );
  digitalWrite( LED_P, LOW );  delay( unitLength );
}  

// -----------------------------------------------------------------------------
// Function dah = "-", a dah is as long as a 3*unitLength
//
void dah(int LED_P){
  digitalWrite( LED_P, HIGH ); delay( unitLength*3 );
  digitalWrite( LED_P, LOW );  delay( unitLength );
}  

// -----------------------------------------------------------------------------
// Function morse:
//   hand over text and LED Pins
//   because you want to use more than one LED hand over also LED Pins

void morse(int LED_N, int LED_P){
  
  // Because I have more Flash than RAM I decided to use a case structure.
  // Its not so pretty, but I can use the RAM to hold my morseText.
  //
  // set N-Side of LED Low, so you can set P-side high to let them flash

  pinMode( LED_N, OUTPUT);
  pinMode( LED_P, OUTPUT);

  digitalWrite( LED_N, LOW);

  // send it to defined LED
  for(int i=0; i<=morseText.length(); i++)
  {
    switch( morseText[i] )
    {            
 
    // Chars A-Z
    case 'A': // .-
      dit(LED_P); dah(LED_P);
      break;
 
    case 'B': // -...
      dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P);
      break;

    case 'C': // -.-.
      dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); 
      break;

    case 'D': // -..
      dah(LED_P); dit(LED_P); dit(LED_P); 
      break; 

    case 'E': // .
      dit(LED_P);
      break; 

    case 'F': // ..-.
      dit(LED_P); dit(LED_P); dah(LED_P); dit(LED_P);
      break; 

    case 'G': // --.
      dah(LED_P); dah(LED_P); dit(LED_P); 
      break; 

    case 'H': // ....
      dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P);
      break; 

    case 'I': // ..
      dit(LED_P); dit(LED_P);
      break; 

    case 'J': // .---
      dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P);
      break; 

    case 'K': // .-.-
      dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P);
      break; 

    case 'L': // .-..
      dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P);
      break; 

    case 'M': // --
       dah(LED_P); dah(LED_P);
      break; 

    case 'N': // -.
      dah(LED_P); dit(LED_P);
      break; 

    case 'O': // ---
      dah(LED_P); dah(LED_P); dah(LED_P);
      break; 

    case 'P': // .--.
      dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P);
      break; 

    case 'Q': // --.-
      dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); 
      break; 

    case 'R': // .-.
      dit(LED_P); dah(LED_P); dit(LED_P);
      break; 
      
    case 'S': // ...
      dit(LED_P); dit(LED_P); dit(LED_P);
      break;       

    case 'T': // -
      dah(LED_P);
      break; 

    case 'U': // ..-
      dit(LED_P); dit(LED_P); dah(LED_P);
      break; 

    case 'V': // ...-
      dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P);
      break; 

    case 'W': // .--
      dit(LED_P); dah(LED_P); dah(LED_P);
      break; 

    case 'X': // -..-
       dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P);
      break; 

    case 'Y': // -.--
       dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P);
      break; 

    case 'Z': // --..
      dah(LED_P);dah(LED_P); dit(LED_P); dit(LED_P);
      break; 

    // Numbers
    case '1': // .----
      dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P);
      break; 

    case '2': // ..---
      dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P);
      break; 

    case '3': // ...--
      dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P);
      break; 

    case '4': // ....-
      dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P);
      break; 

    case '5': // .....
      dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P);
      break; 

    case '6': // -....
      dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); 
      break; 

    case '7': // --...
      dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); 
      break; 

    case '8': // ---..
      dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); 
      break; 

    case '9': // ----.
      dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); 
      break; 

    case '0': // -----
      dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); 
      break; 

    // Special Signs
    case ' ': // Gap
        delay( unitLength*5 );
      break; 

    case '.': // .-.-.-
      dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P);
      break; 

    case ',': // --..--
      dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P);
      break; 

    case '?': // ..--..
      dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P);
      break; 

    case '!': // -.-.--
      dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P);
      break; 

    case ':': // ---...
      dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P);
      break; 

    case ';': // -.-.-.
      dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P);
      break; 

    case '(': // -.--.
      dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P);
      break; 

    case ')': // -.--.-
      dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P);
      break; 

    case '"': // .-..-.
      dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dit(LED_P);
      break; 

    case '@': // .--.-.
      dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P);
      break; 

    case '&': // .-...
      dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P);
    }  
    // wait at end of sign 
    delay( unitLength*3 );
  }
}

// -----------------------------------------------------------------------------
// That's all, folks :-)
// -----------------------------------------------------------------------------
