/*
* PEO - Personal Energy Orb
* by Janko Hofmann & Fabian Pammer
* Sketching With Hardware Summer 2012
*/
#include <EEPROM.h>

unsigned long time;
int PIN_RED = 11;
int PIN_GREEN = 9;
int PIN_BLUE = 10;
int sensorPin = 2;
int ledPin = 13; // select the pin for the LED
int magnetSwitchPin = 12;

boolean readable = true;
int sensorValue = 0; // variable to store the value coming from the sensor

unsigned long tempMillis = 0; // temp variable for last time of EEPROM save
unsigned long tempMillis2 = 0; // temp variable for last time of sending message to PC
unsigned long lastPCResponse = 0; // time of last response from the PC
int energyLevel; // this is what PEO mainly is about
// HSB values for easier color manipulation
int hue = 0;
float saturation = 1;
float brightness = 1;
const int COLOR_RED = 300; // hue for red color
const int COLOR_GREEN = 180; // hue for green color
const int MAX_ENERGY_LEVEL = 100;
boolean bikeConnected = false;
boolean animationCanStart = false; // animation can only start when it was finished before
int animationDelay = 10; // number milliseconds before RGB LED changes to next color
boolean computerConnected = false;

void setup() {
Serial.begin(9600); // start serial communication
pinMode(PIN_RED, OUTPUT);
pinMode(PIN_BLUE, OUTPUT);
pinMode(PIN_GREEN, OUTPUT);
pinMode(magnetSwitchPin, INPUT);
pinMode(ledPin, OUTPUT);
// Magnet-sensor for bike wheel revolutions
pinMode(sensorPin, INPUT);
energyLevel = readFromEEPROM();
//Serial.println(energyLevel);
showEnergyAnimation();
}

void loop() {
time = millis();
// checks if there are incoming messages over serial connection
getReceivedValue();

// checks if PEO is connected to the PC
checkPCConnection();

/* revolutions are only processed when PEO
is connected to the bike */
if (isBikeConnected()) {
// light fades in if it was not on before
boolean dimmIn = (saturation == 0);

getRevolutions();
saturation = 1;
showEnergyLevel(dimmIn);

}
else if (computerConnected) {
saturation = 1; // light stays on
showEnergyLevel(false);
}
else {
// light was on before
if (saturation == 1) {
dimmOut(10); // fade out
} else {
saturation = 0; // light stays off
showEnergyLevel(false);
}
}

// every 30 seconds:
if (time - tempMillis > 30000) {
// only writes the energy level if it has changed
if (readFromEEPROM() != energyLevel) {
saveToEEPROM();
}
tempMillis = time;
}
// every second
else if (time - tempMillis2 > 1000){
tempMillis2 = time;
sendCreditStatus(); // computer receives and processes it
}

delay(10);
}

void checkPCConnection() {
// over 4 secs no PC respnse => connection lost
if (time - lastPCResponse > 4000) {
computerConnected = false;
}
else {
computerConnected = true;
}
}

void getRevolutions() {
// Magnet-switch is closed once per revolution
sensorValue = digitalRead(sensorPin);

// when magnet-switch closed
if (sensorValue == LOW) {
/* Readable = true only if switch was open before
(prevents continuous incrementing when the magnet on the wheel
stops next to the magnet switch) */
if (readable) {
energyLevel++;

// energy level can not be overcharged, stops at 100 percent
if (energyLevel > MAX_ENERGY_LEVEL) {
energyLevel = MAX_ENERGY_LEVEL;
}

readable = false;
}
}
else {
// value can be read during the next revolution
readable = true;
}
}

void showEnergyAnimation() {
animationCanStart = false;
showEnergyLevel(true);
delay(100);
dimmOut(35);
animationCanStart = true;
delay(1000);
}


boolean isBikeConnected() {
// magnet switch is closed when PEO sits on the bike mount
int switchValue = digitalRead(magnetSwitchPin);

// when magnet switch closed
if (switchValue == LOW) {
return true;
}
else {
return false;
}
}

// converts the energy level to a hue value
int getHueFromEnergyLevel() {
/* energyLevel = 0 => PEO is red,
energyLevel = 100 => PEO is green,
hues are interpolated for values in between */
return map(min(energyLevel, MAX_ENERGY_LEVEL), 0, MAX_ENERGY_LEVEL, COLOR_RED, COLOR_GREEN);
}

