#include <SPI.h>
#include <SD.h>
#include <RTClib.h>
#include <DS18B20.h>
#include <LiquidCrystal.h>
#include <EEPROM.h>
// constants
#define TREND_FALL 1
#define TREND_RISE 2
#define TREND_CONST 3
#include "./hardware_conf.h"
#include "./charset.h"
#include "./software_conf.h"
#include "./menus.h"
// devices
RTC_DS1307 rtc;
LiquidCrystal lcd(LCD_RSPin, LCD_EnPin, LCD_D4Pin, LCD_D5Pin, LCD_D6Pin, LCD_D7Pin);
DS18B20 ds(Sensors_PIN);
// Variables
DateTime now, oldTime;
unsigned long ms, tempMs, logMs, cardMs, buttonMs, menuMs;
uint8_t sID;
uint8_t buttonState, oldButtonState;
File logFile; // log file
bool isSD = true; // indicate SD Card in slot
unsigned int samples; // counting log samples
uint8_t sensors; // detected sensors
bool isFirstMeasurement;
float sensorCurrent[MAX_SENSORS];
float sensorOld[MAX_SENSORS];
float sensorMax[MAX_SENSORS];
float sensorMin[MAX_SENSORS];
float sensorAverage[MAX_SENSORS];
uint8_t sensorTrend[MAX_SENSORS];
float trendThreshold = 0.1;
unsigned long LOG_TIME_MS = 15 * 1000;
unsigned long SAMPLE_TIME_MS = 1000;
uint8_t menuMode = 0; // current menu: 0 - view mode; 1 - main menu
uint8_t viewMode = 0; // current view (see menu view)
uint8_t menuOption = 0; // current main menu option
bool redrawMenu = false;
bool sounds = true;
// SD Services
void testSDCard() {
isSD = SD.begin(SD_CSPin);
}
void createLogFile() {
sID = 0;
logFile = SD.open(FILE_NAME, FILE_WRITE);
if (logFile) {
// sensor description
while (ds.selectNext()) {
sID++;
logFile.print(F("Sensor #"));
logFile.print(sID);
logFile.print(F(" at address: "));
uint8_t address[8];
ds.getAddress(address);
for (uint8_t i = 0; i < 8; i++) {
logFile.print(address[i], HEX);
}
logFile.print(F(" Model: "));
switch (ds.getFamilyCode()) {
case MODEL_DS18S20:
logFile.print(F("DS18S20/DS1820"));
break;
case MODEL_DS1822:
logFile.print(F("DS1822"));
break;
case MODEL_DS18B20:
logFile.print(F("DS18B20"));
break;
default:
logFile.print(F("Unrecognized Device"));
break;
}
logFile.print(F(" Resolution: "));
logFile.println(ds.getResolution());
}
// file header
logFile.print(F("Date,Time,"));
for (sID = 1; sID <= sensors; sID++) {
logFile.print(F("Sensor #")); logFile.print(sID);
if (sID < sensors) {
logFile.print(",");
} else {
logFile.println();
}
}
samples = 0;
clearSensors();
startUpTone();
} else {
isSD = false;
SD.end();
}
logFile.close();
}
void writeLogFile() {
logFile = SD.open(FILE_NAME, FILE_WRITE);
if (logFile) {
// sprintf(str,"%2d.%2d.%2d",now.year(),now.month(),now.day());
// logFile.print(str);
logFile.print(now.year());
logFile.print('.');
logFile.print(now.month());
logFile.print('.');
logFile.print(now.day());
logFile.print(FIELD_DELIMITER);
// sprintf(str,"%2d:%2d:%2d",now.hour(),now.minute(),now.second());
// logFile.print(str);
logFile.print(now.hour());
logFile.print(':');
if (now.minute() < 10) {
logFile.print('0');
}
logFile.print(now.minute());
logFile.print(':');
if (now.second() < 10) {
logFile.print('0');
}
logFile.print(now.second());
// sensor(s) data
for (sID = 0; sID <= sensors; sID++) {
logFile.print(FIELD_DELIMITER);
logFile.print(sensorCurrent[sID]);
}
logFile.println();
samples++;
} else {
isSD = false;
SD.end();
}
logFile.close();
}
// Sensor services
void getSensors() {
sID = 0;
while (ds.selectNext()) {
sID++;
float newVal = ds.getTempC();
// trend
float dVal = newVal - sensorOld[sID];
if (dVal > trendThreshold) {
sensorTrend[sID] = TREND_FALL;
} else if (dVal < -trendThreshold) {
sensorTrend[sID] = TREND_RISE;
} else {
sensorTrend[sID] = TREND_CONST;
}
if (sensorTrend[sID] != TREND_CONST) {
sensorOld[sID] = newVal;
}
// current
sensorCurrent[sID] = newVal;
// min & max
if (newVal < sensorMin[sID]) {
sensorMin[sID] = newVal;
}
if (newVal > sensorMax[sID]) {
sensorMax[sID] = newVal;
}
// average
if (isFirstMeasurement) {
dVal = newVal;
} else {
dVal = (sensorAverage[sID] + newVal) / 2;
}
sensorAverage[sID] = dVal;
}
isFirstMeasurement = false;
}
void clearSensors() {
for (sID = 1; sID <= sensors; sID++) {
sensorMin[sID] = 127;
sensorMax[sID] = -127;
sensorAverage[sID] = 0;
sensorTrend[sID] = TREND_CONST;
}
isFirstMeasurement = true;
}
//
// interface
//
void errorHalt(String s) {
lcd.clear();
lcd.println(s);
// Serial.println(s);
while (1) {
errorTone(); delay(2000);
}
}
void showTime() {
lcd.setCursor(14, 0); lcd.write(' ');
if (now.hour() < 10) {
lcd.write(' ');
}
lcd.print(now.hour());
if (now.second() & 1) {
lcd.write(':');
} else {
lcd.write(' ');
}
if (now.minute() < 10) {
lcd.write('0');
}
lcd.print(now.minute());
}
void showSensors() {
float dVal;
char fstr[6];
char str[16];
for (sID = 1; sID <= sensors; sID++) {
switch (viewMode) {
case 0: // current
dVal = sensorCurrent[sID];
break;
case 1: // minimum
dVal = sensorMin[sID];
break;
case 2: // average
dVal = sensorAverage[sID];
break;
case 3: // maximum
dVal = sensorMax[sID];
break;
}
dtostrf(dVal,6,1,fstr);
sprintf(str,"T%1d%c%s%c ",sID,char(sensorTrend[sID]),fstr,char(0xdf));
lcd.setCursor((sID - 1) * 10, 1);
lcd.print(str);
// lcd.write('T'); lcd.print(sID); lcd.write(byte(sensorTrend[sID]));
// lcd.print(fstr);
// lcd.write(byte(0xDF));
}
}
void showCardAvailable() {
byte ch;
if (isSD) {
ch = 242;
} else {
errorTone();
ch = byte('!');
}
if (menuMode == 0) {
lcd.setCursor(13, 0);
lcd.write(ch);
}
}
byte selectMenu(byte sel, byte max) {
if (buttonState == 1) { // previous entry
buttonUpTone();
if (sel > 0) {
sel--;
} else {
sel = max - 1;
}
} else if (buttonState == 2) { // next entry
buttonDownTone();
if (sel < max - 1) {
sel++;
} else {
sel = 0;
}
}
return sel;
}
// view mode menu
void updateViewMode() {
lcd.setCursor(0, 0); lcd.print(menuView[viewMode]);
showSensors();
}
void buttons4ViewMode() {
viewMode = selectMenu(viewMode, viewMenuEntries);
if (buttonState < 4) {
updateViewMode();
} else {
menuMode = 1;
menuOption = DEFAULT_MAINMENU;
redrawMenu = true;
menuMs = ms;
acceptTone();
}
}
// Main menu
void updateSelectMenu(char *items[], byte current, byte maxEntries) {
lcd.clear();
lcd.setCursor(0, 1);
lcd.print("> ");
lcd.print(items[current]);
lcd.setCursor(2, 0);
if (current > 0) {
lcd.print(items[current - 1]);
} else {
lcd.print(items[maxEntries - 1]);
}
redrawMenu = false;
}
void buttons4MainMenu() {
menuMs = ms;
menuOption = selectMenu(menuOption, mainMenuEntries);
if ((buttonState < 4) || redrawMenu) {
updateSelectMenu(menuMain, menuOption, mainMenuEntries);
} else {
switch (menuOption) {
case 1: // Eject SD Card
ejectSDCard();
break;
case 2: // Measurements...
menuMode = 2;
menuOption = DEFAULT_MAINMENU;
redrawMenu = true;
acceptTone();
return;
case 3: // Toggle sounds
sounds = !sounds;
acceptTone();
return;
case 4: // Stats
showStats();
break;
}
back2ViewMode();
}
}
// Mesurements menu
void updateValue(unsigned long value, char unit, float divider) {
char fstr[8];
char str[12];
float dVal=value / divider;
dtostrf(dVal,6,1,fstr);
sprintf(str,"< %s %c >",fstr,unit);
lcd.setCursor(4, 1);
lcd.print(str);
menuMs = ms;
buttonMs = ms;
}
long choiceValue(long value, long min, long max, long step, char unit, float divider) {
bool done = false;
long butDelay = 0;
bool firstPress = true;
acceptTone();
updateValue(value, unit, divider); oldButtonState = 0;
while (digitalRead(But3_PIN));
while (!done) {
ms = millis();
if ((ms - buttonMs) > 20) {
if (butDelay > 0) {
butDelay--;
}
buttonMs = ms;
}
buttonState = digitalRead(But1_PIN) << 0;
buttonState |= digitalRead(But2_PIN) << 1;
buttonState |= digitalRead(But3_PIN) << 2;
if (buttonState != 0) {
if ( butDelay == 0 ) {
switch (buttonState) {
case 1: // inc value
if (value < max) {
value += step;
} else {
value = min;
}
break;
case 2: // dec value
if (value > min) {
value -= step;
} else {
value = max;
}
break;
case 4:
done = true;
}
if (firstPress) {
butDelay = 8;
} else {
butDelay = 4;
}
firstPress = false;
updateValue(value, unit, divider);
}
} else {
firstPress = true;
}
if ((ms - menuMs) > TIMEOUT_MAINMENU) {
value = -1; done = true; exitTone();
}
}
if (value != -1) {
enterTone();
}
return value;
}
void buttons4MeasurementMenu() {
unsigned long newVal;
menuMs = ms;
menuOption = selectMenu(menuOption, measurementMenuEntries);
if ((buttonState < 4) || redrawMenu) {
updateSelectMenu(menuMeasurement, menuOption, measurementMenuEntries);
} else {
if (menuOption > 0) {
lcd.clear();
}
switch (menuOption) {
case 0: // back
back2ViewMode();
return;
case 1: // Reset Minimal/Average/Maximum
clearSensors();
enterTone();
redrawMenu = true;
return;
case 2: // Logging period
lcd.print(F("Logging period time"));
newVal = choiceValue(long(LOG_TIME_MS / 1000.0), 1, 60 * 60, 1, 's', 1);
if (newVal > 0) {
LOG_TIME_MS = newVal * 1000;
}
break;
case 3: // Trend threshold
lcd.print(F("Trend threshold"));
newVal = choiceValue(long(trendThreshold * 1000.0), 0, 1000, 100, byte(0xDF), 1000);
if (newVal >= 0) {
trendThreshold = float(newVal / 1000.0);
}
break;
case 4: // Sampling period
lcd.print(F("Sampling period time"));
newVal = choiceValue(SAMPLE_TIME_MS, 1000, 30 * 1000, 500, 's', 1000);
if (newVal > 0) {
SAMPLE_TIME_MS = newVal;
}
break;
case 5: // store current settings
lcd.print(F("Writeing settings..."));
writeSettings();
acceptTone();
break;
}
redrawMenu = true;
}
}
//
// menus functions
//
void ejectSDCard() {
lcd.clear();
lcd.setCursor(2, 0); lcd.println(F("Logging on hold"));
delay(1000);
isSD = false; SD.end();
lcd.clear();
lcd.println(F("You can remove card."));
enterTone();
lcd.setCursor(1, 1);
lcd.println(F("Press MENU to back"));
while (!digitalRead(But3_PIN));
while (digitalRead(But3_PIN));
}
void back2ViewMode() {
menuMode = 0;
lcd.clear();
updateViewMode();
showTime();
showCardAvailable();
showSensors();
exitTone();
}
void showStats() {
}
//
//
//
void startUpTone () {
if (sounds) {
for (byte i = 5; i < 9; i++) {
tone(BUZZER_PIN, i * 150, 75); delay(75);
}
}
}
void exitTone() {
if (sounds) {
for (byte i = 8; i > 2; i--) {
tone(BUZZER_PIN, i * 150, 50); delay(50);
}
}
}
void buttonUpTone() {
if (sounds) {
tone(BUZZER_PIN, 1500, 50);
}
}
void buttonDownTone() {
if (sounds) {
tone(BUZZER_PIN, 1700, 50);
}
}
void acceptTone() {
if (sounds) {
tone(BUZZER_PIN, 500, 100); delay(100);
tone(BUZZER_PIN, 700, 100); delay(100);
}
}
void enterTone() {
if (sounds) {
tone(BUZZER_PIN, 700, 100); delay(100);
tone(BUZZER_PIN, 500, 100); delay(100);
}
}
void errorTone() {
if (sounds) {
tone(BUZZER_PIN, 100, 250);
}
}
//
//
//
void readSettings() {
byte isStored;
int adr = 0;
EEPROM.get(adr, isStored); adr += sizeof(bool);
if (isStored==0xFF) {
return;
}
EEPROM.get(adr, LOG_TIME_MS); adr += sizeof(long);
EEPROM.get(adr, SAMPLE_TIME_MS); adr += sizeof(long);
EEPROM.get(adr, trendThreshold); adr += sizeof(float);
}
void writeSettings() {
byte isStored = 1;
int adr = 0;
EEPROM.put(adr, isStored); adr += sizeof(bool);
EEPROM.put(adr, LOG_TIME_MS); adr += sizeof(long);
EEPROM.put(adr, SAMPLE_TIME_MS); adr += sizeof(long);
EEPROM.put(adr, trendThreshold); adr += sizeof(float);
}
//
//
//
void setup() {
pinMode(But1_PIN, INPUT);
pinMode(But2_PIN, INPUT);
pinMode(But3_PIN, INPUT);
lcd.begin(20, 4);
lcd.println(F("Initialization..."));
lcd.createChar(TREND_FALL, customChar1);
lcd.createChar(TREND_RISE, customChar2);
delay(500);
if (!rtc.begin()) {
errorHalt(F("RTC not found :("));
}
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
sensors=2;
// sensors = ds.getNumberOfDevices();
// if (sensors==0) { errorHalt(F("Sensor(s) not found :(")); }
testSDCard();
if (!digitalRead(But3_PIN)) { // if Menu button is not pressed...
readSettings(); // read settings from EEPROM
oldButtonState = 4; // prevents entry into the Main Menu
} else {
oldButtonState = 255; // ...otherwise, default settings
}
lcd.clear();
lcd.setCursor(0,1); lcd.print(F("* multiTEMP LOGGER *"));
showTime();
showCardAvailable();
createLogFile();
delay(1000);
updateViewMode();
}
void loop() {
now = rtc.now(); ms = millis();
buttonState = digitalRead(But1_PIN) << 0;
buttonState |= digitalRead(But2_PIN) << 1;
buttonState |= digitalRead(But3_PIN) << 2;
if ((buttonState != oldButtonState) && ((ms - buttonMs) > BUTTON_THRESHOLD) || redrawMenu) {
if ((buttonState > 0) || redrawMenu) {
switch (menuMode) {
case 0:
buttons4ViewMode();
break;
case 1:
buttons4MainMenu();
break;
case 2:
buttons4MeasurementMenu();
break;
}
}
oldButtonState = buttonState;
menuMs = ms;
buttonMs = ms;
}
if (!isSD) {
if ((ms - cardMs) > CARD_TIME_MS) {
testSDCard();
showCardAvailable();
if (isSD) {
createLogFile();
}
cardMs = ms;
}
} else {
if ((ms - logMs) > LOG_TIME_MS) {
getSensors();
writeLogFile();
logMs = ms;
}
}
if (menuMode == 0) {
// show current time & measurements ONLY in view mode...
if (now.second() != oldTime.second()) {
showTime();
oldTime = now;
}
if ((ms - tempMs) > SAMPLE_TIME_MS) {
getSensors();
showSensors();
tempMs = ms;
}
} else {
// ...otherwise if time is out, back to view mode :)
if ((ms - menuMs) > TIMEOUT_MAINMENU) {
back2ViewMode();
}
}
}