// mySQMPRO a DIY Arduino Nano Project // Sky Quality Meter, Cloud Sensor, Rain Sensor, Barometric Sensor, GPS, Lux Sensor // (c) Copyright Robert Brown 2014-2019. All Rights Reserved. // This project is protected by International Copyright Law. // https://sourceforge.net/projects/arduinomysqmskyqualitymeter/files/mySQMPRO/ Original Source // Permission is granted for personal and Academic/Educational use only. // Software distributed under MIT License https://opensource.org/licenses/MIT // // THis fork which extent the code with WIfi based up on the NodeMCU 12E // Created By Chris ALberts // // // REQUIRES LCD1602, LCD1604, LCD2004 or OLED I2C DISPLAY // PCB (Version 5) can be ordered online at https://aisler.net/p/JHYFZBXW // CONTRIBUTIONS // If you wish to make a contribution in thanks for this project, please use PayPal and send the amount // to user rbb1brown@gmail.com (Robert Brown). All contributions are gratefully accepted. // based loosely on code from http://forum.arduino.cc/index.php?PHPSESSID=knhnejk2g00u2smd8ve8tn5l82&topic=21536.45 // and http://stargazerslounge.com/topic/183600-arduino-sky-quality-meter-working/ // Acknowledgements: madepablo, Martin Nawrath (Frequency Counter Library), Corpze // // ------------------------------------------------------------------------------------------------------ // HARDWARE MAPPINGS // Connection for TSL237 Sensor // VDD (pin2) to +5V (Note: Use a 0.1uf ceramic capacitor mounted close to the sensor across VDD and GND) // GND (pin1) to GND // VOUT (pin3) to Arduino Pin D5 // Connection for NEO-6M-GPS // GPS-TXD to Arduino Pin D4 // GPS-RXD to Arduino Pin D3 // GND to GND // VCC to +5V // Connection for LCD2004 or LCD1604 OR LCD1602 I2C display // VCC to +5V // GND to GND // SDA to A4 // SCL to A5 // Connection for MLX90614 IR Sensor - be careful what I2C connector you connect this to // Connecting to 5V will destroy this sensor // VCC 3.3V // GND GND // SDA A4 // SCL A5 // Connection for TSL2561 Sensor - be careful what I2C connector you connect this to // Connecting to 5V will destroy this sensor // VCC 3.3V // GND GND // SDA A4 // SCL A5 // If not fitted, then GL5528 LDR will be used for lux // Connection for GL5528 LDR // 5V - GL5528 - Arduino pin A0 - 10K Pulldown resistor - GND // Connection for Rain Sensor // AO to Arduino pin A1 // DO to Arduino pin D2 // GND GND // VCC 5V // Connection for BME280 Barometric Sensor, uses I2C interface - make sure you purchase the 5V version // VCC 5V // GND GND // SDA A4 // SCL A5 // HARDWARE MAPPINGS END // ------------------------------------------------------------------------------------------------------ // CONFIG SECTION // to use TSL2561 sensor for determining lux, uncomment the next line #define TSL2561SENSOR 1 // To enable GPS, uncomment the next line //#define useGPSNEO 1 // To enable the rain sensor, uncomment the next line #define RAINSENSOR 1 // To enable the MLX90614 ir sensor uncomment the next line #define MLX90614SENSOR 1 // To enable the BME280 humdity, ambient temp, barmetric pressure sensor uncomment the next line #define BME280SENSOR 1 // YOU MUST CHOOSE ONE OF THE FOLLOWING SENSORS, not both, recommended is TSL237 //#define TSL235 1 #define TSL237 2 // To enable the LCD DISPLAY uncomment the next line #define LCDDISPLAY 1 // to enable LCD1602 or LCD1604 or LCD2004, only uncomment one or the other below - you must have only ONE uncommented //#define LCD1602 1 //#define LCD1604 2 #define LCD2004 3 // To enable the OLED display (SSD1306 chip) uncomment the next line //#define OLEDDISPLAY 1 #define LCDADDRESS 0x27 // some LCD displays maybe at 0x3F, use I2Cscanner to find the correct address #define OLEDADDRESS 0x3C // address of OLED, do not change // do not change #ifdef LCDDISPLAY #ifdef OLEDDISPLAY #halt //Error - you can only have one LCD type defined - either LCDDISPLAY or OLEDDISPLAY. #endif #endif // do not change #ifdef LCDDISPLAY #ifdef LCD1602 #ifdef LCD1604 #halt //Error - you can only have one LCD type defined, one of LCD1602, LCD1604 or LCD2004. #endif #endif #endif // do not change #ifdef LCDDISPLAY #ifdef LCD1602 #ifdef LCD2004 #halt //Error - you can only have one LCD type defined, one of LCD1602, LCD1604 or LCD2004. #endif #endif #endif // do not change #ifdef LCDDISPLAY #ifdef LCD1604 #ifdef LCD2004 #halt //Error - you can only have one LCD type defined, one of LCD1602, LCD1604 or LCD2004. #endif #endif #endif // do not change #ifdef LCDDISPLAY #ifdef LCD1602 #ifdef LCD2004 #ifdef LCD1604 #halt //Error - you can only have one LCD type defined, one of LCD1602, LCD1604 or LCD2004. #endif #endif #endif #endif // do not change #ifdef LCDDISPLAY #ifndef LCD2004 #ifndef LCD1604 #ifndef LCD1602 #halt //Error - you can must define either LCD1602 or LCD2004 or LCD1604. #endif #endif #endif #endif // do not change #ifndef TSL235 #ifndef TSL237 #halt Error - you must define ONE TSL SENSOR TYPE #endif #endif // YOU MUST CHOOSE ONE OF THE FOLLOWING METHODS TO CALCULATE THE SQM VALUE //#define OLDSQMMETHOD 1 //#define NEWSQMMETHOD 2 //#define NEWSQMMETHODCORRECTED 3 #define IRRADIANCEMETHOD 4 // do not change #ifdef LCDDISPLAY #ifdef LCD1602 #ifdef LCD2004 #halt // Error, you cannot have both LCD1602 and LCD2004 uncommented at the same time #endif #endif #endif // do not change #ifdef LCDDISPLAY #ifdef LCD1602 #ifdef LCD1604 #halt // Error, you cannot have both LCD1602 and LCD1604 uncommented at the same time #endif #endif #endif // do not change #ifdef LCDDISPLAY #ifdef LCD1604 #ifdef LCD2004 #halt // Error, you cannot have both LCD1604 and LCD2004 uncommented at the same time #endif #endif #endif // do not change #ifdef LCDDISPLAY #ifndef LCD1602 #ifndef LCD2004 #ifndef LCD1604 #halt // Error, you must have either LCD1602 OR LCD1604 OR LCD2004 uncommented #endif #endif #endif #endif // do not change #ifndef OLDSQMMETHOD #ifndef NEWSQMMETHOD #ifndef NEWSQMMETHODCORRECTED #ifndef IRRADIANCEMETHOD #halt Error - you must define ONE sqm method above #endif #endif #endif #endif // do not change #ifdef OLDSQMMETHOD #ifdef NEWSQMMETHOD #halt Error - cannot have more than one SQM method defined #else #ifdef NEWSQMMETHODCORRECTED #halt Error - cannot have more than one SQM method defined #else #ifdef IRRADIANCEMETHOD #halt Error - cannot have more than one SQM method defined #endif #endif #endif #endif // do not change #ifdef NEWSQMMETHOD #ifdef OLDSQMMETHOD #halt Error - cannot have more than one SQM method defined #else #ifdef NEWSQMMETHODCORRECTED #halt Error - cannot have more than one SQM method defined #else #ifdef IRRADIANCEMETHOD #halt Error - cannot have more than one SQM method defined #endif #endif #endif #endif // do not change #ifdef NEWSQMMETHODCORRECTED #ifdef OLDSQMMETHOD #halt Error - cannot have more than one SQM method defined #else #ifdef NEWSQMMETHOD #halt Error - cannot have more than one SQM method defined #else #ifdef IRRADIANCEMETHOD #halt Error - cannot have more than one SQM method defined #endif #endif #endif #endif // do not change #ifdef IRRADIANCEMETHOD #ifdef OLDSQMMETHOD #halt Error - cannot have more than one SQM method defined #else #ifdef NEWSQMMETHOD #halt Error - cannot have more than one SQM method defined #else #ifdef NEWSQMMETHODCORRECTED #halt Error - cannot have more than one SQM method defined #endif #endif #endif #endif // do not change #ifdef OLDSQMMETHOD #define sqm_limit 21.83 // mag limit for earth is 21.95 #endif #ifdef NEWSQMMETHOD #define sqm_limit 23.09 // mag limit for earth is 21.95 #endif #ifdef NEWSQMMETHODCORRECTED //#define sqm_limit 21.95 // mag limit for earth is 21.95 #define sqm_limit 20 // mostaccurate within 18-22 mpsas #endif #ifdef IRRADIANCEMETHOD #define sqm_limit 21.95 // mag limit for earth is 21.95 #endif #include // Chris #include // Chris #include #include // By Steven de Salas #include // required for frequency measurement of TLS sensor, (c) Martin Nawrath #include // required for log() #ifdef LCDDISPLAY #include #include #include // needed for LCD, see https://bitbucket.org/fmalpartida/new-liquidcrystal/downloads #endif #ifdef OLEDDISPLAY #include // needed for I2C, installed when installing the Arduino IDE #include // oled #include // oled #endif #ifdef useGPSNEO #include // reguired for GPS, (c) Adafruit #endif #ifdef MLX90614SENSOR #include // IR sensor, (c) Adafruit #endif #ifdef BME280SENSOR #include #endif #ifdef TSL2561SENSOR #include #endif // Chris // Connect the TX line from the ESP module to the Arduino's pin 2 // and the RX line from the ESP module to the Arduino's pin 3 // Emulate EspSerial on pins 2/3 if not present SoftwareSerial s(8, 9); // RX, TX // ------------------------------------------------------------------------------------------------------ // GLOBAL DEFINES #define rs_AO A1 // the rain sensor analog port pin, voltage #define rs_DO 2 // the rain sensor digital port pin, raining=yes/no #define BME280_I2CADDR 0x76 #define LCDADDRESS 0x27 #define OLEDADDRESS 0x3C // address of OLED #define GPSRXPin 4 #define GPSTXPin 3 // the gps transmits to Arduino to pin D4, Arduino transmits to GPS using Pin3 to RXD #define SENSORSAMPLETIME 1500 // time between updates of sensors #define SQMSAMPLETIME 5000 // an SQM sample is taken every 5s (cannot be less than 5s) #define LCDUPDATETIME 4000 // LCD screen updated every 4.5s #define buf_size 20 // size of temporary buffers for string conversions #define timebuf_size 12 #define datebuf_size 12 #define SerialPortSpeed 9600 // Note that to talk to APT as a Sky Meter needs to be 115200 #define GPSPortSpeed 9600 // The speed at which the controller talks to a GPS unit #define LDRCutoff1 275 // boundary between black and dark, used to determine Gate time #define LDRCutoff2 500 // boundary between dark and light #define blackperiod 2000 // 2s Gate time, at very dark sites #define darkperiod 1000 // 1s Gate time, at dark sites #define lightperiod 100 // 100ms Gate time for day light #define MAXCOMMAND 8 // size of receive buffer, :xxxx# #define SKYCLEAR 0 #define SKYPCLOUDY 1 #define SKYCLOUDY 2 #define SKYUNKNOWN 3 // ------------------------------------------------------------------------------------------------------ // PROGRAM VARIABLES char programName[] = "MYSQMPRO and Wifi"; // Chris Program title and version information byte programVersion = 26; int setpoint1; // setpoint values used to determine sky state int setpoint2; int skystate; long sensorsampletimer; // used to determine when a sample sensor reading is taken bool sensorsamplerequest; // determines which sensors to read long sqmsampletimer; // used to determine when a sample sqm reading is taken long lcdsampletimer; long now; double bme280humidity; double bme280temperature; unsigned long int bme280pressure; float dewpoint; float mlx90614ambient; float mlx90614object; double lux; bool raining; int rainVout; int volts; #ifdef useGPSNEO SoftwareSerial ss(GPSRXPin, GPSTXPin); // define software serial port for GPS Adafruit_GPS GPS(&ss); // create GPS object char mytime[timebuf_size]; // holds formatted time string from GPS data char mydate[datebuf_size]; // holds formatted date string from GPS data boolean usingInterrupt = true; void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy #endif #ifdef LCDDISPLAY LiquidCrystal_I2C lcd(LCDADDRESS, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); int lcdpage; // which page to display #endif #ifdef OLEDDISPLAY SSD1306AsciiWire myoled; int lcdpage; // which page to display #endif // TSL237 Sensor #define mySQMSensor 5 // TSL237 OUT to digital pin 5 (cannot change) float mySQMreading; // the SQM value, sky magnitude double frequency; // measured TSL237 frequency which is dependent on light double irradiance; double nelm; #ifdef TSL2561SENSOR TSL2561 myTSL2561(TSL2561_ADDR_FLOAT); #endif // LDR GL5528 int LDRpin = A0; // Light Dependant Resistor is on A0 short period; // gate time - specifies duration time for frequency measurement // IR Sensor, used as a cloud sensor, values help define sky state - cloudy, partly cloudy, clear #ifdef MLX90614SENSOR Adafruit_MLX90614 mymlx90614 = Adafruit_MLX90614(); #endif // Serial command interface Queue queue(10); // receive serial queue of commands char line[MAXCOMMAND]; int eoc; // end of command int idx; // index into command string #ifdef BME280SENSOR bme280 mybme280; #endif // ------------------------------------------------------------------------------------------------------ // METHODS START // calculates dew point // input: humidity [%RH], temperature in C // output: dew point in C void calc_dewpoint(float t, float h) { float logEx; logEx = 0.66077 + 7.5 * t / (237.3 + t) + (log10(h) - 2); dewpoint = (logEx - 0.66077) * 237.3 / (0.66077 + 7.5 - logEx); } void getskystate() { #ifdef useGPSNEO GPS.pause(true); #endif #ifdef MLX90614SENSOR float TempDiff = mlx90614ambient - mlx90614object; // object temp is IR temp of sky which at night time will be a lot less than ambient temp // so TempDiff is basically ambient + abs(object) // setpoint 1 is set for clear skies // setpoint 2 is set for cloudy skies // setpoint2 should be lower than setpoint1 // For clear, Object will be very low, so TempDiff is largest // For cloudy, Object is closer to ambient, so TempDiff will be lowest // Readings are only valid at night when dark and sensor is pointed to sky // During the day readings are meaningless if ( TempDiff > setpoint1 ) skystate = SKYCLEAR; // clear else if ( (TempDiff > setpoint2) && (TempDiff < setpoint1) ) skystate = SKYPCLOUDY; // partly cloudy else if (TempDiff < setpoint2) skystate = SKYCLOUDY; // cloudy else skystate = SKYUNKNOWN; // unknown #ifdef useGPSNEO GPS.pause(false); #endif #endif } void getraining() { #ifdef RAINSENSOR if ( !digitalRead( rs_DO ) ) raining = true; else raining = false; rainVout = analogRead( rs_AO ); #endif } // convert string to int int decstr2int(String line) { int ret = 0; ret = line.toInt(); return ret; } void processCommand() { int len; int cmdval; String replystr = ""; String mycmd = ""; String param = ""; #ifdef useGPSNEO GPS.pause(true); #endif replystr = queue.pop(); len = replystr.length(); if ( len == 2 ) { mycmd = replystr; // a valid command with no parameters, ie, :01# } else if ( len > 2 ) { mycmd = replystr.substring(0, 2); // this command has parameters param = replystr.substring(2, len); } else return; cmdval = decstr2int(mycmd); #ifdef DEBUG Serial.print("replystr = "); Serial.println(replystr); Serial.print("len = "); Serial.println(len); Serial.print("mycmd = "); Serial.println(mycmd); Serial.print("param = "); Serial.println(param); Serial.print("cmdval = "); Serial.println(cmdval); #endif switch ( cmdval ) { case 1: // if the command is GR "GetReading" replystr = "A" + String(mySQMreading) + "#"; Serial.print(replystr); break; case 2: // if the command is GF "Get Frequency" replystr = "B" + String(frequency) + "#"; Serial.print(replystr); break; case 3: // if the command is GI "Get Irradiance" replystr = "C" + String(irradiance) + "#"; Serial.print(replystr); break; case 4: // if the command is GV "Get Firmware Version" replystr = "D" + String(programVersion) + "#"; Serial.print(replystr); break; case 5: // if the command is GZ "Get Firmware Filename" replystr = "E" + String(programName) + "#"; Serial.print(replystr); break; case 6: // if the command is GL "Get LDR value" { int LDRval = analogRead(LDRpin); // Read the analogue pin replystr = "F" + String(LDRval) + "#"; Serial.print(replystr); } break; case 7: // if the command is GP "Get period gate time" replystr = "G" + String(period) + "#"; Serial.print(replystr); break; case 8: // if the command is GD "Get Date" #ifdef useGPSNEO replystr = "H" + String(GPS.day) + "/" + String(GPS.month) + "/20" + String(GPS.year) + "#"; #else replystr = "H01/01/2001#"; #endif Serial.print(replystr); break; case 9: // if the command is GT "Get Time" #ifdef useGPSNEO replystr = "I" + String(GPS.hour) + ":" + String(GPS.minute) + ":" + String(GPS.seconds) + "#"; #else replystr = "I00:00:00#"; #endif Serial.print(replystr); break; case 10: // if the command is GO "Get Longitude" #ifdef useGPSNEO replystr = "J" + String(GPS.longitude / 100) + String(GPS.lon) + "#"; #else replystr = "J00#"; #endif Serial.print(replystr); break; case 11: // if the command is GA "Get Latitude" #ifdef useGPSNEO replystr = "K" + String(GPS.latitude / 100) + String(GPS.lat) + "#"; #else replystr = "K00#"; #endif Serial.print(replystr); break; case 12: // if the command is GH "Get Altitude" #ifdef useGPSNEO replystr = "L" + String((int)GPS.altitude) + "#"; #else replystr = "L0#"; #endif Serial.print(replystr); break; case 13: // if the command is GS "Get Satelittes" #ifdef useGPSNEO replystr = "M" + String(GPS.satellites) + "#"; #else replystr = "M0#"; #endif Serial.print(replystr); break; case 16: // gps fix? replystr = "N0#"; #ifdef useGPSNEO if ( GPS.fix ) replystr = "N1#"; #endif Serial.print(replystr); break; case 19: // get IR object temperature replystr = "O" + String(mlx90614object) + "#"; Serial.print(replystr); break; case 20: // get IR ambient temperature replystr = "P" + String(mlx90614ambient) + "#"; Serial.print(replystr); break; case 21: // return LUX value replystr = "U" + String(lux) + "#"; Serial.print(replystr); break; case 23: // return raining replystr = "R0#"; #ifdef RAINSENSOR if ( raining == true ) replystr = "R1#"; #endif Serial.print(replystr); break; case 24: // return rain voltage replystr = "S0#"; #ifdef RAINSENSOR replystr = "S" + String(rainVout) + "#"; #endif Serial.print(replystr); break; case 25: // save setpoint1 setpoint1 = decstr2int(param); break; case 26: // save setpoint2 setpoint2 = decstr2int(param); break; case 27: // return sky state replystr = "V" + String(skystate) + "#"; Serial.print(replystr); break; case 28: // return setpoint1 replystr = "W" + String(setpoint1) + "#"; Serial.print(replystr); break; case 29: // return setpoint2 replystr = "X" + String(setpoint2) + "#"; Serial.print(replystr); break; case 31: // if the command is GI "Get Nelm" replystr = "Z" + String(nelm) + "#"; Serial.print(replystr); break; case 32: // Get Humidity BME280 sensor replystr = "a" + String(bme280humidity, 2) + "#"; Serial.print(replystr); break; case 33: // Get pressure BME280 sensor replystr = "e" + String(bme280pressure) + "#"; Serial.print(replystr); break; case 34: // Get temperature BME280 sensor replystr = "c" + String(bme280temperature, 2) + "#"; Serial.print(replystr); break; case 35: // Get dewpoint replystr = "d" + String(dewpoint, 2) + "#"; Serial.print(replystr); break; } #ifdef useGPSNEO GPS.pause(false); #endif } // calculate LUX reading from LDR value void getlux() { #ifdef useGPSNEO GPS.pause(true); #endif #ifdef TSL2561SENSOR uint32_t lum = myTSL2561.getFullLuminosity(); uint16_t ir, full; ir = lum >> 16; full = lum & 0xFFFF; lux = myTSL2561.calculateLux(full, ir); #else // portions of code from https://www.digikey.com/en/maker/projects/design-a-luxmeter-with-an-ldr-and-an-arduino/623aeee0f93e427bb57e02c4592567d1 int ldrRawData; float resistorVoltage, ldrVoltage, ldrResistance; ldrRawData = analogRead(LDRpin); // Read the analogue pin, returns value 0-1023 resistorVoltage = (float)ldrRawData / 1023 * 5; ldrVoltage = 5.0 - resistorVoltage; ldrResistance = ldrVoltage / resistorVoltage * 10000; // Resistor is 10 kohm lux = (double)12518931 * pow(ldrResistance, -1.405); #endif #ifdef useGPSNEO GPS.pause(false); #endif } #ifdef useGPSNEO // Interrupt is called once a millisecond, looks for any new GPS data, and stores it SIGNAL(TIMER0_COMPA_vect) { char c = GPS.read(); } #endif #ifdef OLEDDISPLAY void showpageoled() { String tempStr; char tempstr[buf_size]; char sqmstr[] = "SQM"; #ifdef useGPSNEO GPS.pause(true); #endif // can only be called if displayenabled is true myoled.clear(); switch ( lcdpage ) { case 1: myoled.set2X(); myoled.println(sqmstr); myoled.println(mySQMreading); lcdpage = 2; break; case 2: #ifdef useGPSNEO myoled.set1X(); // date and time myoled.print("DATE: "); myoled.print(GPS.day, DEC); myoled.print('/'); myoled.print(GPS.month, DEC); myoled.print("/20"); myoled.println(GPS.year, DEC); myoled.print("TIME: "); myoled.print(GPS.hour, DEC); myoled.print(':'); myoled.print(GPS.minute, DEC); myoled.print(':'); myoled.println(GPS.seconds, DEC); // latitude and longitude myoled.print("LAT : "); myoled.print(GPS.latitude / 100); if ( GPS.lat == 'N' ) myoled.println(" N"); else myoled.println(" S"); myoled.print("LON : "); myoled.print(GPS.longitude / 100); if ( GPS.lon == 'W' ) myoled.println(" W"); else myoled.println(" E"); // satellites myoled.print("SAT : "); myoled.println( GPS.satellites ); myoled.print("ALT : "); myoled.print((int)GPS.altitude); myoled.println(" M"); #else myoled.print("GPS NOT ENABLED"); #endif lcdpage = 3; break; case 3: myoled.set1X(); myoled.print("NELM = "); myoled.println(nelm); // ir sensor myoled.print("IR AMBIENT = "); myoled.println(mlx90614ambient); myoled.print("IR OBJECT = "); myoled.println(mlx90614object); // LUX memset(tempstr, 0, buf_size); myoled.print("LUX = "); myoled.println(lux); // raining myoled.print("RAINING = "); if ( raining == false ) myoled.println("NO"); else myoled.println("YES"); // raining Vout, resolution accuracy is 4.9 mV volts = map(rainVout, 0, 1023, 0, 5000 ); myoled.print("RAIN VOUT = "); myoled.print( volts); myoled.println(" mV"); // setpoint1 and 2 myoled.print("SP1 = "); myoled.println(setpoint1); myoled.print("SP2 = "); myoled.println(setpoint2); lcdpage = 4; break; case 4: // sky state switch ( skystate ) { case SKYCLEAR: myoled.println("CLEAR"); break; case SKYPCLOUDY: myoled.println("PARTLY CLOUDY"); break; case SKYCLOUDY: myoled.println("CLOUDY"); break; default: myoled.println("UNKNOWN"); break; } // bme280 data, ambient, humidity, dewpoint, barometric pressure myoled.print("HUMIDITY : "); myoled.println(bme280humidity); myoled.print("AMBIENT : "); myoled.println(bme280temperature); myoled.print("DEWPOINT : "); myoled.println(dewpoint); myoled.print("HPA : "); myoled.println(bme280pressure); lcdpage = 1; break; } #ifdef useGPSNEO GPS.pause(false); #endif } #endif #ifdef LCDDISPLAY #ifdef LCD2004 void showpage2004() { #ifdef useGPSNEO GPS.pause(true); #endif switch ( lcdpage ) { case 1: lcd.print("SQM : "); lcd.print(mySQMreading); lcd.setCursor(0, 1); lcd.print("NELM: "); lcd.print(nelm); #ifdef useGPSNEO lcd.setCursor(0, 2); lcd.print("Date: "); lcd.print(GPS.day, DEC); lcd.print('/'); lcd.print(GPS.month, DEC); lcd.print("/20"); lcd.print(GPS.year, DEC); lcd.setCursor(0, 3); lcd.print("Time: "); lcd.print(GPS.hour, DEC); lcd.print(':'); lcd.print(GPS.minute, DEC); lcd.print(':'); lcd.print(GPS.seconds, DEC); #endif lcdpage = 2; break; case 2: #ifdef useGPSNEO // latitude and longitude lcd.print("Lat : "); lcd.print(GPS.latitude / 100); if ( GPS.lat == 'N' ) lcd.print(" N"); else lcd.print(" S"); lcd.setCursor(0, 1); lcd.print("Lon : "); lcd.print(GPS.longitude / 100); if ( GPS.lon == 'W' ) lcd.print(" W"); else lcd.print(" E"); lcd.setCursor(0, 2); // satellites lcd.print("Sat : "); lcd.print( GPS.satellites ); lcd.setCursor(0, 3); lcd.print("Alt : "); lcd.print((int)GPS.altitude); lcd.print("m"); #else lcd.print("GPS not enabled"); #endif lcdpage = 3; break; case 3: lcd.print("IRAmb : "); lcd.print(mlx90614ambient); lcd.print("c"); lcd.setCursor(0, 1); lcd.print("IRObj : "); lcd.print(mlx90614object); lcd.print("c"); lcd.setCursor(0, 2); // raining lcd.print("Rain? : "); if ( raining == false ) { lcd.print("NO"); } else { lcd.print("YES"); } lcd.setCursor(0, 3); volts = map(rainVout, 0, 1023, 0, 5000 ); lcd.print("RVout : "); lcd.print(volts); lcd.print("mV"); lcdpage = 4; break; case 4: // LUX lcd.print("Lux : " ); lcd.print(lux); lcd.setCursor(0, 1); lcd.print("SKY : "); // Sky state switch ( skystate ) { case SKYCLEAR: lcd.print("CLEAR"); break; case SKYPCLOUDY: lcd.print("PART CLOUDY"); break; case SKYCLOUDY: lcd.print("CLOUDY"); break; default: lcd.print("UNKNOWN"); break; } // setpoint1 and 2 lcd.setCursor(0, 2); lcd.print("SP1 : "); lcd.print(setpoint1); lcd.setCursor(0, 3); lcd.print("SP2 : "); lcd.print(setpoint2); lcdpage = 5; break; case 5: // bme280 data, ambient, humidity, dewpoint, barometric pressure lcd.print("Hum: "); lcd.print(bme280humidity); lcd.print("%"); lcd.setCursor(0, 1); lcd.print("Amb: "); lcd.print(bme280temperature); lcd.print("c"); lcd.setCursor(0, 2); lcd.print("Dew: "); lcd.print(dewpoint); lcd.print("c"); lcd.setCursor(0, 3); lcd.print("hPa: "); lcd.print(bme280pressure); lcdpage = 1; break; } #ifdef useGPSNEO GPS.pause(false); #endif } #endif #endif #ifdef LCDDISPLAY #ifdef LCD1604 void showpage1604() { #ifdef useGPSNEO GPS.pause(true); #endif switch ( lcdpage ) { case 1: lcd.print("SQM : "); lcd.print(mySQMreading); lcd.setCursor(0, 1); lcd.print("NELM: "); lcd.print(nelm); #ifdef useGPSNEO lcd.setCursor(0, 2); lcd.print("Date: "); lcd.print(GPS.day, DEC); lcd.print('/'); lcd.print(GPS.month, DEC); lcd.print("/20"); lcd.print(GPS.year, DEC); lcd.setCursor(0, 3); lcd.print("Time: "); lcd.print(GPS.hour, DEC); lcd.print(':'); lcd.print(GPS.minute, DEC); lcd.print(':'); lcd.print(GPS.seconds, DEC); #endif lcdpage = 2; break; case 2: #ifdef useGPSNEO // latitude and longitude lcd.print("Lat : "); lcd.print(GPS.latitude / 100); if ( GPS.lat == 'N' ) lcd.print(" N"); else lcd.print(" S"); lcd.setCursor(0, 1); lcd.print("Lon : "); lcd.print(GPS.longitude / 100); if ( GPS.lon == 'W' ) lcd.print(" W"); else lcd.print(" E"); lcd.setCursor(0, 2); // satellites lcd.print("Sat : "); lcd.print( GPS.satellites ); lcd.setCursor(0, 3); lcd.print("Alt : "); lcd.print((int)GPS.altitude); lcd.print("m"); #else lcd.print("GPS not enabled"); #endif lcdpage = 3; break; case 3: lcd.print("IRAmb : "); lcd.print(mlx90614ambient); lcd.print("c"); lcd.setCursor(0, 1); lcd.print("IRObj : "); lcd.print(mlx90614object); lcd.print("c"); lcd.setCursor(0, 2); // raining lcd.print("Rain? : "); if ( raining == false ) lcd.print("NO"); else lcd.print("YES"); lcd.setCursor(0, 3); volts = map(rainVout, 0, 1023, 0, 5000 ); lcd.print("RVout : "); lcd.print(volts); lcd.print("mV"); lcdpage = 4; break; case 4: // LUX lcd.print("Lux : " ); lcd.print(lux); lcd.setCursor(0, 1); lcd.print("SKY : "); // Sky state switch ( skystate ) { case SKYCLEAR: lcd.print("CLEAR"); break; case SKYPCLOUDY: lcd.print("PCLOUDY"); break; case SKYCLOUDY: lcd.print("CLOUDY"); break; default: lcd.print("UNKNOWN"); break; } // setpoint1 and 2 lcd.setCursor(0, 2); lcd.print("SP1 : "); lcd.print(setpoint1); lcd.setCursor(0, 3); lcd.print("SP2 : "); lcd.print(setpoint2); lcdpage = 5; break; case 5: // bme280 data, ambient, humidity, dewpoint, barometric pressure lcd.print("Hum: "); lcd.print(bme280humidity); lcd.print("%"); lcd.setCursor(0, 1); lcd.print("Amb: "); lcd.print(bme280temperature); lcd.print("c"); lcd.setCursor(0, 2); lcd.print("Dew: "); lcd.print(dewpoint); lcd.print("c"); lcd.setCursor(0, 3); lcd.print("hPa: "); lcd.print(bme280pressure); lcdpage = 1; break; } #ifdef useGPSNEO GPS.pause(false); #endif } #endif #endif #ifdef LCDDISPLAY #ifdef LCD1602 void showpage1602() { #ifdef useGPSNEO GPS.pause(true); #endif switch ( lcdpage ) { case 1: lcd.print("SQM : "); lcd.print(mySQMreading); lcd.setCursor(0, 1); lcd.print("NELM: "); lcd.print(nelm); lcdpage = 2; break; case 2: #ifdef useGPSNEO lcd.print("Date: "); lcd.print(GPS.day, DEC); lcd.print('/'); lcd.print(GPS.month, DEC); lcd.print("/20"); lcd.print(GPS.year, DEC); lcd.setCursor(0, 1); lcd.print("Time: "); lcd.print(GPS.hour, DEC); lcd.print(':'); lcd.print(GPS.minute, DEC); lcd.print(':'); lcd.print(GPS.seconds, DEC); #endif lcdpage = 3; break; case 3: #ifdef useGPSNEO // latitude and longitude lcd.print("Lat : "); lcd.print(GPS.latitude / 100); if ( GPS.lat == 'N' ) lcd.print(" N"); else lcd.print(" S"); lcd.setCursor(0, 1); lcd.print("Lon : "); lcd.print(GPS.longitude / 100); if ( GPS.lon == 'W' ) lcd.print(" W"); else lcd.print(" E"); lcdpage = 4; break; case 4: // satellites lcd.print("Sat : "); lcd.print( GPS.satellites ); lcd.setCursor(0, 1); lcd.print("Alt : "); lcd.print((int)GPS.altitude); lcd.print("m"); lcdpage = 5; break; #else lcd.print("GPS not enabled"); #endif lcdpage = 5; break; case 5: lcd.print("IRAmb : "); lcd.print(mlx90614ambient); lcd.print("c"); lcd.setCursor(0, 1); lcd.print("IRObj : "); lcd.print(mlx90614object); lcd.print("c"); lcdpage = 6; break; case 6: // raining lcd.print("Rain? : "); if ( raining == false ) lcd.print("NO"); else lcd.print("YES"); lcd.setCursor(0, 1); // raining Vout, resolution accuracy is 4.9 mV volts = map(rainVout, 0, 1023, 0, 5000 ); lcd.print("RVout : "); lcd.print(volts); lcd.print("mV"); lcdpage = 7; break; case 7: lcd.print("Lux : " ); lcd.print(lux); lcd.setCursor(0, 1); lcd.print("SKY : "); switch ( skystate ) { case SKYCLEAR: lcd.print("CLEAR"); break; case SKYPCLOUDY: lcd.print("PART CLOUDY"); break; case SKYCLOUDY: lcd.print("CLOUDY"); break; default: lcd.print("UNKNOWN"); break; } lcdpage = 8; break; case 8: // setpoint1 and 2 lcd.print("SP1 : "); lcd.print(setpoint1); lcd.setCursor(0, 1); lcd.print("SP2 : "); lcd.print(setpoint2); lcdpage = 9; break; case 9: // bme280 data, ambient, humidity, dewpoint, barometric pressure lcd.print("Hum: "); lcd.print(bme280humidity); lcd.print("%"); lcd.setCursor(0, 1); lcd.print("Amb: "); lcd.print(bme280temperature); lcd.print("c"); lcdpage = 10; break; case 10: // bme280 data, ambient, humidity, dewpoint, barometric pressure lcd.print("Dew: "); lcd.print(dewpoint); lcd.print("c"); lcd.setCursor(0, 1); lcd.print("hPa: "); lcd.print(bme280pressure); lcdpage = 1; break; } #ifdef useGPSNEO GPS.pause(false); #endif } #endif #endif // Chris void jsonsend() { StaticJsonBuffer<500> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); root["data1"] = mySQMreading; root["data2"] = nelm; root["data3"] = mlx90614ambient; root["data4"] = mlx90614object; root["data5"] = raining; root["data6"] = volts; root["data7"] = lux; root["data8"] = skystate; root["data9"] = setpoint1; root["data10"] = setpoint2; root["data11"] = bme280humidity; root["data12"] = bme280temperature; root["data13"] = dewpoint; root["data14"] = bme280pressure; if(s.available()>0) { root.printTo(s); } } void setup() { Serial.begin(9600); clearSerialPort(); s.begin(9600); // Chris - your esp's baud rate communication to NodeMCU #ifdef useGPSNEO ss.begin(GPSPortSpeed); delay(200); GPS.begin(GPSPortSpeed); // start GPS device // There needs to be a delay before sending an commands to GPS after a GPS.begin() delay(200); // delay 100mS, minimum 10mS is required GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA); GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ); // 1 Hz update rate GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_ALLDATA); GPS.pause(true); #endif pinMode( rs_DO, INPUT ); // rain sensor mySQMreading = 0.0; // the SQM value, sky magnitude frequency = 1.0; // measured TLS237 frequency which is dependent on light irradiance = 1.0; nelm = 1.0; setpoint1 = 22; // setpoint values used to determine sky state setpoint2 = 2; sensorsamplerequest = false; dewpoint = 10.0; bme280temperature = 20.0; bme280humidity = 50.0; bme280pressure = 1100; mlx90614ambient = 20.0; mlx90614object = 20.0; lux = 1000; skystate = SKYCLEAR; raining = false; rainVout = 0; eoc = 0; idx = 0; memset(line, 0, MAXCOMMAND); #ifdef LCDDISPLAY #ifdef LCD1602 lcd.begin(16, 2); #endif #ifdef LCD1604 lcd.begin(16, 4); #endif #ifdef LCD2004 lcd.begin(20, 4); #endif lcdpage = 1; lcd.setBacklight(HIGH); lcd.print(programName); lcd.setCursor(0, 1); lcd.print("Version: "); lcd.print(programVersion); lcd.setCursor(0, 2); lcd.print("(c) R Brown 2018"); #endif #ifdef OLEDDISPLAY lcdpage = 1; Wire.begin(); myoled.begin(&Adafruit128x64, OLEDADDRESS); myoled.set400kHz(); myoled.setFont(Adafruit5x7); myoled.clear(); // clrscr OLED myoled.Display_Normal(); // black on white myoled.Display_On(); // display ON myoled.Display_Rotate(0); // portrait, not rotated myoled.Display_Bright(); // The screen size is 128 x 64, so using characters at 6x8 this gives 21chars across and 8 lines down myoled.println(programName); myoled.println(programVersion); myoled.InverseCharOn(); myoled.println("(C) R BROWN."); myoled.InverseCharOff(); #endif #ifdef BME280SENSOR mybme280.begin(BME280_I2CADDR); mybme280.init(); #endif #ifdef MLS90614SENSOR mymlx90614.begin(); #endif #ifdef TSL2561SENSOR myTSL2561.begin(); myTSL2561.setGain(TSL2561_GAIN_16X); // set 16x gain (for dim situations) myTSL2561.setTiming(TSL2561_INTEGRATIONTIME_13MS); // shortest integration time (bright light) #endif #ifdef useGPSNEO useInterrupt(true); GPS.pause(false); #endif lcdsampletimer = sensorsampletimer = sqmsampletimer = millis(); } void loop() { if ( queue.count() >= 1 ) // check for serial command { processCommand(); } int chris; #ifdef useGPSNEO if (GPS.newNMEAreceived()) { if (!GPS.parse(GPS.lastNMEA())) // this also sets the newNMEAreceived() flag to false return; // we can fail to parse a sentence in which case we should just wait for another } #endif // refresh sensor readings now = millis(); if ( ((now - sensorsampletimer ) > SENSORSAMPLETIME ) || (now < sensorsampletimer) ) { sensorsampletimer = now; if ( sensorsamplerequest == false ) { // update bmesensor #ifdef BME280SENSOR mybme280.readData(); mybme280.getValues(&bme280temperature, &bme280humidity, &bme280pressure); calc_dewpoint((float) bme280temperature, (float) bme280humidity); chris=bme280pressure; #endif // update lux getlux(); // get raining getraining(); volts = map(rainVout, 0, 1023, 0, 5000 ); sensorsamplerequest = true; } else { // update MLX90614 and rainsensor #ifdef MLX90614SENSOR mlx90614ambient = mymlx90614.readAmbientTempC(); mlx90614object = mymlx90614.readObjectTempC(); #endif // update skystate getskystate(); sensorsamplerequest = false; } } // refresh sqm readings now = millis(); // take a sample every 5 seconds if ( ((now - sqmsampletimer ) > SQMSAMPLETIME ) || (now < sqmsampletimer) ) { sqmsampletimer = now; int LDRval = analogRead( LDRpin ); // read LDR to determine background light level if ( LDRval < LDRCutoff1 ) // and use this to set the Gate Time (duration) for this samples frequency measurement { period = blackperiod; // its very dark } else if ( LDRval < LDRCutoff2 ) { period = darkperiod; // its dark } else period = lightperiod; // its light light(); // read frequency from sensor #ifdef TSL235 frequency = frequency * 7.2; #endif irradiance = frequency / 2.3e3; // calculate irradiance as uW/(cm^2) #ifdef OLDSQMMETHOD mySQMreading = ((sqm_limit - (2.5 * log10( (frequency * 1.584) / 2.0 )))) + 1.0; // frequency to magnitudes/arcSecond^2 #endif #ifdef NEWSQMMETHOD mySQMreading = sqm_limit - 2.5 * log10 (frequency); #endif #ifdef NEWSQMMETHODCORRECTED // 0.973 was derived by comparing TLS237 sensor readings against Unihedron and plotting on graph then deriving coefficient mySQMreading = (sqm_limit - (2.5 * log10( frequency )) * 0.973); // frequency to magnitudes/arcSecond2 #endif #ifdef IRRADIANCEMETHOD mySQMreading = log10((irradiance / 6.83) / 108000) / -0.4; // mag/arcsec^2 #endif nelm = 7.93 - 5.0 * log10((pow(10, (4.316 - (mySQMreading / 5.0))) + 1)); //chris Send Information to NodeMCU for WIfi jsonsend(); } now = millis(); // update LCD Screen every 4.5 seconds if ( ((now - lcdsampletimer ) > LCDUPDATETIME ) || (now < lcdsampletimer) ) { lcdsampletimer = now; #ifdef LCDDISPLAY // now update screens lcd.clear(); #ifdef LCD1602 showpage1602(); #endif #ifdef LCD1604 showpage1604(); #endif #ifdef LCD2004 showpage2004(); #endif #endif #ifdef OLEDDISPLAY showpageoled(); #endif } } void light() { long pulses = 1L; #ifdef useGPSNEO GPS.pause(true); #endif FreqCounter::f_comp = 0; // Cal Value / Calibrate with professional Freq Counter FreqCounter::start(period); // Gate Time while (FreqCounter::f_ready == 0) pulses = FreqCounter::f_freq; delay(20); frequency = ((double)pulses * (double)1000.0) / (double) period; // if cannot measure the number of pulses set to freq 1hz which is mag 21.95 if ( frequency < 1.0 ) frequency = 1.0; #ifdef useGPSNEO GPS.pause(false); #endif } void clearSerialPort() { while ( Serial.available() ) Serial.read(); } // SerialEvent occurs whenever new data comes in the serial RX. void serialEvent() { #ifdef useGPSNEO GPS.pause(true); #endif // : starts the command, # ends the command, do not store these in the command buffer // read the command until the terminating # character while (Serial.available() && !eoc) { char inChar = Serial.read(); if (inChar != '#' && inChar != ':') { line[idx++] = inChar; if (idx >= MAXCOMMAND) { idx = MAXCOMMAND - 1; } } else { if (inChar == '#') { eoc = 1; idx = 0; queue.push(String(line)); eoc = 0; memset( line, 0, MAXCOMMAND); } } } #ifdef useGPSNEO GPS.pause(false); #endif } #ifdef useGPSNEO void useInterrupt(boolean v) { if (v) { OCR0A = 0xAF; TIMSK0 |= _BV(OCIE0A); usingInterrupt = true; } else { TIMSK0 &= ~_BV(OCIE0A); usingInterrupt = false; } } #endif