void getReceivedValue() {
while (Serial.available() >= 10) { // are there any bytes available on the serial port?

char c = Serial.read();


if (c == 'p') { // received a ping message from PC
for (int i=0; i<10; i++) {
c = Serial.read();
if (c == 'e') {
lastPCResponse = millis();
}
}
}
else if (c == 'n') { // received a "decrease energy level" message from PC
int value = 0;
for (int i=0; i<10; i++) {
c = Serial.read();
if (c != 'e') {
value = (10*value)+(c-'0');
}
else {
if (value > 0) {
if(energyLevel >= 1) {
energyLevel = energyLevel - value;
}
lastPCResponse = millis();
}
}
}
}
}
}

void sendCreditStatus() {
String myString = String(energyLevel);
String zeroes = "00000000";
// ensures that the energy level is transferred in an 8-digit number
myString = zeroes.substring(0, 7-(myString.length()-1)) + myString;

// code for "begin of message"
Serial.write('s');

for (int i=0; i< myString.length(); i++) {
// transfers every digit of the energy level
Serial.write(myString.charAt(i));
}

// code for "end of message"
Serial.write('e');
Serial.println();
}

// makes peo glow in a color that represents the energy level
void showEnergyLevel(boolean fadeIn) {
hue = getHueFromEnergyLevel();
if (fadeIn) {
dimmIn(30);
}
else {
outputColor();
}
}

void dimm(int rate) {
dimmOut(rate);
dimmIn(rate);
}

// progresively lights up
void dimmIn(int rate) {
for (float b=0; b<=1; b+=0.01) {
saturation = b;
outputColor();
delay(rate);
}
}

// progresively dims down
void dimmOut(int rate) {
for (float b=1; b>=0; b-=0.01) {
saturation = b;
outputColor();
delay(rate);
}
}

// prepares and outputs the color for the RGB LEDs
void outputColor() {
long color = HSBtoRGB(hue, saturation, brightness);

// Get the red, blue and green parts from generated color
int red = color >> 16 & 255;
int green = color >> 8 & 255;
int blue = color & 255;

setColor(red, green, blue);
}

// sends the RGB values to the RGB LED pins
void setColor (unsigned char red, unsigned char green, unsigned char blue)
{
analogWrite(PIN_RED, red);
analogWrite(PIN_GREEN, green);
analogWrite(PIN_BLUE, blue);
}

// converts HSB to RGB colors
long HSBtoRGB(float _hue, float _sat, float _brightness) {
float red = 0.0;
float green = 0.0;
float blue = 0.0;

if (_sat == 0.0) {
red = _brightness;
green = _brightness;
blue = _brightness;
}
else {
if (_hue == 360.0) {
_hue = 0;
}

int slice = _hue / 60.0;
float hue_frac = (_hue / 60.0) - slice;

float aa = _brightness * (1.0 - _sat);
float bb = _brightness * (1.0 - _sat * hue_frac);
float cc = _brightness * (1.0 - _sat * (1.0 - hue_frac));

switch(slice) {
case 0:
red = _brightness;
green = cc;
blue = aa;
break;
case 1:
red = bb;
green = _brightness;
blue = aa;
break;
case 2:
red = aa;
green = _brightness;
blue = cc;
break;
case 3:
red = aa;
green = bb;
blue = _brightness;
break;
case 4:
red = cc;
green = aa;
blue = _brightness;
break;
case 5:
red = _brightness;
green = aa;
blue = bb;
break;
default:
red = 0.0;
green = 0.0;
blue = 0.0;
break;
}
}

long ired = red * 255.0;
long igreen = green * 255.0;
long iblue = blue * 255.0;

return long((ired << 16) | (igreen << 8) | (iblue));
}

void saveToEEPROM() {
// value to save: current energy level
int toConvert = energyLevel;

// Integers are stored in two bytes, EEPROM_save can only save one byte at the same time
// firstByte = high-order, secondByte = low-order
byte firstByte = byte(toConvert >> 8);
byte secondByte = byte(toConvert & 0x00FF);

EEPROM.write(0, firstByte);
EEPROM.write(1, secondByte);

}

int readFromEEPROM() {
byte firstByte = EEPROM.read(0);
byte secondByte = EEPROM.read(1);
// restores the energy value from the EEPROM bytes
return int(firstByte << 8) + int(secondByte);
}
PEO – Personal Energy Orb « Sketching with Hardware