My controller-less mysensors temperature logger
-
Hi!
I built a mysensors-datalogger that does not need a controller. The reason for this is that I wanted to log some temperature-data at my office which has a strict IT-security policy and would not allow any network based controller like rasperry pi or vera.
This is what it looks like:
I used the following hardware:
- Arduino Mega 2560 clone (atmega328 has not enough flash for this project)
- oled-display based on sh1106 chipset
- rtc based on DS1302 (would not recommend, but had this one available)
- micro-SD breakout board
- NRF24L01 module
- DHT22 temperature and humidity senosr
- DS18B20 temperature sensor (waterproof)
- some buttons
It is possible to deactivate most of the hardware-pieces in the software. If the RTC has an error or is deactivated by hand it falls back to software RTC.
For powering the NRF24L01 I used the 3.3V regulator of the micro-SD breakout as the mega2560 has a very weak regulator.Basically the software continuously fetches the onboard sensors and waits for radio signals. Every 5 minutes it writes all available values to the SD-Card.
The mysensors-support is very basic. Currently it only supports up to 6 temperature sensors with one temperature and battery reading each.
Here is the code:
/* Weatherstation-"Gateway" (Stand-alone) Created by Florian Heinze This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. */ #include <U8glib.h> #include <SPI.h> #include "SD.h" #include <MySensor.h> #include <MyGateway.h> #include <stdarg.h> #include <DHT.h> #include <DallasTemperature.h> #include <OneWire.h> #include <Time.h> #include <DS1302RTC.h> #include <EEPROM.h> const uint8_t ARROW[] U8G_PROGMEM = { B00000000, B00001100, B00000110, B11111111, B11111111, B00000110, B00001100, B00000000 }; /* * PIN-definitions */ const int DHT_BUS = A9; const int ONE_WIRE_BUS = 25; const int ONE_WIRE_VCC = 23; const int ONE_WIRE_GND = 27; const int NRF_CE_PIN = 30; const int NRF_CS_PIN = 36; const int SD_CS_PIN = 12; const int SD_MISO_PIN = 9; const int SD_MOSI_PIN = 10; const int SD_SCK_PIN = 11; const int DISP_CS_PIN = A4; const int DISP_DC_PIN = A3; const int DISP_RES_PIN = A2; const int DISP_SCK_PIN = A0; const int DISP_MOSI_PIN = A1; const int RTC_SCK_PIN = 16; const int RTC_DAT_PIN = 15; const int RTC_RST_PIN = 14; const int BUTTON_RIGHT_PIN = 4; const int BUTTON_LEFT_PIN = 5; const int BUTTON_UP_PIN = 6; const int BUTTON_DOWN_PIN = 3; const int BUTTON_ENTER_PIN = 7; //Variables for buttons boolean right = false; boolean left = false; boolean up = false; boolean down = false; boolean enter = false; boolean lastright = false; boolean lastleft = false; boolean lastup = false; boolean lastdown = false; boolean lastenter = false; const unsigned long DEBOUNCE_DELAY = 10; unsigned long debouncetime = 0; unsigned long lastSDWrite = 0; //Channel for RF const int RF24_CUST_CHANNEL = 50; //EEPROM-addreses. start at 512 so it doesnt collide with mysensors. const int START_EEPROM_ADR = 512; const int DHT_EEPROM_ADR = START_EEPROM_ADR; const int ONEWIRE_EEPROM_ADR = START_EEPROM_ADR + 1; const int RTC_EEPROM_ADR = START_EEPROM_ADR + 2; const int NRF_EEPROM_ADR = START_EEPROM_ADR + 3; const int SD_EEPROM_ADR = START_EEPROM_ADR + 4; //Filenames #define MYS_LOG "myslog.txt" #define DATA_LOG "datalog.txt" const unsigned int WRITE_MINUTE = 5; //status of hardware typedef enum {OFF = 0, ERR, ON} hw_stat_t; hw_stat_t dht_stat; hw_stat_t onewire_stat; hw_stat_t rtc_stat; hw_stat_t nrf_stat; //no ERR state hw_stat_t sd_stat; //used for rtc time_t t = 0; boolean clock_set = false; enum {DAY, MONTH, YEAR, HOUR, MINUTE, SECOND} set_digit = DAY; #define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) ) static const uint8_t monthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // API starts months from 1, this array starts from 0 //state of HW-page enum {DHT_22 = 0, DS18B22, RTC, NRF, SD_CARD} hw_page_stat = DHT_22; //state of display enum {PAGE1, PAGE2, PAGE3, PAGE4, PAGE5, DIAGRAM, CLOCK, HW_SET} dispstat = PAGE1; //max number of mysensors const int MYS_MAX_SENSORS = 6; //variables for sensors long lastDHTTime; long lastOnewireTime; float tempDHT = 0; float humDHT = 0; float tempOnewire = 0; float tempMyS[MYS_MAX_SENSORS] = {0};//temp of mysensors time_t lastReadMyS[MYS_MAX_SENSORS] = {0};//last time the mysensors are read uint8_t battMyS[MYS_MAX_SENSORS] = {0};//battery of mysensors //used for the diagram float diagram[128]; boolean diagramRead = false; boolean diagramScroll = false; boolean linesUpdate = false; unsigned long diagramStart = 1; unsigned long lines = 0; uint8_t numDiagram = 1; const int BOT_PXL = 64; const int TOP_PXL = 0; boolean dispOnewire = false; //objects for hardware: temperature-sensors DHT dht; OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); //rtc DS1302RTC rtc(RTC_RST_PIN, RTC_DAT_PIN, RTC_SCK_PIN); //display U8GLIB_SH1106_128X64 u8g(DISP_SCK_PIN, DISP_MOSI_PIN, DISP_CS_PIN, DISP_DC_PIN, DISP_RES_PIN); //mysensors MyGateway gw(NRF_CE_PIN, NRF_CS_PIN); //setup-routine of dht void setup_dht() { dht.setup(DHT_BUS); lastDHTTime = millis(); if (dht.getStatus() == DHT::ERROR_NONE) dht_stat = ON; //check for error else dht_stat = ERR; } //setup routine of onewire void setup_onewire() { sensors.begin(); sensors.setWaitForConversion(false); sensors.requestTemperatures(); if (sensors.getDeviceCount() == 1) onewire_stat = ON; //check for error else onewire_stat = ERR; lastOnewireTime = millis(); } //setup rtc void setup_rtc() { if (rtc.get() == 0) //check for error { rtc_stat = ERR; } else { rtc_stat = ON; setSyncProvider(rtc.get); //rtc is set up as sync provider. normal time comes from internal clock. } } //setup for sd-card void setup_sd() { if (SD.begin(SD_CS_PIN, SD_MOSI_PIN, SD_MISO_PIN, SD_SCK_PIN)) {//start and check for error sd_stat = ON; if (!SD.exists(DATA_LOG)) {//if the file does not exist, write header File dataFile = SD.open(DATA_LOG, FILE_WRITE); if (dataFile) { dataFile.print("time,temp-dht,humidity-dht,temp-onewire"); for (int i = 1; i <= MYS_MAX_SENSORS; i++) { dataFile.print(",temp"); dataFile.print(i); } for (int i = 1; i <= MYS_MAX_SENSORS; i++) { dataFile.print(",batt"); dataFile.print(i); } dataFile.print("\n"); dataFile.close(); } } } else { sd_stat = ERR; } } //reads the last line of the datalog-file to initialize the mysensor-values with the last values after reset. void readLastMyS() { File dataFile = SD.open(DATA_LOG, FILE_READ); char buf[106]; char *str, *p; if (dataFile) { //11 date 6 time 6 dhttemp 6 dhthum 6 onewire 6*6 onewire 6*4 onewire = 101 max 56 min unsigned long filesize = dataFile.size(); dataFile.seek(filesize - 106); dataFile.find("\n"); dataFile.readBytesUntil('\n', buf, 105); //read a line uint8_t j = 0; for (str = strtok_r(buf, ",", &p); // goto last entry before MyS-Data str && j < 3; str = strtok_r(NULL, ",", &p) ) { j++; } for (int i = 0; i < MYS_MAX_SENSORS; i++) { //read MyS-temps str = strtok_r(NULL, ",", &p); if (str) { tempMyS[i] = atof(str); } else break; } for (int i = 0; i < MYS_MAX_SENSORS; i++) { //read MyS batts str = strtok_r(NULL, ",", &p); if (str) { battMyS[i] = atoi(str); } else break; } } } void(* reset_func) (void) = 0; //declare reset function @ address 0 void setup() { pinMode(BUTTON_RIGHT_PIN, INPUT_PULLUP); pinMode(BUTTON_LEFT_PIN, INPUT_PULLUP); pinMode(BUTTON_UP_PIN, INPUT_PULLUP); pinMode(BUTTON_DOWN_PIN, INPUT_PULLUP); pinMode(BUTTON_ENTER_PIN, INPUT_PULLUP); LoadParameters(); //load hw-state from eeprom if (digitalRead(BUTTON_ENTER_PIN) == LOW) nrf_stat = OFF; //to power up without NRF24, hold down enter button on startup (no error state) //startup hardware pinMode(ONE_WIRE_GND, OUTPUT); pinMode(ONE_WIRE_VCC, OUTPUT); digitalWrite(ONE_WIRE_GND, LOW); digitalWrite(ONE_WIRE_VCC, HIGH); delay(10);//let the sensor power up. if (onewire_stat != OFF) { setup_onewire(); } if (dht_stat != OFF) { setup_dht(); } if (rtc_stat != OFF) { setup_rtc(); } if (nrf_stat != OFF)//no ERR { gw.begin(RF24_PA_MAX, RF24_CUST_CHANNEL, RF24_DATARATE, incomingRadio); } else { Serial.begin(115200); } if (sd_stat != OFF) { setup_sd(); } if (sd_stat == ON) { //read last MyS-Data from SD-card readLastMyS(); } } void loop() { if ( (millis() - lastDHTTime > dht.getMinimumSamplingPeriod()) && (dht_stat == ON) ) //read DHT { tempDHT = dht.getTemperature(); humDHT = dht.getHumidity(); lastDHTTime = millis(); } if ( (millis() - lastOnewireTime >= 750) && (onewire_stat == ON) ) //read onewire-sensor { tempOnewire = sensors.getTempCByIndex(1); sensors.requestTemperatures(); lastOnewireTime = millis(); } t = now();//read time if (nrf_stat != OFF) {//process radio gw.processRadioMessage(); } updateDisplay(); readButtons(); updateState();//change display states depending on buttons //write SD if (sd_stat == ON && (minute(t) % WRITE_MINUTE == 0) && (millis() - lastSDWrite >= 120000)) { writeLogSD(); } } //read and debounce the buttons void readButtons() { //inverted for internal pullup boolean tempright = !digitalRead(BUTTON_RIGHT_PIN); boolean templeft = !digitalRead(BUTTON_LEFT_PIN); boolean tempup = !digitalRead(BUTTON_UP_PIN); boolean tempdown = !digitalRead(BUTTON_DOWN_PIN); boolean tempenter = !digitalRead(BUTTON_ENTER_PIN); if (right || left || up || down || enter) //any button last pressed { debouncetime = millis(); } if ( (millis() - debouncetime) > DEBOUNCE_DELAY ) { if (tempright && !lastright) right = tempright; if (templeft && !lastleft) left = templeft; if (tempup && !lastup) up = tempup; if (tempdown && !lastdown) down = tempdown; if (tempenter && !lastenter) enter = tempenter; } else { right = 0; //right stays high only for one loop left = 0; up = 0; down = 0; enter = 0; } lastright = tempright; lastleft = templeft; lastup = tempup; lastdown = tempdown; lastenter = tempenter; } //write line to log-file void writeLogSD() { File dataFile = SD.open(DATA_LOG, FILE_WRITE); if (dataFile) {//"time,temp-dht,humidity-dht,temp-onewire,temp1,temp2,temp3,temp4,temp5,temp6" //dataFile.print((float)(t) / 86400 + 25569,6); //excel timestamp dataFile.print(day(t) > 9 ? "" : "0"); dataFile.print(day(t)); dataFile.print("."); dataFile.print(month(t) > 9 ? "" : "0"); dataFile.print(month(t)); dataFile.print("."); dataFile.print(year(t)); dataFile.print(" "); dataFile.print(hour(t) > 9 ? "" : "0"); dataFile.print(hour(t)); dataFile.print(":"); dataFile.print(minute(t) > 9 ? "" : "0"); dataFile.print(minute(t)); dataFile.print(","); dataFile.print(tempDHT, 1); dataFile.print(","); dataFile.print(humDHT, 1); dataFile.print(","); dataFile.print(tempOnewire, 1); for (int i = 0; i < MYS_MAX_SENSORS; i++) { dataFile.print(","); dataFile.print(tempMyS[i], 1); } for (int i = 0; i < MYS_MAX_SENSORS; i++) { dataFile.print(","); dataFile.print(battMyS[i]); } dataFile.print("\n"); dataFile.close(); lastSDWrite = millis(); } } //returns the number of lines of the logfile unsigned long readSDFileLines() { unsigned long numLines = 0; if (sd_stat != ON) return 0; File dataFile = SD.open(DATA_LOG, FILE_READ); while (dataFile.find("\n")) { //find number of lines numLines++; } dataFile.close(); return numLines; } //read values from sd-card for the diagramm void readTempFromSD(uint8_t numToRead, unsigned long startRecord) { char buf[128]; char *str, *p; if (sd_stat != ON) return; File dataFile = SD.open(DATA_LOG, FILE_READ); for (int i = 0; i < startRecord; i++) { dataFile.find("\n"); } for (uint8_t i = 0; i < 128; i++) { dataFile.readBytesUntil('\n', buf, 127); //read a line uint8_t j = 0; for (str = strtok_r(buf, ",", &p); // split using comma str && j < numToRead; // loop while str is not null an max 5 times str = strtok_r(NULL, ",", &p) // get subsequent tokens ) { j++; } if (str) { diagram[i] = atof(str); } else { for (; i < 128; i++) { diagram[i] = -127; } break; } } dataFile.close(); } //print the diagram void printTempDiagram() { int maxtemp = (int) ceil(diagram[0]);//start with valid value int mintemp = (int) floor(diagram[0]);//start with valid value for (int i = 126; i >= 0; i--) { if (diagram[i] > maxtemp) { maxtemp = (int) ceil(diagram[i]); } if (diagram[i] < mintemp && diagram[i] > -127) { mintemp = (int) floor(diagram[i]); } } u8g.setFont(u8g_font_helvB08); u8g.setPrintPos(0, TOP_PXL + 8); u8g.print(maxtemp); //scale u8g.setPrintPos(0, BOT_PXL); u8g.print(mintemp); //scale if (diagramScroll) { u8g.setPrintPos(30, BOT_PXL); u8g.print("scroll "); //scale u8g.print(diagramStart); } u8g.setPrintPos(20, 8); if (numDiagram == 1) { u8g.print("Temp DHT"); } else if (numDiagram == 2) { u8g.print("Hum DHT"); } else if (numDiagram == 3) { u8g.print("Temp Onewire"); } else if (numDiagram > 3 && numDiagram <= (3 + MYS_MAX_SENSORS)) { u8g.print("Temp MyS "); u8g.print(numDiagram - 3); } else if (numDiagram <= (3 + MYS_MAX_SENSORS * 2)) { u8g.print("Batt MyS "); u8g.print(numDiagram - 3 - MYS_MAX_SENSORS); } for (int i = 0; i < 128; i++) //print array { float pxl = (diagram[i] - mintemp) * (BOT_PXL - TOP_PXL) / (-maxtemp + mintemp) + BOT_PXL; if (diagram[i] >= mintemp && diagram[i] <= maxtemp) //dont print if out of boundery { u8g.drawPixel(i, (int)pxl); } } } //dump the logfile to the serial port void dumpFile() { if (sd_stat == ON) { File dataFile = SD.open(DATA_LOG, FILE_READ); if (dataFile) { while (dataFile.available()) { Serial.write(dataFile.read()); } dataFile.close(); } } } //updates the state-variables depending on pressed buttons void updateState() { switch (dispstat) { case PAGE1: if (right == true) dispstat = PAGE2; if (left == true) dispstat = HW_SET; if (enter == true) dumpFile(); break; case PAGE2: if (right == true) dispstat = PAGE3; if (left == true) dispstat = PAGE1; if (up == true || down == true) dispOnewire = !dispOnewire; break; case PAGE3: if (right == true) dispstat = PAGE4; if (left == true) dispstat = PAGE2; if (up == true || down == true) dispOnewire = !dispOnewire; break; case PAGE4: if (right == true) dispstat = PAGE5; if (left == true) dispstat = PAGE3; if (up == true || down == true) dispOnewire = !dispOnewire; break; case PAGE5: if (right == true) dispstat = DIAGRAM; if (left == true) dispstat = PAGE4; break; case DIAGRAM: if (right == true) { if (diagramScroll == false) { dispstat = CLOCK; linesUpdate = false; } else { if (diagramStart < lines - 256 && lines > 128) { diagramStart += 128; } else { diagramStart = lines > 128 ? lines - 128 : 1; //Serial.print(diagramStart); } diagramRead = false; } } else if (left == true) { if (diagramScroll == false) { dispstat = PAGE5; linesUpdate = false; } else { if (diagramStart > 128) { diagramStart -= 128; } else { diagramStart = 1; } diagramRead = false; } } else if (enter == true) { diagramScroll = !diagramScroll; } //unsigned long diagramStart = 0; else if (up == true && numDiagram > 1) { numDiagram--; diagramRead = false; } else if (down == true && numDiagram < (3 + 2 * MYS_MAX_SENSORS)) { numDiagram++; diagramRead = false; } else if (!linesUpdate) { lines = readSDFileLines(); diagramStart = lines - 128; linesUpdate = true; } else if (!diagramRead) { //readTempFromSD(numDiagram, diagramStart); readTempFromSD(numDiagram, diagramStart); diagramRead = true; } break; case CLOCK: if (clock_set == false && right == true) dispstat = HW_SET; if (clock_set == false && left == true) dispstat = DIAGRAM; if (enter == true) clock_set = !clock_set; if (clock_set == true) { if (left == true) { switch (set_digit) { case SECOND: set_digit = MINUTE; break; case MINUTE: set_digit = HOUR; break; case HOUR: set_digit = YEAR; break; case YEAR: set_digit = MONTH; break; case MONTH: set_digit = DAY; break; case DAY: set_digit = SECOND; break; } } else if (right == true) { switch (set_digit) { case SECOND: set_digit = DAY; break; case MINUTE: set_digit = SECOND; break; case HOUR: set_digit = MINUTE; break; case YEAR: set_digit = HOUR; break; case MONTH: set_digit = YEAR; break; case DAY: set_digit = MONTH; break; } } if (up == true || down == true) { int inc = up - down; int d = day(t); int m = month(t); int y = year(t); int h = hour(t); int mi = minute(t); int s = second(t); switch (set_digit) { case SECOND: s += inc; if (s < 0) s = 59; if (s > 59) s = 0; break; case MINUTE: mi += inc; if (mi < 0) mi = 59; if (mi > 59) mi = 0; break; case HOUR: h += inc; if (h < 0) h = 23; if (h > 23) h = 0; break; case YEAR: y += inc; break; case MONTH: m += inc; if (m < 1) m = 12; if (m > 12) m = 1; break; case DAY: d += inc; uint8_t maxday = monthDays[m - 1]; if (LEAP_YEAR(y - 1970) && m == 2) maxday = 29; if (d < 1) d = maxday; if (d > maxday) d = 1; break; } //set time tmElements_t tm = {s, mi, h, 0, d, m, y - 1970}; t = makeTime(tm); if (rtc_stat == ON) { rtc.set(t); // set the RTC and the system time to the received value } setTime(t); } } break; case HW_SET: if (right == true) dispstat = PAGE1; if (left == true) dispstat = CLOCK; if (up == true) { switch (hw_page_stat) { case DHT_22: hw_page_stat = SD_CARD; break; case DS18B22: hw_page_stat = DHT_22; break; case RTC: hw_page_stat = DS18B22; break; case NRF: hw_page_stat = RTC; break; case SD_CARD: hw_page_stat = NRF; break; } } if (down == true) { switch (hw_page_stat) { case DHT_22: hw_page_stat = DS18B22; break; case DS18B22: hw_page_stat = RTC; break; case RTC: hw_page_stat = NRF; break; case NRF: hw_page_stat = SD_CARD; break; case SD_CARD: hw_page_stat = DHT_22; break; } } if (enter == true) { switch (hw_page_stat) { case DHT_22: if (dht_stat == ON) { dht_stat = OFF; tempDHT = 0; humDHT = 0; } else { dht_stat = ON; setup_dht(); } break; case DS18B22: if (onewire_stat == ON) { onewire_stat = OFF; tempOnewire = 0; } else { onewire_stat = ON; setup_onewire(); } break; case RTC://TBD if (rtc_stat == ON) { rtc_stat = OFF; setSyncProvider(NULL); } else { rtc_stat = ON; setup_rtc(); } break; case NRF: if (nrf_stat == ON) { nrf_stat = OFF; SaveParameters(); reset_func(); } else { nrf_stat = ON; SaveParameters(); reset_func(); } break; case SD_CARD: if (sd_stat == ON) { sd_stat = OFF; SD.end(); } else { sd_stat = ON; setup_sd(); } break; } SaveParameters(); } break; default: break; } } //updates the display depending on state-variables void updateDisplay() { u8g.firstPage(); do { switch (dispstat) { case PAGE1: u8g.setFont(u8g_font_helvB08); u8g.setPrintPos(40, 10); u8g.print(tempDHT, 1); u8g.print("\xb0 C "); u8g.print(humDHT, 1); u8g.print("%"); u8g.setPrintPos(40, 20); u8g.print(tempOnewire, 1); u8g.print("\xb0 C"); u8g.setPrintPos(40, 30); u8g.print(tempMyS[0], 1); u8g.print("\xb0 C "); u8g.print(tempMyS[1], 1); u8g.print("\xb0 C"); u8g.setPrintPos(40, 40); u8g.print(tempMyS[2], 1); u8g.print("\xb0 C "); u8g.print(tempMyS[3], 1); u8g.print("\xb0 C"); u8g.setPrintPos(0, 60); u8g.print(day(t) > 9 ? "" : "0"); u8g.print(day(t)); u8g.print("."); u8g.print(month(t) > 9 ? "" : "0"); u8g.print(month(t)); u8g.print("."); u8g.print(year(t)); u8g.print(" "); u8g.print(hour(t) > 9 ? "" : "0"); u8g.print(hour(t)); u8g.print(":"); u8g.print(minute(t) > 9 ? "" : "0"); u8g.print(minute(t)); u8g.print(":"); u8g.print(second(t) > 9 ? "" : "0"); u8g.print(second(t)); u8g.setFont(u8g_font_5x7); u8g.setPrintPos(0, 10); u8g.print("DHT:"); u8g.setPrintPos(0, 20); u8g.print("Dallas:"); u8g.setPrintPos(0, 30); u8g.print("MyS:"); break; case PAGE2: u8g.setFont(u8g_font_helvB18); u8g.setPrintPos(20, 30); u8g.print(dispOnewire ? tempOnewire : tempDHT, 1); u8g.print("\xb0 C"); u8g.setPrintPos(20, 60); u8g.print(humDHT, 1); u8g.print("%"); break; case PAGE3: u8g.setFont(u8g_font_helvB10); u8g.setPrintPos(10, 14); u8g.print(dispOnewire ? tempOnewire : tempDHT, 1); u8g.print("\xb0 C"); u8g.setPrintPos(74, 14); u8g.print(humDHT, 1); u8g.print("%"); u8g.setPrintPos(0, 38); u8g.setFont(u8g_font_5x7); u8g.print("1 "); u8g.setFont(u8g_font_helvB10); u8g.print(tempMyS[0], 1); u8g.print("\xb0 C "); u8g.setPrintPos(64, 38); u8g.setFont(u8g_font_5x7); u8g.print("2 "); u8g.setFont(u8g_font_helvB10); u8g.print(tempMyS[1], 1); u8g.print("\xb0 C "); u8g.setPrintPos(0, 60); u8g.setFont(u8g_font_5x7); u8g.print("3 "); u8g.setFont(u8g_font_helvB10); u8g.print(tempMyS[2], 1); u8g.print("\xb0 C "); u8g.setPrintPos(64, 60); u8g.setFont(u8g_font_5x7); u8g.print("4 "); u8g.setFont(u8g_font_helvB10); u8g.print(tempMyS[3], 1); u8g.print("\xb0 C "); break; case PAGE4: u8g.setFont(u8g_font_helvB18); u8g.setPrintPos(20, 30); u8g.print(dispOnewire ? tempOnewire : tempDHT, 1); u8g.print("\xb0 C"); u8g.setPrintPos(20, 60); u8g.print(tempMyS[0], 1); u8g.print("\xb0 C"); u8g.setFont(u8g_font_5x7); u8g.setPrintPos(0, 30); u8g.print("in"); u8g.setPrintPos(0, 60); u8g.print("out"); break; case PAGE5: for (uint8_t i = 0; i < MYS_MAX_SENSORS; i++) { u8g.setFont(u8g_font_helvB08); u8g.setPrintPos(0, 10 * (i + 1)); u8g.print(i + 1); u8g.print(":"); u8g.setPrintPos(10, 10 * (i + 1)); u8g.print(tempMyS[i], 1); u8g.print("\xb0 C"); u8g.setPrintPos(55, 10 * (i + 1)); u8g.print(battMyS[i]); u8g.print("%"); u8g.setPrintPos(90, 10 * (i + 1)); if (lastReadMyS[i] == 0) u8g.print("nie"); else if (t - lastReadMyS[i] < 60) { u8g.print(t - lastReadMyS[i]); u8g.print("s"); } else if (t - lastReadMyS[i] < 3600) { u8g.print((t - lastReadMyS[i]) / 60, 1); u8g.print("min"); } else { u8g.print((t - lastReadMyS[i]) / 3600, 1); u8g.print("h"); } } break; case DIAGRAM: printTempDiagram(); break; case CLOCK: u8g.setFont(u8g_font_helvB08); u8g.setPrintPos(14, 40); u8g.print(day(t) > 9 ? "" : "0"); u8g.print(day(t)); u8g.print("."); u8g.print(month(t) > 9 ? "" : "0"); u8g.print(month(t)); u8g.print("."); u8g.print(year(t)); u8g.print(" "); u8g.print(hour(t) > 9 ? "" : "0"); u8g.print(hour(t)); u8g.print(":"); u8g.print(minute(t) > 9 ? "" : "0"); u8g.print(minute(t)); u8g.print(":"); u8g.print(second(t) > 9 ? "" : "0"); u8g.print(second(t)); if (clock_set == true) { //u8g.setPrintPos(0,50); //u8g.print("set"); switch (set_digit) { case DAY: u8g.drawFrame(12, 30, 15, 12); break; case MONTH: u8g.drawFrame(27, 30, 15, 12); break; case YEAR: u8g.drawFrame(42, 30, 27, 12); break; case HOUR: u8g.drawFrame(69, 30, 15, 12); break; case MINUTE: u8g.drawFrame(84, 30, 15, 12); break; case SECOND: u8g.drawFrame(99, 30, 15, 12); break; } } break; case HW_SET: u8g.setFont(u8g_font_helvB08); u8g.setPrintPos(10, 10); u8g.print("DHT"); u8g.setPrintPos(70, 10); if (dht_stat == OFF) u8g.print("OFF"); if (dht_stat == ON) u8g.print("ON"); if (dht_stat == ERR) u8g.print("ERR"); u8g.setPrintPos(10, 20); u8g.print("DS18B20"); u8g.setPrintPos(70, 20); if (onewire_stat == OFF) u8g.print("OFF"); if (onewire_stat == ON) u8g.print("ON"); if (onewire_stat == ERR) u8g.print("ERR"); u8g.setPrintPos(10, 30); u8g.print("RTC"); u8g.setPrintPos(70, 30); if (rtc_stat == OFF) u8g.print("OFF"); if (rtc_stat == ON) u8g.print("ON"); if (rtc_stat == ERR) u8g.print("ERR"); u8g.setPrintPos(10, 40); u8g.print("NRF"); u8g.setPrintPos(70, 40); if (nrf_stat == OFF) u8g.print("OFF"); if (nrf_stat == ON) u8g.print("ON"); u8g.setPrintPos(10, 50); u8g.print("SD"); u8g.setPrintPos(70, 50); if (sd_stat == OFF) u8g.print("OFF"); if (sd_stat == ON) u8g.print("ON"); if (sd_stat == ERR) u8g.print("ERR"); u8g.drawBitmapP(0, 2 + (hw_page_stat) * 10, 1, 8, ARROW); break; default: break; } } while ( u8g.nextPage() ); } //callback-method called every time something arrives on the radio. void incomingRadio(char *message) { char *str, *p, *variable; uint8_t node = 0; uint8_t sensor = 0; uint8_t command = 0; uint8_t ack = 0; uint8_t type = 0; uint8_t i = 0; //log incoming message on SD-card if (sd_stat == ON) { File dataFile = SD.open(MYS_LOG, FILE_WRITE); if (dataFile) { dataFile.print(day(t) > 9 ? "" : "0"); dataFile.print(day(t)); dataFile.print("."); dataFile.print(month(t) > 9 ? "" : "0"); dataFile.print(month(t)); dataFile.print("."); dataFile.print(year(t)); dataFile.print(" "); dataFile.print(hour(t) > 9 ? "" : "0"); dataFile.print(hour(t)); dataFile.print(":"); dataFile.print(minute(t) > 9 ? "" : "0"); dataFile.print(minute(t)); dataFile.print(":"); dataFile.print(second(t) > 9 ? "" : "0"); dataFile.print(second(t)); dataFile.print(" "); dataFile.print(message); dataFile.close(); } } //process string for (str = strtok_r(message, ";", &p); // split using semicolon str && i < 6; // loop while str is not null an max 5 times str = strtok_r(NULL, ";", &p) // get subsequent tokens ) { switch (i) { case 0: // Radioid (destination) node = atoi(str); break; case 1: // Childid sensor = atoi(str); break; case 2: // Message type command = atoi(str); break; case 3: // Should we request ack from destination? ack = atoi(str); break; case 4: // Data type type = atoi(str); break; case 5: // Variable value variable = str; break; } i++; } if ( (node <= MYS_MAX_SENSORS) && (command == 1) && (type == 0) ) { //if temp-message: write to array tempMyS[node - 1] = atof(variable); lastReadMyS[node - 1] = t; } if ( (node <= MYS_MAX_SENSORS) && (command == 3) && (type == 0) ) { //battery battMyS[node - 1] = atoi(variable); } } //load parameters from eeprom (hardware-states) void LoadParameters() { dht_stat = (hw_stat_t) EEPROM.read(DHT_EEPROM_ADR); onewire_stat = (hw_stat_t) EEPROM.read(ONEWIRE_EEPROM_ADR); rtc_stat = (hw_stat_t) EEPROM.read(RTC_EEPROM_ADR); nrf_stat = (hw_stat_t) EEPROM.read(NRF_EEPROM_ADR); sd_stat = (hw_stat_t) EEPROM.read(SD_EEPROM_ADR); if (dht_stat == 0xff) dht_stat = ON; //after first boot, set state to one if (onewire_stat == 0xff) onewire_stat = ON; if (rtc_stat == 0xff) rtc_stat = ON; if (nrf_stat == 0xff) nrf_stat = ON; if (sd_stat == 0xff) sd_stat = ON; } //save parameters to eeprom (hardware-states) void SaveParameters() { if (dht_stat != (hw_stat_t) EEPROM.read(DHT_EEPROM_ADR)) EEPROM.write(DHT_EEPROM_ADR, (uint8_t) dht_stat); if (onewire_stat != (hw_stat_t) EEPROM.read(ONEWIRE_EEPROM_ADR)) EEPROM.write(ONEWIRE_EEPROM_ADR, (uint8_t) onewire_stat); if (rtc_stat != (hw_stat_t) EEPROM.read(RTC_EEPROM_ADR)) EEPROM.write(RTC_EEPROM_ADR, (uint8_t) rtc_stat); if (nrf_stat != (hw_stat_t) EEPROM.read(NRF_EEPROM_ADR)) EEPROM.write(NRF_EEPROM_ADR, (uint8_t) nrf_stat); if (sd_stat != (hw_stat_t) EEPROM.read(SD_EEPROM_ADR)) EEPROM.write(SD_EEPROM_ADR, (uint8_t) sd_stat); }
Hope you like it!
-
PS: The program was written for library-version 1.4. For 1.5 there are some minor changes necessary in the MySensors Constructor and the begin-statement.
-
Thanks for sharing. I like your coding,, clean and easy to read. Can you post some pictures of the representation on the OLED display? I am experimenting with the U8g lib which turns out to be a memory hog if combined with a few fonts. I was thinking of using an SD card for font storage but have not found good examples yet.
-
I will post some pictures soon (hope tomorrow).
-
Here are the Fotos
First page, containing most informations
Just the local DHT-Sensor
Like first page, but less information and bigger font:
Local temperature-Sensor plus first MySensor:
All MySensors-informations on one page: temperature-reading, battery level and last update:
This one is tricky: it reads the last 128 values from SD-Card and displays a diagram. Actually very slow and nearly unsuable
This page is for setting the RTC
Here is the hardware-configuration. Each hardware-entry can be deactivated and restarted after errors. Deactivating the nrf actually causes a reboot with deactivated MySensors.
It is true that the fonts use a lot of progmem, especially the big types. But I didn't like the look of the adafruit-library-font.
-
@fleinze Compliments! I have been browsing your code and noticed that you are using the same SPI bus for both the SD and nRF radio. Did you make any changes to the MySensors libraries?
-
@AWI: No, I use hardware SPI. I just changed the CE and CSN-pins.
-
Hi Fleinze. Our project is really closed to what I am planning to do: a node that receives data from others and make something with them (no gateway or controller,...).
You said your code is 1.4 compatible or easily portable to 1.5 but I do not find the MyGateway.h file, the Mygateway class, the right prototype of the begin function or, most important for me, the function gw.processRadioMessage in the 1.4 API or later.
Have I missed something in our description?
-
Hi @sbackaer !
The version 1.5 does not have the gateway class. I currently work with the development branch and I didn't use the 1.5 for quite some time; but I think it is enough to- remove the mygateway.h include
- replace the mygateway contructor with a mysensor constructor
- replace gw.processRadioMessage with gw.process()
Take a look at the serial gateway example.