You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

330 lines
9.0 KiB

// //////////
// System includes for libraries
// //////////
#include "Arduino.h"
#include "Wire.h"
#include "MAX1704.h"
#include "Sleepy.h"
#include "Metro.h"
#include "SharpIR.h"
#include "Adafruit_NeoPixel.h"
#ifdef __AVR__
#include <avr/power.h>
#endif
// //////////
// Various defines / pins / etc
// TWEAK THESE ACCORDINGLY
// //////////
#define DEBUG false
#define INTERACTIVE_DEBUG false
#define USE_SLEEP true
#define USE_PIR false
#define SLEEP_INTERVAL 500 // miliseconds
#define ALERT_LEVEL 15 // battery alert level (%) [1-32% are valid values here]
#define POT_BRIGHT A0
#define POT_COLOR A1
#define PIN_ALWAYS_ON 9 // Switch that enables always on/off of pixels
#define PIN_WHITE 12 // Switch that switches between color wiper and white color
#define PIN_DOOR 11 // Door sensor pin
#define PIN_PIR A3 // Pin with PIR distance sensor
#define PIN_NEO 6 // Pin with the pixel comm line
#define NEO_PIX_NUM 5
// //////////
// Objects / values / etc
// //////////
MAX1704 fuelGauge;
Metro timer = Metro(2000); // Timer for battery level alert (flashes pixels red or yellow [if red set]) to alert
float charge_percent;
uint8_t brightness;
uint16_t raw_color;
uint8_t color;
bool always_on = false;
Adafruit_NeoPixel neopix = Adafruit_NeoPixel(NEO_PIX_NUM, PIN_NEO, NEO_GRB + NEO_KHZ800);
uint8_t rgb[3]; // index 0 = red / index 1 = green / index 2 = blue
uint8_t red_threshold = 10; // 10 on the 360 degree mapping represents where red / orange / yellow start to bleed and the alert blink needs to be a different color
bool white = false;
bool max_found = false;
bool door_open = false;
bool lights_on = false;
#if USE_PIR
SharpIR sharp(PIN_PIR, 25, 93, 1080);
int distance = 0;
#endif
// //////////
// Various function definitions
// //////////
void hsvToRgb(int h, double s, double v); // See end of file for implementation
void print_debug(); // Print debugging info if debug if debugging output is enabled
void read_values(); // Read values from various sensors/devices
void do_low_power(); // Enable sleep / low power mode(s) as appropriate
void pixel_update(); // Update pixel colors, on/off state, etc as appropriate
bool show_low_battery_alert(); // Whether or not the low battery alert is to be shown
ISR(WDT_vect) { Sleepy::watchdogEvent(); } // Setup the watchdog -- For sleepy
// //////////
// Standard setup method for Arduino IDE
// //////////
void setup() {
Wire.begin();
// Setup serial and wait for console
if (DEBUG) {
Serial.begin(9600);
if (INTERACTIVE_DEBUG) {
while(!Serial);
}
Serial.println("Starting up...");
}
// Setup various digital/analog inputs
pinMode(PIN_ALWAYS_ON, INPUT_PULLUP);
pinMode(PIN_WHITE, INPUT_PULLUP);
pinMode(PIN_DOOR, INPUT_PULLUP);
pinMode(PIN_PIR, INPUT);
pinMode(POT_BRIGHT, INPUT);
pinMode(POT_COLOR, INPUT);
// Setup / config MAX1704
Wire.beginTransmission(MAX1704_ADDR);
max_found = (Wire.endTransmission() == 0);
if (max_found) {
fuelGauge.reset();
fuelGauge.quickStart();
fuelGauge.awake(); // In case it was turned off when sleep was enabled
delay(250); // Wait for it to settle before writing config value
fuelGauge.setAlertThreshold(ALERT_LEVEL);
}
// AdaFruit NeoPixel init
// Begin trinket special code (per NeoPixel examples)
#if defined (__AVR_ATtiny85__)
if (F_CPU == 16000000) clock_prescale_set(clock_div_1);
#endif
// End of trinket special code
// Setup pixels, off to begin
neopix.begin();
neopix.setBrightness(0);
neopix.show();
// Various debugging output
if (DEBUG) {
fuelGauge.showConfig();
Serial.println("----------");
}
if (INTERACTIVE_DEBUG) {
while(!Serial.available());
}
}
// //////////
// Standard loop for Arduino IDE
// //////////
void loop() {
read_values();
pixel_update();
print_debug();
do_low_power();
}
// //////////
// Read values from inputs/devices
// ///////////
void read_values() {
// Read various values needed
if (max_found) {
charge_percent = fuelGauge.stateOfCharge(); // Battery level
}
brightness = map(analogRead(POT_BRIGHT), 0, 1024, 0, 255); // Brightness (0-1023) mapped to 0-255
raw_color = analogRead(POT_COLOR); // Color (0-1024)
always_on = (digitalRead(PIN_ALWAYS_ON) == LOW); // Toggle button pressed == ALWAYS ON
white = (digitalRead(PIN_WHITE) == LOW); // Shorted pin to enable white color for LEDs
door_open = (digitalRead(PIN_DOOR) == HIGH); // SparkFun door sensor is indicating door is open (NO for door closed state, set as input_pullup so the logic is inverted)
#if USE_PIR
distance = sharp.distance();
#endif
lights_on = always_on || (door_open); // Take a look at all appropriate values and set lights on/off as appropriate
}
// ///////////
// Update pixels appropriately (on/off, color, etc)
// ///////////
void pixel_update() {
// Store temp variable for alert showing so it doesn't change mid-run
// Metro timer is checked and is a method call that can/will change during this code block / method
bool pix_show_alert = show_low_battery_alert();
// Set RGB array to values as appropriate
if (white) {
rgb[0] = rgb[1] = rgb[2] = 255;
}
else {
// Convert raw color value to position on a circle and then use the circle position to figure out color on a color wheel
color = map(raw_color, 0, 1024, 0, 360);
hsvToRgb(color, 1, 1); // set rgb byte array
}
// Set to red/yellow for alert if alerting and the timer has run out
if (pix_show_alert) {
if (color <= red_threshold && !white) {
rgb[0] = 0;
rgb[1] = rgb[2] = 255;
}
else {
rgb[0] = 255;
rgb[1] = rgb[2] = 0;
}
}
// Set color accordingly
for (uint8_t i=0; i<NEO_PIX_NUM; i++) {
neopix.setPixelColor(i, neopix.Color(rgb[0], rgb[1], rgb[2]));
}
// Deal with brightness
if (!lights_on) {
brightness = 0;
}
neopix.setBrightness(brightness);
// Apply changes
neopix.show();
// Introduce brief delay so there is a visible flicker when alerting
if (pix_show_alert) {
delay(500);
}
}
// //////////
// Setup / run low power mode if appropriate
// //////////
void do_low_power() {
// Verify sleep mode is enabled
// Don't sleep if the lights are on (pixels eat way more power than an AVR chip / board, don't worry about "excess" battery use just yet)
// This is inverted logic + return to make life easier and not having to wrap contents of method in an if block
if (!USE_SLEEP || lights_on) {
return;
}
// Carry on with sleep stuff if we get this far
if (DEBUG) {
Serial.println("SLEEPING!");
}
if (max_found) {
fuelGauge.sleep();
}
delay(250); // Be sure that everything is settled before going to sleep
Sleepy::loseSomeTime(SLEEP_INTERVAL);
if (max_found) {
fuelGauge.awake();
}
}
// //////////
// Figure out if the low battery alert needs to be shown
// //////////
bool show_low_battery_alert() {
return fuelGauge.isAlerting() && timer.check() == 1;
}
// //////////
// Print debugging info
// //////////
void print_debug() {
// Debugging code
if (DEBUG) {
if (max_found) {
Serial.print("Battery %: ");
Serial.println(charge_percent);
Serial.print("Alert level: ");
Serial.println(fuelGauge.alertThreshold());
Serial.print("Alert: ");
Serial.println(fuelGauge.isAlerting());
}
else {
Serial.println("MAX1704 not found!");
}
Serial.print("Brightness: ");
Serial.println(brightness);
Serial.print("White: ");
Serial.println(white);
Serial.print("Color: ");
Serial.println(color);
Serial.print("Red: ");
Serial.println(rgb[0]);
Serial.print("Green: ");
Serial.println(rgb[1]);
Serial.print("Blue: ");
Serial.println(rgb[2]);
Serial.print("Always on: ");
Serial.println(always_on);
Serial.print("Door sensor: ");
Serial.println(door_open);
#if USE_PIR
Serial.print("PIR Sensor: ");
Serial.println(distance);
#endif
Serial.println("----------");
delay(250);
}
}
// //////////
// Convert degrees on a circle to color values
// http://www.skylark-software.com/2011/01/arduino-notebook-rgb-led-and-color_21.html
// //////////
void hsvToRgb(int h, double s, double v) {
// Make sure our arguments stay in-range
h = max(0, min(360, h));
s = max(0, min(1.0, s));
v = max(0, min(1.0, v));
if(s == 0) {
// Achromatic (grey)
rgb[0] = rgb[1] = rgb[2] = round(v * 255);
}
double hs = h / 60.0; // sector 0 to 5
int i = floor(hs);
double f = hs - i; // factorial part of h
double p = v * (1 - s);
double q = v * (1 - s * f);
double t = v * (1 - s * (1 - f));
double r, g, b;
switch(i) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
default: // case 5:
r = v;
g = p;
b = q;
}
rgb[0] = round(r * 255.0);
rgb[1] = round(g * 255.0);
rgb[2] = round(b * 255.0);
}