#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSans12pt7b.h>
#include <Fonts/FreeSans9pt7b.h>
#include <DHT.h>
#include <avr/sleep.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
const unsigned char startLogo [] PROGMEM = {
// 'thermometer, 128x32px, image2cpp
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x10,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x30,
0x0e, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x24, 0x18,
0x04, 0x00, 0x01, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x64, 0x10,
0x0c, 0x00, 0x01, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x24, 0x10,
0x0d, 0xcf, 0x7f, 0xd3, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xad, 0x10,
0x0d, 0x9c, 0x79, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x10,
0x1f, 0x18, 0x63, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0f, 0x38, 0x63, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1b, 0x18, 0x63, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x19, 0x9e, 0xc3, 0x87, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x11, 0x06, 0x41, 0x85, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x00, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x40,
0x00, 0x3f, 0x97, 0x81, 0xf1, 0xb3, 0x7b, 0xe0, 0xfc, 0x6f, 0x3c, 0x1f, 0x9f, 0x8f, 0x86, 0xc0,
0x00, 0x0c, 0x3c, 0xc3, 0x19, 0xeb, 0xba, 0x61, 0x8c, 0x3b, 0xee, 0x18, 0x86, 0x18, 0xc7, 0x80,
0x00, 0x0c, 0x30, 0xc6, 0x18, 0xc3, 0x0c, 0x33, 0x06, 0x31, 0xc6, 0x30, 0xc4, 0x18, 0x66, 0x00,
0x00, 0x0c, 0x30, 0x66, 0x19, 0x83, 0x0c, 0x33, 0x06, 0x60, 0x83, 0x30, 0xc6, 0x30, 0x66, 0x00,
0x00, 0x0c, 0x10, 0x67, 0xf8, 0x83, 0x0c, 0x33, 0x03, 0x30, 0xc2, 0x3f, 0xcc, 0x3f, 0xe6, 0x00,
0x00, 0x0c, 0x30, 0x46, 0x01, 0x83, 0x0c, 0x33, 0x06, 0x20, 0x83, 0x20, 0x06, 0x10, 0x06, 0x00,
0x00, 0x0c, 0x30, 0x66, 0x00, 0x83, 0x0c, 0x33, 0x06, 0x60, 0xc2, 0x30, 0x04, 0x30, 0x06, 0x00,
0x00, 0x0c, 0x30, 0x66, 0x01, 0x83, 0x0c, 0x31, 0x86, 0x31, 0x83, 0x30, 0x06, 0x18, 0x06, 0x00,
0x00, 0x07, 0x10, 0x43, 0xf8, 0x83, 0x0c, 0x31, 0xfc, 0x20, 0xc2, 0x1f, 0xc7, 0x8f, 0xc6, 0x00,
0x00, 0x03, 0x30, 0x60, 0xc1, 0x82, 0x04, 0x10, 0x50, 0x20, 0x82, 0x06, 0x01, 0x06, 0x84, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
const int DHT11_PIN = 5;
const int MODE_PIN = 2;
const int HISTORY_SIZE = 30;
const int TEMP_MIN = 10;
const int TEMP_MAX = 30;
enum MODES {
STARTUP,
DISPLAY_SLEEP,
CURRENT_TEMP,
CURRENT_HUMID,
GRAPH_TENSECONDS,
GRAPH_MINUTES,
GRAPH_HOURS,
LOGO,
VERSION,
MILLIS
};
volatile enum MODES mode;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);
DHT dht(DHT11_PIN, DHT11);
class HistoryManager {
public:
HistoryManager(unsigned int bufferSize, unsigned int passSize, HistoryManager *mother) : appendCount(0) {
this->bufferSize = bufferSize;
this->buffer = (float *)malloc(sizeof(float) * bufferSize);
for (unsigned int i = 0; i < bufferSize; i++)
this->buffer[i] = NAN;
this->passSize = passSize;
this->mother = mother;
};
~HistoryManager() {
free(buffer);
};
int append(float value) {
if (++this->appendCount == this->passSize && this->mother) {
mother->append(value);
this->appendCount = 0;
}
// avoid using memcpy
for (unsigned int i = 0; i < this->bufferSize - 1; i++) {
this->buffer[i] = this->buffer[i + 1];
}
this->buffer[this->bufferSize - 1] = value;
}
float* getBuffer() {
return this->buffer;
}
private:
float *buffer;
unsigned int bufferSize;
HistoryManager *mother;
unsigned int appendCount;
unsigned int passSize;
};
class thermometer {
public:
thermometer() : lastMeasured(0) {
this->hoursHist = new HistoryManager(HISTORY_SIZE , 0, (HistoryManager *) NULL);
this->minutesHist = new HistoryManager(HISTORY_SIZE, 30, this->hoursHist); // every two minutes
this->tenSecondsHist = new HistoryManager(HISTORY_SIZE, 12, this->minutesHist);
}
~thermometer() {
return;
}
int setthermometer(DHT* pdht) {
this->pdht = pdht;
}
int measure() {
unsigned long currentTime = millis();
if (currentTime - this->lastMeasured > 10000 - 10 || this->lastMeasured == 0) {
this->lastMeasured = currentTime;
this->lastHumidity = this->pdht->readHumidity();
this->lastTemperature = this->pdht->readTemperature();
if (isnan(this->lastHumidity) || isnan(this->lastTemperature)) {
Serial.println(F("thermometer measure error"));
delay(1000);
return 0;
} else {
this->tenSecondsHist->append(this->lastTemperature);
}
} else {
return 0; // pass
}
return 1;
}
float getHumidity() {
return lastHumidity;
}
float getTemperature() {
return lastTemperature;
}
char* getHumidityStr() {
static char buf[8];
dtostrf(this->getHumidity(), 0, 1, buf);
return buf;
}
char* getTemperatureStr() {
static char buf[8];
dtostrf(this->getTemperature(), 0, 1, buf);
return buf;
}
HistoryManager *minutesHist;
HistoryManager *hoursHist;
HistoryManager *tenSecondsHist;
private:
DHT* pdht;
unsigned long lastMeasured;
float lastHumidity;
float lastTemperature;
} thermometer;
void modeButton_Push() {
if (mode == MILLIS) {
mode = DISPLAY_SLEEP;
} else {
mode = (MODES)(mode + 1);
}
}
void setup() {
Serial.begin(9600);
set_sleep_mode(SLEEP_MODE_IDLE);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
delay(1000);
mode = STARTUP;
Serial.println(F("kcrt's thermometer"));
show_kcrt();
delay(3000);
pinMode(MODE_PIN, INPUT_PULLUP);
pinMode(DHT11_PIN, INPUT);
dht.begin();
thermometer.setthermometer(&dht);
attachInterrupt(digitalPinToInterrupt(MODE_PIN), modeButton_Push, FALLING);
Serial.println(F("...Ready"));
interrupts();
mode = CURRENT_TEMP;
}
void show_kcrt() {
display.clearDisplay();
display.drawBitmap(0, 0, startLogo, SCREEN_WIDTH, SCREEN_HEIGHT, WHITE);
display.display();
}
void loop() {
static enum MODES lastMode = STARTUP;
static unsigned long lastMeasured = 0;
const char *infoStr;
float *histBuf;
char buf[48];
unsigned long m;
const int START_X = SCREEN_WIDTH - HISTORY_SIZE * 3;
const int START_Y = 0;
const int WIDTH = HISTORY_SIZE * 3;
const int HEIGHT = SCREEN_HEIGHT;
enum MODES currentMode = mode;
if (thermometer.measure() == 0 && lastMode == currentMode) {
// 測定値もモードも変更なし
sleep_mode();
if(currentMode == MILLIS || currentMode == DISPLAY_SLEEP){
// pass
}else{
return;
}
}
switch (currentMode) {
case DISPLAY_SLEEP:
display.clearDisplay();
if((millis() / 1000) & 1)
display.drawPixel(SCREEN_WIDTH - 8, SCREEN_HEIGHT - 8, WHITE);
break;
case CURRENT_TEMP:
case CURRENT_HUMID:
if (mode == CURRENT_TEMP)
sprintf(buf, "%s C", thermometer.getTemperatureStr());
else
sprintf(buf, "%s %%", thermometer.getHumidityStr());
display.setFont(&FreeSans12pt7b);
display.clearDisplay();
display.setCursor(32, 24);
display.setTextColor(WHITE);
display.println(buf);
break;
case GRAPH_TENSECONDS:
case GRAPH_MINUTES:
case GRAPH_HOURS:
histBuf = (mode == GRAPH_TENSECONDS) ? thermometer.tenSecondsHist->getBuffer() :
(mode == GRAPH_MINUTES) ? thermometer.minutesHist->getBuffer() : thermometer.hoursHist->getBuffer();
infoStr = (mode == GRAPH_TENSECONDS) ? "5m" :
(mode == GRAPH_MINUTES) ? "1h" : "30h";
display.setFont(&FreeSans9pt7b);
sprintf(buf, "%s", thermometer.getTemperatureStr());
display.clearDisplay();
display.setCursor(0, 24);
display.setTextColor(WHITE);
display.print(buf);
// TEMP_MIN C - TEMP_MAX Cを画面内に表示することにする。
display.drawRect(START_X, START_Y, WIDTH, HEIGHT, WHITE);
display.drawLine(START_X, START_Y + HEIGHT / 2, START_X + WIDTH - 1 , START_Y + HEIGHT / 2, WHITE);
for (unsigned int i = 0; i < HISTORY_SIZE; i++) {
if (!isnan(histBuf[i])) {
float t = constrain(histBuf[i], TEMP_MIN, TEMP_MAX);
int x = START_X + i * 3;
int h = 1.0 * HEIGHT / (TEMP_MAX - TEMP_MIN) * (t - TEMP_MIN);
int y0 = START_Y + HEIGHT - h;
display.fillRect(x, y0, 3, h, WHITE);
if (t > (TEMP_MAX + TEMP_MIN) / 2)
display.drawLine(x, START_Y + HEIGHT / 2, x + 3, START_Y + HEIGHT / 2, BLACK);
}
}
display.setFont();
display.setCursor(START_X + 6, 3);
display.setTextColor(WHITE, BLACK);
display.print(infoStr);
break;
case LOGO:
show_kcrt();
break;
case VERSION:
display.clearDisplay();
display.setFont();
display.setCursor(0, 8);
display.setTextColor(WHITE);
display.println("kcrt's thermometer\n v.0.1, kcrt@kcrt.net");
break;
case MILLIS:
display.clearDisplay();
display.setFont();
display.setCursor(0, 8);
display.setTextColor(WHITE);
m = millis() / 1000;
sprintf(buf, "%02ld day, %02ld hour,\n%02ld min., %02ld sec.", m / (60L * 60 * 24), (m / 60 / 60) % 24, (m / 60) % 60, m % 60);
display.println(buf);
break;
}
lastMode = currentMode;
display.display();
}