Skip to content

Instantly share code, notes, and snippets.

@CAFxX
Last active July 25, 2020 07:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CAFxX/899710ba7144f29e07753def20886100 to your computer and use it in GitHub Desktop.
Save CAFxX/899710ba7144f29e07753def20886100 to your computer and use it in GitHub Desktop.

WeatherStation

WIP

const byte CCHAR_SUB_2[8] PROGMEM = {
B00000,
B00000,
B00000,
B11000,
B00100,
B01000,
B10000,
B11100
};
const byte CCHAR_PLACEHOLDER_START[8] PROGMEM = {
B10101,
B00000,
B10000,
B00000,
B10000,
B00000,
B10101,
B00000
};
const byte CCHAR_PLACEHOLDER_END[8] PROGMEM = {
B10101,
B00000,
B00001,
B00000,
B00001,
B00000,
B10101,
B00000
};
const byte CCHAR_PLACEHOLDER_MIDDLE[8] PROGMEM = {
B10101,
B00000,
B00000,
B00000,
B00000,
B00000,
B10101,
B00000
};
const byte CCHAR_ICON_RADIO_SIGNAL[8] PROGMEM = {
B00000,
B00000,
B11100,
B00010,
B11001,
B00101,
B10101,
B00000
};
void loadCustomChars() {
loadCustomCharP(1, CCHAR_SUB_2);
loadCustomCharP(2, CCHAR_PLACEHOLDER_START);
loadCustomCharP(3, CCHAR_PLACEHOLDER_END);
loadCustomCharP(4, CCHAR_PLACEHOLDER_MIDDLE);
loadCustomCharP(5, CCHAR_ICON_RADIO_SIGNAL);
}
void loadCustomCharP(byte idx, const byte c[8]) {
byte buf[8];
memcpy_P(buf, c, 8);
lcd.createChar(idx, buf);
}
bool drawGraph(byte n, byte k, byte *data) {
if (n > 8 || n < 1) return false;
for (int i = 0; i < n; i++) {
byte buf[8];
for (int j = 0; j < 8; j++) {
switch (k) {
case 1:
buf[7 - j] = (data[i * 1 + 0] > j ? B11111 : 0);
case 2:
buf[7 - j] = (data[i * 2 + 0] > j ? B11000 : 0) | (data[i * 2 + 1] > j ? B00011 : 0);
case 3:
buf[7 - j] = (data[i * 3 + 0] > j ? B10000 : 0) | (data[i * 3 + 1] > j ? B00100 : 0) | (data[i * 3 + 2] > j ? B00001 : 0);
case 6:
buf[7 - j] = (data[i * 6 + 1] > j ? B10000 : 0) | (data[i * 6 + 2] > j ? B01000 : 0) | (data[i * 6 + 3] > j ? B00100 : 0) | (data[i * 6 + 4] > j ? B00010 : 0) | (data[i * 6 + 5] > j ? B00001 : 0);
default:
return false;
}
}
lcd.createChar(i, buf);
}
return true;
}
#include <Wire.h>
#include <BMx280I2C.h>
#include <NeoSWSerial.h>
#include <TinyGPS.h>
#include <EEPROM.h>
#include <IRremote.h>
#include <IRremoteInt.h>
#include <LowPower.h>
#include <LiquidCrystal.h>
#include <dht_nonblocking.h>
#include <limits.h>
#define UNKNOWN_INT (INT_MIN/2)
#define UNKNOWN_ULONG (LONG_MIN/2)
#define PIN_LCD_RS 12
#define PIN_LCD_ENABLE 11
#define PIN_LCD_D4 5
#define PIN_LCD_D5 4
#define PIN_LCD_D6 3
#define PIN_LCD_D7 2
#define PIN_LCD_BCKLGT 8
#define PIN_DHT 10
#define PIN_IR 7
#define PIN_GPS_RX A0
#define PIN_GPS_TX A1
#define I2C_BMP280 0x76
#define TIMEZONE 9 // hours
#define ALTITUDE 35 // m
#define DEBUG 1
LiquidCrystal lcd(PIN_LCD_RS, PIN_LCD_ENABLE, PIN_LCD_D4, PIN_LCD_D5, PIN_LCD_D6, PIN_LCD_D7);
DHT_nonblocking dht(PIN_DHT, DHT_TYPE_11);
IRrecv irrecv(PIN_IR);
NeoSWSerial gpsSerial(PIN_GPS_RX, PIN_GPS_TX);
TinyGPS gps;
BMx280I2C bmp280(I2C_BMP280);
bool disp_on = false;
char disp_mode = 0;
unsigned long disp_last_button = UNKNOWN_ULONG;
#pragma GCC cold
void setup() {
#if DEBUG
Serial.begin(115200);
while (!Serial);
Serial.println(F("\n\n\n==============\nWeatherStation\n=============="));
#endif
lcd.begin(16, 2);
loadCustomChars();
pinMode(PIN_LCD_BCKLGT, OUTPUT);
digitalWrite(PIN_LCD_BCKLGT, HIGH);
disp_on = true;
irrecv.enableIRIn();
gpsSerial.begin(9600);
Wire.begin();
if (!bmp280.begin()) {
#if DEBUG
Serial.println(F("BMP280 connection failed"));
#endif
} else {
bmp280.resetToDefaults();
bmp280.writeOversamplingPressure(BMx280MI::OSRS_P_x16);
bmp280.writeOversamplingTemperature(BMx280MI::OSRS_T_x02);
bmp280.writeFilterSetting(BMx280MI::FILTER_x16);
bmp280.writeStandbyTime(BMx280MI::T_SB_7);
#if DEBUG
if (bmp280.readOversamplingTemperature() != BMx280MI::OSRS_T_x02)
Serial.println(F("BMP280 failed setting temperature oversampling"));
if (bmp280.readOversamplingPressure() != BMx280MI::OSRS_P_x16)
Serial.println(F("BMP280 failed setting pressure oversampling"));
if (bmp280.readFilterSetting() != BMx280MI::FILTER_x16)
Serial.println(F("BMP280 failed setting filter"));
if (bmp280.readStandbyTime() != BMx280MI::T_SB_7)
Serial.println(F("BMP280 failed setting standby time"));
#endif
}
#if DEBUG
Serial.println(F("setup complete"));
#endif
}
#pragma GCC optimize("O2")
void loop() {
unsigned long m = millis();
static float temp, humid;
bool ok = measure(&temp, &humid, m);
int disp_humid = UNKNOWN_INT;
if (ok) {
if (humid > 99)
disp_humid = 99;
else if (humid < 0)
disp_humid = 0;
else
disp_humid = int(humid);
}
int disp_temp = UNKNOWN_INT;
if (ok) {
if (temp > 99)
disp_temp = 99;
else if (temp < -9)
disp_temp = -9;
else
disp_temp = int(temp);
}
int disp_pressure = UNKNOWN_INT;
if (bmp280.measure() && bmp280.hasValue()) {
float temperature = bmp280.getTemperature();
float pressure = bmp280.getPressure64(); // Pa
float sealevel_pressure = sealevel(pressure, ALTITUDE); // Pa
disp_pressure = roundf(sealevel_pressure / 100.0); // hPa
#if DEBUG
Serial.print(F("BMP280 > "));
Serial.print(pressure);
Serial.print(F(" "));
Serial.print(sealevel_pressure);
Serial.print(F(" "));
Serial.println(temperature);
#endif
}
static long gpstime = -1;
static unsigned short gpssat = -1;
static long gpslat = -1;
static long gpslon = -1;
if (gpsSerial.available()) {
if (gps.encode(gpsSerial.read())) {
byte hours, minutes, seconds;
gps.crack_datetime(NULL, NULL, NULL, &hours, &minutes, &seconds, NULL, NULL);
hours = (hours + TIMEZONE) % 24;
gpstime = (unsigned long)(hours) * 3600 + (unsigned long)(minutes) * 60 + (unsigned long)(seconds);
gpssat = gps.satellites();
gps.get_position(&gpslat, &gpslon);
#if DEBUG
Serial.print(F("NEO-6M > "));
Serial.print(gpstime); Serial.print(F(" "));
Serial.print(gpslat); Serial.print(F(" "));
Serial.print(gpslon); Serial.print(F(" "));
Serial.println(gpssat);
#endif
}
}
bool refresh = false;
decode_results results;
if (irrecv.decode(&results)) {
refresh = parseCommand(results.value);
irrecv.resume();
EEPROM.update(1, disp_mode);
}
if (disp_mode == 0) {
disp_mode = EEPROM.read(1);
if (disp_mode == 0)
disp_mode = 1;
refresh = true;
}
if (!disp_on) {
return;
}
if (refresh) {
lcd.clear();
}
switch (disp_mode) {
case 1: draw1(refresh, disp_temp, disp_humid, disp_pressure, gpstime); break;
case 2: draw2(); break;
case 3: draw3(refresh, false, gpslat, gpslon, gpssat); break;
case 4: draw3(refresh, true, gpslat, gpslon, gpssat); break;
}
}
static bool measure(float *temp, float *humid, unsigned long now) {
static unsigned long last_measure = -60000UL;
if (dht.measure(temp, humid)) {
last_measure = now;
#if DEBUG
Serial.print(F("DHT11 > "));
Serial.print(*temp);
Serial.print(F(" "));
Serial.println(*humid);
#endif
}
return now - last_measure < 60000UL;
}
float sealevel(float pressure, float altitude) {
return pressure / pow( 1.0 - altitude / 44330.0, 5.255 );
}
static bool parseCommand(unsigned long cmd) {
if (cmd != 0xFFFFFFFFUL)
disp_last_button = cmd;
int prev_disp_mode = disp_mode;
switch (cmd) {
case 0xFF30CF: // 1
disp_mode = 1;
return disp_mode != prev_disp_mode;
case 0xFF18E7: // 2
disp_mode = 2;
return disp_mode != prev_disp_mode;
case 0xFF7A85: // 3
disp_mode = 3;
return disp_mode != prev_disp_mode;
case 0xFF10EF: // 4
disp_mode = 4;
return disp_mode != prev_disp_mode;
case 0xFF38C7: // 5
disp_mode = 5;
return disp_mode != prev_disp_mode;
case 0xFFA25D: // on/off
if (disp_on) {
lcd.noDisplay();
digitalWrite(PIN_LCD_BCKLGT, LOW);
disp_on = false;
} else {
lcd.display();
digitalWrite(PIN_LCD_BCKLGT, HIGH);
disp_on = true;
}
return disp_on;
default:
return false;
}
}
static void draw1(bool force, int temp, int humid, int pressure, unsigned long now) {
static int prev_temp = UNKNOWN_INT;
static int prev_humid = UNKNOWN_INT;
static int prev_pressure = UNKNOWN_INT;
static unsigned long prev_time = UNKNOWN_ULONG;
if (pressure == UNKNOWN_INT) pressure = prev_pressure;
bool ref1 = false, ref2 = false;
if (now != prev_time) ref2 = true;
if (pressure != prev_pressure) ref2 = true;
if (temp != prev_temp) ref1 = true;
if (humid != prev_humid) ref1 = true;
if (force) ref1 = ref2 = true;
refresh:
prev_temp = temp;
prev_humid = humid;
prev_pressure = pressure;
prev_time = now;
if (ref1) {
if (temp != UNKNOWN_INT)
disp_print(0, 0, PSTR("%2d\xDF""C %2d%% CO\1\2\4\4\3"), temp, humid);
else
disp_print(0, 0, PSTR("\2\3\xDF""C \2\3%% CO\1\2\4\4\3"));
}
if (ref2) {
if (now == -1 && pressure == UNKNOWN_INT)
disp_print(0, 1, PSTR("\2\4\4\3hPa \2\3:\2\3:\2\3"));
else if (pressure == UNKNOWN_INT)
disp_print(0, 1, PSTR("\2\4\4\3hPa %02d:%02d:%02d"), int((now / 60 / 60) % 60), int((now / 60) % 60), int((now) % 60));
else if (now == -1)
disp_print(0, 1, PSTR("%4dhPa \2\3:\2\3:\2\3"), pressure);
else
disp_print(0, 1, PSTR("%4dhPa %02d:%02d:%02d"), pressure, int((now / 60 / 60) % 60), int((now / 60) % 60), int((now) % 60));
}
}
static void draw2() {
disp_print(0, 0, PSTR("IR code %08lX"), disp_last_button);
}
static void draw3(bool force, bool alt, long gpslat, long gpslon, unsigned short gpssat) {
static long prev_gpslat = 0;
static long prev_gpslon = 0;
static unsigned short prev_gpssat = 0;
bool ref1 = false, ref2 = false;
if (gpslat != prev_gpslat) ref1 = true;
if (gpslon != prev_gpslon) ref2 = true;
if (gpssat != prev_gpssat) ref2 = true;
if (force) ref1 = ref2 = true;
prev_gpslat = gpslat;
prev_gpslon = gpslon;
prev_gpssat = gpssat;
if (!alt) {
if (ref1) disp_print(0, 0, PSTR("GPS %4ld.%06ld%c"), (gpslat / 1000000), ((gpslat >= 0 ? gpslat : -gpslat) % 1000000), (gpslat >= 0 ? 'N' : 'S'));
if (ref2) disp_print(0, 1, PSTR("\5%2d %4ld.%06ld%c"), gpssat > 99 ? 99 : gpssat, (gpslon / 1000000), ((gpslon >= 0 ? gpslon : -gpslon) % 1000000), (gpslon >= 0 ? 'E' : 'W'));
} else {
if (ref1) {
int gpslatdeg, gpslatmin, gpslatsec, gpslatmsec;
bool gpslatneg;
gpsdec2deg(gpslat, &gpslatdeg, &gpslatmin, &gpslatsec, &gpslatmsec, &gpslatneg);
disp_print(0, 0, PSTR("%1d %3d\xDF%02d'%02d\"%03d%c"), gpssat > 9 ? 9 : gpssat, gpslatdeg, gpslatmin, gpslatsec, gpslatmsec, (gpslatneg ? 'S' : 'N'));
}
if (ref2) {
int gpslondeg, gpslonmin, gpslonsec, gpslonmsec;
bool gpslonneg;
gpsdec2deg(gpslon, &gpslondeg, &gpslonmin, &gpslonsec, &gpslonmsec, &gpslonneg);
disp_print(0, 1, PSTR("\5 %3d\xDF%02d'%02d\"%03d%c"), gpslondeg, gpslonmin, gpslonsec, gpslonmsec, (gpslonneg ? 'W' : 'E'));
}
}
}
static void gpsdec2deg(long dec, int *deg, int *min, int *sec, int *msec, bool *neg) {
if (neg != NULL) *neg = dec < 0;
if (dec < 0) dec = -dec;
long _sec = dec * 9 / 2500; // * 3600 / 1000000
if (msec != NULL) *msec = ((dec * 9) - (_sec * 2500)) * 1000 / 2500;
if (deg != NULL) *deg = _sec / 3600;
if (min != NULL) *min = (_sec / 60) % 60;
if (sec != NULL) *sec = _sec % 60;
}
static void disp_print(int col, int row, const char* fmt, ...) {
char data[16 - col + 1];
va_list args;
va_start(args, fmt);
vsnprintf_P(data, sizeof(data), fmt, args);
va_end(args);
lcd.setCursor(col, row);
lcd.print(data);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment