diff --git a/Alarms.cpp b/Alarms.cpp new file mode 100644 index 0000000..239424a --- /dev/null +++ b/Alarms.cpp @@ -0,0 +1,143 @@ +#include "Alarms.h" +#include "units.h" +#include + +Alarms::Alarms( Config cfg, WeatherData *_wd ): + WeatherSink( cfg, _wd, "Alarms" ) + +{ + pthread_mutex_init( ¬ify, NULL ); + pthread_mutex_lock( ¬ify ); + startMain(); +} + +Alarms::~Alarms() +{ + endMain(); +} + +void Alarms::setLimits() +{ + + for ( int p = WeatherData::insideTemp; p != WeatherData::END; p++ ) + { + lowerLimit[ ( WeatherData::PROPERTY)p ] = NO_VALUE; + upperLimit[ ( WeatherData::PROPERTY)p ] = NO_VALUE; + alarming[ (WeatherData::PROPERTY)p ] = false; + } + lowerLimit[WeatherData::insideTemp] = 55; + upperLimit[WeatherData::insideTemp] = 78; + lowerLimit[WeatherData::insideHumidity] = 10; + upperLimit[WeatherData::insideHumidity] = 90; + lowerLimit[WeatherData::outsideTemp] = 0; + upperLimit[WeatherData::outsideTemp] = 90; + upperLimit[WeatherData::windSpeed] = 30; + upperLimit[WeatherData::averageWindSpeed] = 30; + upperLimit[WeatherData::windGust] = 30; + upperLimit[WeatherData::dailyRain] = 100; + lowerLimit[WeatherData::SLP] = (29.00); + upperLimit[WeatherData::SLP] = (31.00); + + lowerLimit[WeatherData::apparentTemp] = 0; + upperLimit[WeatherData::apparentTemp] = 90; + + upperLimit[WeatherData::rain24Hour] = 100; + upperLimit[WeatherData::instantRain] = 100; + + // TODO: Need to add calculated values too +} + + +void Alarms::newData() +{ + pthread_mutex_unlock( ¬ify ); +} + +void Alarms::main() +{ + bool alarmOccured; + setLimits(); + while ( threadRunning ) + { + std::ostringstream oss; + alarmOccured = false; + pthread_mutex_lock( ¬ify ); + if( !threadRunning ) + { + dbg.printf(NOTICE, "Exiting\n"); + return; + } + + float timeSinceLast = alarmCheckTime.timeSinceLast(); + dbg.printf(DEBUG, "time since last alarm check: %f\n", timeSinceLast); + + if ( timeSinceLast <= ALARM_INTERVAL ) + { + dbg.printf(DEBUG, "too soon since last update.. not doing anything\n"); + continue; + } + for ( int i = WeatherData::insideTemp; i != WeatherData::END; i++ ) + { + WeatherData::PROPERTY p = (WeatherData::PROPERTY)i; + // localAlarm is used in the loop to determine if we need to throw + // another alarm condition. It is set anytime an alarm condition is + // encountered. We can use this to see if we were alarming in our + // previous iteration (in which case we don't throw the alarm), or if + // this is a new alarm condition + bool localAlarm = false; + + if ( lowerLimit[p] != NO_VALUE ) + { + // If we are alarming, we won't send out another notice until this + // alarm clears + if ( wd->getValue( p, 0 ) < + lowerLimit[ p ] ) + { + localAlarm = true; + if ( !alarming[ p ] ) + { + alarming[ p] = true; + oss << wd->propertyToString(p); + oss << " exceeding alarm condition. Current value is "; + oss << wd->getValue( p, 0 ) << ", lower limit is "; + oss << lowerLimit[ p ] << std::endl; + alarmOccured = true; + } + } + } + + if ( upperLimit[p] != NO_VALUE ) + { + // If we are alarming, we won't send out another notice until this + // alarm clears + if ( wd->getValue( p, 0 ) > upperLimit[ p ] ) + { + localAlarm = true; + if ( !alarming[ p ] ) + { + alarming[ p ] = true; + oss << wd->propertyToString(p); + oss << " exceeding alarm condition. Current value is "; + oss << wd->getValue( p, 0 ) << ", upper limit is "; + oss << upperLimit[ p ] << std::endl; + alarmOccured = true; + } + } + } + // If this was alarming, but now we aren't... + if ( !localAlarm && alarming[p] ) + { + dbg.printf(NOTICE, "Resetting alarm condition on %s\n", + wd->propertyToString(p)); + alarming[p] = false; + } + } + if ( alarmOccured ) + { + dbg.printf(NOTICE, oss.str().c_str()); + } + alarmCheckTime.updateTime(); + } + +} + diff --git a/Alarms.h b/Alarms.h new file mode 100644 index 0000000..75455a6 --- /dev/null +++ b/Alarms.h @@ -0,0 +1,27 @@ +#ifndef __ALARMS_H__ +#define __ALARMS_H__ +#include "WeatherSink.h" +#include + +#define ALARM_INTERVAL (10) + +class Alarms: public WeatherSink +{ +public: + Alarms( Config cfg, WeatherData *wd ); + ~Alarms(); + + void newData(); + void setLimits(); + void main(); + +private: + pthread_mutex_t notify; + std::map< WeatherData::PROPERTY, double > lowerLimit; + std::map< WeatherData::PROPERTY, double > upperLimit; + std::map< WeatherData::PROPERTY, double > oldAlarmVal; + std::map< WeatherData::PROPERTY, bool > alarming; + Now alarmCheckTime; +}; + +#endif // __ALARMS_H__ diff --git a/Almanac.cpp b/Almanac.cpp new file mode 100644 index 0000000..cb09c2e --- /dev/null +++ b/Almanac.cpp @@ -0,0 +1,348 @@ +#include "Almanac.h" +#include "math.h" +#include "units.h" +#include "utils.h" +#include +// http://bodmas.org/astronomy/riset.html#twig01a +// +// +// Test the algorithm from USNO website: +// http://aa.usno.navy.mil/data/docs/RS_OneDay.php + +Almanac::Almanac( Config _cfg, time_t t, double horizon ): + dbg("Almanac"), + cfg(_cfg) +{ + this->lat = cfg.getDouble("latitude"); + this->lon = cfg.getDouble("longitude"); + this->horizon = horizon; + setNewTime( t ); +} + +void Almanac::setNewTime( struct tm *tm ) +{ + this->tm = *tm; + this->tm.tm_sec = 0; + this->tm.tm_hour = 0; + this->tm.tm_min = 0; + tz = cfg.getDouble("timezone"); + if ( tm->tm_isdst == 1 ) + tz+=1; + + + dbg.printf(DEBUG, "Almanac::setNewTime()\n"); + + time_t t = mktime( &this->tm ); + dataStruct alm( t, (int)tz*3600, horizon, lat, lon ); + sunrise = alm.sunrise; + sunset = alm.sunset; + lengthOfDay = alm.lengthOfDay; + + +} + +Almanac::dataStruct::dataStruct( time_t t, int timeCorrection, double horizon, double lat, double lon, bool interpolate ) +{ + + struct tm tm; + sunrise = sunset = transitTime = t; + localtime_r( &t, &tm ); + julianDay = getJulianDay( tm.tm_mday, + tm.tm_mon+1, + tm.tm_year, + 0, + 0, + 0 ); + + + if ( interpolate ) + { + yesterday = new dataStruct( t - 86400, timeCorrection, horizon, lat, lon, false); + tomorrow = new dataStruct( t + 86400, timeCorrection, horizon, lat, lon, false); + } + + // ( 22.1 ) + long double T = (julianDay - 2451545.0)/36525.0; // Julian Centuries from J2000 + leapSeconds = 64; + // Compute Sun's Mean Longitude ( 25.2 ) + sunsMeanLongitude = rangeDegrees(280.466450 + 36000.769830*T + 0.0003032 * T * T); + + // Compute Moon's Mean Longitude ( 47.1 ) + moonsMeanLongitude = rangeDegrees(218.3164477 + 481267.88123421 * T - 0.0015786 * T * T + T*T*T/538841 - T*T*T*T/65194000.0); + // Original: moonsMeanLongitude = rangeDegrees(218.316459 + 481267.8813421 * T - 0.001327 * T * T + T*T*T/538841 - T*T*T*T/65194000.0); + + // Compute the mean anomaly of the sun ( 25.3 ) + meanAnomalyOfTheSun = 357.52911 + 35999.05029*T - .0001537*T*T; + // Original: meanAnomalyOfTheSun = 357.5291 + 35999.05030*T - .000156*T*T + + //.00000048 * T * T * T; + + // Compute the sun's equation of center ( 24.4+ ) + sunsEquationOfCenter = (1.914602 - 0.004817 * T - 0.000014*T*T ) * sin( deg2rad(meanAnomalyOfTheSun) ); + //+ ( 0.019993 - 0.000101*T)*sin(deg2rad(2*meanAnomalyOfTheSun)) + //+ ( 0.00289)*sin(deg2rad(3*meanAnomalyOfTheSun)); + /* Original: sunsEquationOfCenter = (1.9146 - 0.004817 * T - 0.000014*T*T ) + * sin( deg2rad(meanAnomalyOfTheSun) ) + + ( 0.019993 - 0.000101*T)*sin(deg2rad(2*meanAnomalyOfTheSun)) + + ( 0.00029)*sin(deg2rad(3*meanAnomalyOfTheSun));*/ + + // True longitude of the sun ( 24.4++) + sunsTrueLongitude = rangeDegrees(sunsMeanLongitude + sunsEquationOfCenter); + + // Moon's Long Mean Ascending Node ( ??? ) + /*moonsMeanAscendingNode = 125.0445479 - 1934.1362891 * T + 0.0020754 * T * T + + T*T*T/467441.0 - T*T*T*T / 60616000.0;*/ + moonsMeanAscendingNode = 125.9445479 - 1934.1362891 * T + 0.0020754 * T * T + + T*T*T/467441.0 - T*T*T*T / 60616000.0; + + // 25.5+ + sunsApparentLongitude = rangeDegrees(sunsTrueLongitude - 0.00569 - 0.00478 * sin ( deg2rad( moonsMeanAscendingNode ))); + + // p 147 + meanObliq = dms2deg( 23, 26, 21.448 ) - dms2deg( 0, 0, 46.8150 ) * T - dms2deg( 0, 0, 0.00059 ) * ( T * T) + dms2deg( 0, 0, 0.001813 ) * ( T * T * T ); + + // p 144 + double omega2= (125.04452 - 1934.136261*T); + double L= (rangeDegrees(280.4665 + 36000.7698*T)); + double LPrime= rangeDegrees(218.3165 + 481267.8813*T); + + long double NutationInObliq = 9.20/3600.0*(cos(deg2rad(omega2))) + + 0.57/3600.0*(cos(deg2rad(2*L))) + + 0.10/3600.0*(cos(deg2rad(2*LPrime))) - + 0.09/3600.0*(cos(deg2rad(2*omega2))); + long double NutationInLong = -17.20/3600.0*(sin(deg2rad(omega2))) - + 1.32/3600.0*(sin(deg2rad(2*L))) - + 0.23/3600.0*(sin(deg2rad(2*LPrime))) + + 0.21/3600.0*(sin(deg2rad(2*omega2))); + + TrueObliq = meanObliq + NutationInObliq; + + // 25.7 + sunsApparent.declination = rad2deg(asin( sin( deg2rad( TrueObliq )) * sin( deg2rad( sunsApparentLongitude )))); + + // 25.7 + sunsApparent.rightAscention = rangeDegrees(rad2deg( atan( ( cos( deg2rad( TrueObliq )) * tan( deg2rad( sunsApparentLongitude )))))); + + // 25.6 + sunsApparent.rightAscentionCorrected = sunsApparent.rightAscention + + 90 * ( trunc( sunsApparentLongitude/90.0) + - trunc( sunsApparent.rightAscention/90.0 )); + + // 12.3 + siderealTimeAtMeridianAt0Hrs.mean = rangeDegrees(100.46061837 + 36000.770053608 * T + 0.000387933*T*T - T*T*T/38710000.0); + + // 12.4 + siderealTimeAtMeridianAt0Hrs.apparent = siderealTimeAtMeridianAt0Hrs.mean + NutationInLong * cos(deg2rad( TrueObliq )); + + + // 15.1 + horizonToNoonAngle = rad2deg(acos((sin(deg2rad( horizon )) + - sin( deg2rad( lat ) ) + * sin(deg2rad( sunsApparent.declination ))) + / ( cos(deg2rad(lat)) * cos(deg2rad(sunsApparent.declination ))))); + + // 15.2 + approxTransitTime = (sunsApparent.rightAscentionCorrected + + fabs(lon) - siderealTimeAtMeridianAt0Hrs.apparent)/360.0 ; + if ( approxTransitTime < 0 ) + approxTransitTime += 1; + if ( approxTransitTime > 1 ) + approxTransitTime -= 1; + + if ( !interpolate ) + return; + + sunsInterpolated.n = approxTransitTime + leapSeconds / 86400.0; + sunsInterpolated.a = sunsApparent.rightAscentionCorrected - + yesterday->sunsApparent.rightAscentionCorrected; + if ( sunsInterpolated.a < 0 ) sunsInterpolated.a += 360; + + sunsInterpolated.b = tomorrow->sunsApparent.rightAscentionCorrected - + sunsApparent.rightAscentionCorrected; + if ( sunsInterpolated.b < 0 ) sunsInterpolated.b += 360; + + sunsInterpolated.c = sunsInterpolated.b-sunsInterpolated.a; + + sunsInterpolated.rightAscention = sunsApparent.rightAscentionCorrected + + (sunsInterpolated.n/2.0)*(sunsInterpolated.a+sunsInterpolated.b+sunsInterpolated.n*sunsInterpolated.c); + + + // p 103 + sunsInterpolated.siderealTimeAtGreenwich = + rangeDegrees(siderealTimeAtMeridianAt0Hrs.apparent + 360.985647 * approxTransitTime); + // p 103 + + sunsInterpolated.hourAngle = sunsInterpolated.siderealTimeAtGreenwich + - fabs(lon) - sunsInterpolated.rightAscention; + float modVal = 180; + if ( sin(deg2rad(sunsInterpolated.hourAngle)) < 0 ) + { + modVal = -180; + } + sunsInterpolated.hourAngle = fmod( sunsInterpolated.hourAngle, modVal ); + + // 15.2 + approxRise = approxTransitTime - horizonToNoonAngle/360.0; + + // Sun's Interpolated Apparent Right Ascention, Declinatipon at Rising Hour + // Angle and Attitude + sunsInterpolatedApparent.n = approxRise + leapSeconds / 86400.0; + + sunsInterpolatedApparent.rightAscention = + sunsApparent.rightAscentionCorrected + + (sunsInterpolatedApparent.n/2.0) + * (sunsInterpolated.a + sunsInterpolated.b + sunsInterpolated.n * sunsInterpolated.c); + sunsInterpolatedApparent.siderealTimeAtGreenwich= rangeDegrees( + siderealTimeAtMeridianAt0Hrs.apparent + 360.985647 * approxRise); + sunsInterpolatedApparent.a = sunsApparent.declination - yesterday->sunsApparent.declination; + sunsInterpolatedApparent.b = tomorrow->sunsApparent.declination - sunsApparent.declination; + sunsInterpolatedApparent.c = sunsInterpolatedApparent.b - + sunsInterpolatedApparent.a; + + sunsInterpolatedApparent.declination = sunsApparent.declination + ( sunsInterpolatedApparent.n + / 2.0 )* ( sunsInterpolatedApparent.a + sunsInterpolatedApparent.b + + sunsInterpolatedApparent.n * sunsInterpolatedApparent.c ); + sunsInterpolatedApparent.hourAngle = + sunsInterpolatedApparent.siderealTimeAtGreenwich - fabs(lon) - + sunsInterpolatedApparent.rightAscention; + + // Need formula + sunsInterpolatedApparent.altitude = + rad2deg(sin(deg2rad(fabs(lat)))*sin(deg2rad(sunsInterpolatedApparent.declination))+cos(deg2rad(fabs(lat)))*cos(deg2rad(sunsInterpolatedApparent.declination))*cos(deg2rad(sunsInterpolatedApparent.hourAngle))); + + risingCorrection = (sunsInterpolatedApparent.altitude - + horizon)/(360*cos(deg2rad(sunsInterpolatedApparent.declination))*cos(deg2rad(fabs(lat)))*sin(deg2rad(sunsInterpolatedApparent.hourAngle))); + + + + approxSet = approxTransitTime + horizonToNoonAngle/360.0; + + setting.n = approxSet + leapSeconds / 86400.0; + setting.declination = sunsApparent.declination + ( setting.n/2.0 ) * ( + sunsInterpolatedApparent.a + +sunsInterpolatedApparent.b + +setting.n*sunsInterpolatedApparent.c ); + + setting.a = sunsApparent.rightAscentionCorrected - yesterday->sunsApparent.rightAscentionCorrected; + if ( setting.a < 0 ) setting.a += 360; + + setting.b = tomorrow->sunsApparent.rightAscentionCorrected - sunsApparent.rightAscentionCorrected; + if ( setting.b < 0 ) setting.b += 360; + + setting.c = setting.b-setting.a; + setting.rightAscention = sunsApparent.rightAscentionCorrected + ( setting.n + / 2.0 )*(setting.a+setting.b+setting.n*setting.c); + + setting.siderealTimeAtGreenwich = + rangeDegrees(siderealTimeAtMeridianAt0Hrs.apparent + 360.985647 * approxSet); + setting.hourAngle = setting.siderealTimeAtGreenwich - fabs( lon ) - + setting.rightAscention; + + setting.altitude = + rad2deg(sin(deg2rad(fabs(lat)))*sin(deg2rad(setting.declination))+cos(deg2rad(fabs(lat)))*cos(deg2rad(setting.declination))*cos(deg2rad(setting.hourAngle ))); + + settingCorrection = ( setting.altitude - horizon )/(360*cos( + deg2rad(setting.declination))*cos(deg2rad(fabs(lat)))*sin(deg2rad(setting.hourAngle))); + + + transitTime += (time_t)((approxTransitTime - sunsInterpolated.hourAngle/360.0)*86400) + + timeCorrection; + sunrise += (time_t)(( approxRise + risingCorrection ) * 86400) + + timeCorrection; + sunset += (time_t)(( approxSet + settingCorrection ) * 86400) + + timeCorrection; + + lengthOfDay = sunset - sunrise; + +return; + + printf("------------\n"); + printf("horizon: %f\n", horizon ); + printf("lat: %f\n", fabs(lat) ); + printf("lon: %f\n", fabs(lon)); + printf("julian day: %f\n", julianDay ); + printf("moon's mean longitude: %.12Lf\n", moonsMeanLongitude ); + printf("sun's Mean Longitude: %Lf\n", sunsMeanLongitude ); + printf("mean anomaly of the sun: %Lf\n", meanAnomalyOfTheSun ); + printf("sun's equation of center: %Lf\n", sunsEquationOfCenter ); + printf("sun's true longitude: %Lf\n", sunsTrueLongitude ); + printf("moon's mean AsceningNode: %Lf\n", moonsMeanAscendingNode ); + printf("sun's apparent longtiude: %Lf\n", sunsApparentLongitude ); + printf("notation in long: %Lf\n", NutationInLong ); + printf("true obliq: %Lf (%s)\n", TrueObliq, deg2dms(TrueObliq).c_str()); + printf("Sun's Apparent Right Ascention And Declination:\n"); + printf("\tDEC: %Lf\n", sunsApparent.declination ); + printf("\tR.A. (raw) %Lf\n", sunsApparent.rightAscention ); + printf("\tR.A. (same quadrant as long): %Lf\n", sunsApparent.rightAscentionCorrected ); + printf("Sidereal Time @ Meridian @ 0 Hrs UT\n"); + printf("\tmean (AA): %Lf\n", siderealTimeAtMeridianAt0Hrs.mean ); + printf("\tapparent (AB): %Lf\n", siderealTimeAtMeridianAt0Hrs.apparent ); + printf("horizon to noon angle: %Lf\n", horizonToNoonAngle ); + printf("approximate transit time (AF): %Lf\n", approxTransitTime ); + //printf("sun's actual right ascention: %Lf\n", sunsRightAscentionCorrected) ; + printf("Sun's Interpolated Apparent Right Ascention at Transit and Hour Angle:\n"); + printf("\tsidereal time @ Greenwich (AL): %Lf\n", sunsInterpolated.siderealTimeAtGreenwich ); + printf("\thour angle: %Lf\n", sunsInterpolated.hourAngle ); + printf("\tright ascention: %Lf\n", sunsInterpolated.rightAscention ); + printf("\tn = %Lf a = %Lf b = %Lf c = %Lf\n", + sunsInterpolated.n, + sunsInterpolated.a, + sunsInterpolated.b, + sunsInterpolated.c); + printf("approx rise time: %Lf\n", approxRise ); + printf("suns Interpolated Apparent\n"); + printf("\tn: %Lf\n", sunsInterpolatedApparent.n ); + printf("\tR.A: %Lf\n", sunsInterpolatedApparent.rightAscention ); + printf("\tSidereal Time at Greenwich Corrected: %Lf\n", + sunsInterpolatedApparent.siderealTimeAtGreenwich ); + printf("\tdeclination: %Lf\n", sunsInterpolatedApparent.declination ); + printf("\thour Angle: %Lf\n", sunsInterpolatedApparent.hourAngle ); + printf("\taltitude: %Lf\n", sunsInterpolatedApparent.altitude ); + printf("rising Correction: %Lf\n", risingCorrection ); + + printf("approx set: %Lf\n", approxSet ); + printf("setting:\n"); + printf("\tright ascention: %Lf\n", setting.rightAscention ); + printf("\tdeclination: %Lf\n", setting.declination ); + printf("\tsiderealTime@setting: %Lf\n", setting.siderealTimeAtGreenwich); + printf("\tsetting hour angle: %Lf\n", setting.hourAngle ); + printf("\tsetting altitude: %Lf\n", setting.altitude); + printf("\tsetting correction: %Lf\n", settingCorrection ); + // + // + printf("\n\n"); + //printf("transitTime: %Lf\n", transitTime ); + //printf("rise Time: %Lf\n", riseTime ); + //printf("set time : %Lf\n", setTime ); + +// printf("Y13 = %Lf\n", sunsRightAscentionCorrected ); + //printf("Y12 = %Lf\n", yesterday->sunsRightAscentionCorrected ); +} + +void Almanac::setNewTime( time_t t ) +{ + if ( t == 0 ) + { + dbg.printf(DEBUG, "setNewTime(): Using current time\n"); + t = time(NULL); + } + struct tm tm; + localtime_r( &t, &tm ); + setNewTime( &tm ); +} + +/* +void Almanac::setNewTime( int day, int month, int year, int hour, int min, + int sec ) +{ + struct tm tm; + tm.tm_hour = hour; + tm.tm_min = min; + tm.tm_sec = sec; + tm.tm_mday = day; + tm.tm_mon = month - 1; + tm.tm_year = year - 1900; + setNewTime( &tm ); +}*/ + + + diff --git a/Almanac.h b/Almanac.h new file mode 100644 index 0000000..263b442 --- /dev/null +++ b/Almanac.h @@ -0,0 +1,130 @@ +#ifndef __ALMANAC_H__ +#define __ALMANAC_H__ +#include +#include "Debug.h" +#include "Config.h" +#include + + +class Almanac +{ +public: + + Almanac( Config cfg, time_t t = 0, double horizon = -50/60.0 ); + + void setNewTime( time_t t ); + void setNewTime( struct tm * ); + + + time_t sunrise; + time_t sunset; + int lengthOfDay; + +// Private variables +private: + Debug dbg; + Config cfg; + + float tz; + struct tm tm; + double julianDay; + double lon; // F6 + double lat; // F7 + double horizon; + + +private: + struct dataStruct + { + dataStruct( time_t t, int timeCorrection, double horizon, double lat, double lon, bool interpolate = true ); + + dataStruct *yesterday; + dataStruct *tomorrow; + + double julianDay; + double lat; + double lon; + int leapSeconds; // G4 + long double sunsMeanLongitude; // I13 + long double moonsMeanLongitude; // K13 + long double meanAnomalyOfTheSun; // L13 + long double sunsEquationOfCenter; // M13 + long double sunsTrueLongitude; // N13 + long double moonsMeanAscendingNode; // O13 + long double sunsApparentLongitude; // Q13 + long double T; // G13 + long double meanObliq; // R13 + long double NutationInObliq; // S13 + long double NutationInLong; // T13 + long double TrueObliq; // U13 + + struct + { + long double declination; // V + long double rightAscention; // X + long double rightAscentionCorrected; // Y + } sunsApparent; + + struct + { + long double mean; // AA + long double apparent; // AB + } siderealTimeAtMeridianAt0Hrs; + + long double horizonToNoonAngle; // AD + long double approxTransitTime; // AF + + struct + { + long double n, a, b, c; // For interpolation + long double rightAscention; // AK + long double siderealTimeAtGreenwich; // AM + long double hourAngle; // AO + } sunsInterpolated; + /*long double sunsActualRightAscention; // AK + long double siderealTimeAtGreenwich; // AM + long double rawHourAngle; // AN*/ + + long double approxRise; // AP + + struct + { + long double n; // AQ + long double rightAscention; // AR + long double siderealTimeAtGreenwich; // AT + long double a, b, c; // AH AI AJ + long double declination; // AX + long double hourAngle; // AY + long double altitude; // AZ + } sunsInterpolatedApparent; + + long double risingCorrection; // BA + long double approxSet; // BB; + + struct + { + long double rightAscention; // BD; + long double declination; // BE; + long double siderealTimeAtGreenwich; // BG + long double hourAngle; // BH + long double altitude; // BI + long double n,a,b,c; + } setting; + + long double settingCorrection; // BJ + + /*long double transitTime; + long double riseTime; + long double setTime; */ + + time_t sunrise; + time_t sunset; + time_t transitTime; + int lengthOfDay; + + + }; +public: +}; + +#endif diff --git a/Barometer.cpp b/Barometer.cpp new file mode 100644 index 0000000..cdb7cb1 --- /dev/null +++ b/Barometer.cpp @@ -0,0 +1,217 @@ +#include "Barometer.h" +#include "units.h" +#include + +Barometer::Barometer( double _elevation /* ft */): + vpAlgorithm( vpBolton ), + altimeterAlgorithm( altASOS ), + slpAlgorithm( slpManBar ), + + elevation( _elevation ), + dbg("Barometer"), + gravity ( 9.80665 ), + uGC ( 8.31432 ), + moleAir ( 0.0289644 ), + moleWater ( 0.01801528 ), + gasConstantAir ( uGC/moleAir ), + standardSLP ( 1013.25 ), + standardSlpInHg ( 29.921 ), + standardTempK ( 288.15 ), + earthRadius45 ( 6356.766 ), + standardLapseRate ( 0.0065 ), + standardLapseRateFt ( standardLapseRate * 0.3048 ), + vpLapseRateUS ( 0.00275 ), + manBarLapseRate ( 0.0117 ) +{ +} + +Barometer::~Barometer() +{ +} + +double Barometer::GeopotentialAltitude() +{ + float elevM = ft2m( elevation ); + return (earthRadius45 * 1000 * elevM) / ((earthRadius45 * 1000) + elevM ); +} + +double Barometer::StationPressureToAltimeter( + float stationPressure ) // inHg +{ + float geopEl = 0; + float k1 = 0, k2 = 0; + float pressureHPa = inHg2hPa( stationPressure ); + float elevationM = ft2m( elevation ); + switch ( altimeterAlgorithm ) + { + case altASOS: + // see ASOS training at http://www.nwstc.noaa.gov + // see also http://wahiduddin.net/calc/density_altitude.htm + return pow(pow( stationPressure, 0.1903 ) + ( 1.313e-5 * elevation), 5.255 ); + break; + case altASOS2: + geopEl = GeopotentialAltitude(); + k1 = standardLapseRate * gasConstantAir / gravity; // approx. 0.190263 + k2 = 8.41728638E-5; // (standardLapseRate / standardTempK) * (Power(standardSLP, k1) + return hPa2inHg(pow(pow( pressureHPa, k1) + (k2 * geopEl), 1/k1)); + break; + case altMADIS: + // from MADIS API by NOAA Forecast Systems Lab, see http://madis.noaa.gov/madis_api.html + k1 = 0.190284; // discrepency with calculated k1 probably because Smithsonian used less precise gas constant and gravity values + k2 = 8.4184960528E-5; // (standardLapseRate / standardTempK) * (Power(standardSLP, k1) + return hPa2inHg(pow(pow(pressureHPa - 0.3, k1) + (k2 * elevationM), 1/k1)); + break; + + case altNOAA: + // see http://www.srh.noaa.gov/elp/wxclc/formulas/altimeterSetting.html + k1 = 0.190284; // discrepency with k1 probably because Smithsonian used less precise gas constant and gravity values + k2 = 8.42288069E-5; // (standardLapseRate / 288) * (Power(standardSLP, k1SMT); + return hPa2inHg((pressureHPa - 0.3) * pow(1 + (k2 * (elevationM / pow(pressureHPa - 0.3, k1))), 1/k1)); + break; + case altWOB: + // see http://www.wxqa.com/archive/obsman.pdf + k1 = standardLapseRate * gasConstantAir / gravity; // approx. 0.190263 + k2 = 1.312603E-5; //(standardLapseRateFt / standardTempK) * Power(standardSlpInHg, k1); + return pow(pow(stationPressure, k1) + (k2 * elevation), 1/k1); + case altSMT: + // see WMO Instruments and Observing Methods Report No.19 at http://www.wmo.int/pages/prog/www/IMOP/publications/IOM-19-Synoptic-AWS.pdf + k1 = 0.190284; // discrepency with calculated value probably because Smithsonian used less precise gas constant and gravity values + k2 = 4.30899E-5; // (standardLapseRate / 288) * (Power(standardSlpInHg, k1SMT)); + geopEl = GeopotentialAltitude(); + return (stationPressure - 0.01) * pow(1 + (k2 * (geopEl / pow(stationPressure - 0.01, k1))), 1/k1); + + default: + dbg.printf(EMERG, "Unknown altimeter type!\n"); + } + return 0; +} + +double Barometer::SLPtoStationPressure( + float SLP, // inHg + float currentTemp, // degrees F + float meanTemp, // degrees F + int humidity ) // % +{ + return SLP/PressureReductionRatio( + SLP, + currentTemp, + meanTemp, + humidity ); +} + +double Barometer::SaturationVaporPressure( + float currentTemp ) // degreesF +{ + float tempC = F2C(currentTemp); + // see http://cires.colorado.edu/~voemel/vp.html + // comparison of vapor pressure algorithms + // see (for DavisVP) http://www.exploratorium.edu/weather/dewpoint.html + switch ( vpAlgorithm ) + { + case vpDavisVP: + return 6.112 * exp((17.62 * tempC )/(243.12 + tempC )); + break; + case vpBuck: // Buck (1996) + return 6.1121 * exp((18.678 - (tempC/234.5)) * tempC / ( 257.14 + tempC )); + break; + case vpBuck81: // Buck(1981) + return 6.1121 * exp((17.502 * tempC)/(240.97 + tempC)); + break; + case vpBolton: // Bolton(1980) + return 6.112 * exp(17.67 * tempC / (tempC + 243.5)); + break; + case vpTetenNWS: // Magnus Teten see www.srh.weather.gov/elp/wxcalc/formulas/vaporPressure.html + return 6.112 * pow(10,(7.5 * tempC / (tempC + 237.7))); + break; + case vpTetenMurray: // Magnus Teten (Murray 1967) + return pow(10, (7.5 * tempC / (237.5 + tempC)) + 0.7858); + break; + case vpTeten: // Magnus Teten see www.vivoscuola.it/US/RSIGPP3202/umidita/attivita/relhumONA.htm + return 6.1078 * pow(10, (7.5 * tempC / (tempC + 237.3))); + break; + default: + dbg.printf(EMERG, "Unknown vapor pressure algorithm!\n"); + + + + } + return 0; +} + +double Barometer::ActualVaporPressure( + float currentTemp, // degreesF + int humidity ) // % +{ + return ( humidity * SaturationVaporPressure( currentTemp )) / 100.0; +} + +double Barometer::HumidityCorrection( + float currentTemp, // degrees F + int humidity ) // humidity +{ + float vapPress = ActualVaporPressure( currentTemp, humidity ); + return ( vapPress * (( 2.3222e-9 * sqrt( ft2m( elevation ))) + ( 2.225e-5 * ft2m( elevation )) + 0.10743 )); +} + +double Barometer::VirtualTempK( + float pressureHPa, + float temp, // degrees F + int humidity ) +{ + const float epsilon = 1 - (moleWater / moleAir); // 0.37802 + float vapPres = 0; + // see http://www.univie.ac.at/IMG-Wien/daquamap/Parametergencom.html + // see also http://www.vivoscuola.it/US/RSIGPP3202/umidita/attiviat/relhumONA.htm + // see also http://wahiduddin.net/calc/density_altitude.htm + + // set buck + + VaporPressureAlgorithms v = vpAlgorithm; + vpAlgorithm = vpBuck; + vapPres = ActualVaporPressure(temp, humidity ); + vpAlgorithm = v; + return (F2K(temp)) / (1-(epsilon * (vapPres/pressureHPa))); + +} + +double Barometer::PressureReductionRatio( + float SLP, // inHg + float currentTemp, // degrees F + float meanTemp, // degrees F + int humidity ) // % +{ + float hCorr = 0; + float pressureHPa = inHg2hPa( SLP ); + float geopElevationM = GeopotentialAltitude(); + switch( slpAlgorithm ) + { + case slpDavisVP: + if ( humidity > 0 ) + { + hCorr = (9/5.0) * HumidityCorrection( currentTemp, humidity ); + } + return pow( 10, ( elevation / ( 122.894311 * ( meanTemp + 460 + ( elevation * vpLapseRateUS/2 ) + hCorr )))); + break; + case slpUnivie: + return exp(((gravity/gasConstantAir) * geopElevationM) + / (VirtualTempK(pressureHPa, meanTemp, humidity) + (geopElevationM * standardLapseRate/2))); + break; + case slpManBar: + // see WMO Instruments and Observing Methods Report No.19 at http://www.wmo.int/pages/prog/www/IMOP/publications/IOM-19-Synoptic-AWS.pdf + // see WMO Instruments and Observing Methods Report No.19 at http://www.wmo.ch/web/www/IMOP/publications/IOM-19-Synoptic-AWS.pdf + if (humidity > 0) + { + // set buck + VaporPressureAlgorithms v = vpAlgorithm; + vpAlgorithm = vpBuck; + hCorr = (9/5.0) * HumidityCorrection(currentTemp, humidity ); + vpAlgorithm = v; + // unset buck + } + return exp(geopElevationM * 6.1454E-2 / ( meanTemp + 459.7 + (geopElevationM * manBarLapseRate / 2) + hCorr)); + + default: + dbg.printf(EMERG, "Unknown SLP algorithm!\n"); + } + return 1; +} diff --git a/Barometer.h b/Barometer.h new file mode 100644 index 0000000..99c05e4 --- /dev/null +++ b/Barometer.h @@ -0,0 +1,107 @@ +#ifndef __BAROMETER_H__ +#define __BAROMETER_H__ + +#include "Debug.h" + +class Barometer +{ + +public: + enum VaporPressureAlgorithms { + vpDavisVP, // algorithm closely approximates calculation used by Davis + // Vantage Pro weather stations and software + vpBuck, // this and the remaining algorithms described at + // http://cires.colorado.edu/~voemel/vp.html + vpBuck81, + vpBolton, + vpTetenNWS, + vpTetenMurray, + vpTeten + }; + + enum AltimeterAlgorithms { + altASOS, // formula described in the ASOS training docs + altASOS2, // metric formula that was likely used to derive the altASOS + // formula + altMADIS, // apparently the formula used by the MADIS system + altNOAA, // essentially the same as aaSMT with any result differences + // caused by unit conversion rounding error and geometric + // vs. geopotential elevation + altWOB, // Weather Observation Handbook (algorithm similar to altASOS + // & altASOS2 - main differences being precision of constants + // used) + altSMT // Smithsonian Meteorological Tables (1963) + }; + enum SLPAlgorithms { + slpDavisVP, // algorithm closely approximates SLP calculation used + // inside Davis Vantage Pro weather equipment console + // (http://www.davisnet.com/weather) + slpUnivie, // http://www.univie.ac.at/IMG-Wien/daquamap/Parametergencom.html + slpManBar // from Manual of Barometry (1963) + }; + + Barometer( double elevation /* ft */); + ~Barometer(); + + double SLPtoStationPressure( + float SLP, // inHg + float currentTemp, // degrees F + float meanTemp, // degrees F + int humidity ); // % ( 87 = 87%) + + double StationPressureToAltimeter( + float stationPressure );// inHg + +private: + double PressureReductionRatio( + float SLP, // inHg + float currentTemp, // degrees F + float meanTemp, // degrees F + int humidity ); // % + + double HumidityCorrection( + float currentTemp, // degrees F + int humidity ); // humidity + + double ActualVaporPressure( + float currentTemp, // degreesF + int humidity ); // % + + double SaturationVaporPressure( + float currentTemp ); // degreesF + + double GeopotentialAltitude(); + + double VirtualTempK( + float pressureHPa, + float temp, // degrees F + int humidity ); + +public: + VaporPressureAlgorithms vpAlgorithm; + AltimeterAlgorithms altimeterAlgorithm; + SLPAlgorithms slpAlgorithm; + +private: + float elevation; + Debug dbg; + +private: + // U.S. Standard Atmosphere (1976) constants + const float gravity; // g at sea level at latitude 45.5 degrees in m/sec^2 + const float uGC; // universal gas constant in J/mole-K + const float moleAir; // mean molecular mass of air in kg/mole + const float moleWater; // molecular weight of water in kg/mole + const float gasConstantAir; // (287.053) gas constant for air in J/kgK + const float standardSLP; // standard sea level pressure in hPa + const float standardSlpInHg; // standard sea level pressure in inHg + const float standardTempK; // standard sea level temperature in Kelvin + const float earthRadius45; // radius of the earth at latitude 45.5 degrees in km + const float standardLapseRate; // standard lapse rate (6.5C/1000m i.e. 6.5K/1000m) + const float standardLapseRateFt; // (0.0019812) standard lapse rate per foot (1.98C/1000ft) + const float vpLapseRateUS; // lapse rate used by Davis VantagePro (2.75F/1000ft) + const float manBarLapseRate; // lapse rate from Manual of Barometry (11.7F/1000m, which = 6.5C/1000m) + +}; + +#endif // __BAROMETER_H__ diff --git a/CWOP.cpp b/CWOP.cpp new file mode 100644 index 0000000..101a173 --- /dev/null +++ b/CWOP.cpp @@ -0,0 +1,316 @@ +#include "CWOP.h" +#include "units.h" +#include +#include +#include +#include /* for socket(), connect(), send(), and recv() */ +#include /* for sockaddr_in and inet_addr() */ +#include /* for gethostbyname */ +#include /* for perror */ +#include + +// Cygwin doesn't have these. Why not? +#include "gethostbyname_r.h" + +#ifndef MSG_EOR +#define MSG_EOR (0) +#endif + +#ifndef MSG_WAITALL +#define MSG_WAITALL (0) +#endif +// end cygwin hackery + +CWOP::CWOP( Config cfg, WeatherData *_wd ): + WeatherSink( cfg, _wd, "CWOP" ) +{ + latitude = DecimalLatitudeToLORAN(cfg.getDouble("latitude")); + longitude = DecimalLongitudeToLORAN(cfg.getDouble("longitude")); + snprintf(login, CWOP_PACKET_SIZE, "user %s pass %s ver %s\n\r", + cfg.getString("cwop_user").c_str(), + cfg.getString("cwop_password").c_str(), + cfg.getString("sw_vers").c_str() ); + pthread_mutex_init( ¬ify, NULL ); + pthread_mutex_lock( ¬ify ); + + + startMain(); +} + +CWOP::~CWOP() +{ + endMain(); +} + +bool CWOP::createPacket() +{ + char header[255]; + char buf[255]; + snprintf( header, 40, "%s>APRS,TCPIP*:", cfg.getString("cwop_user").c_str()); + time_t t = time(NULL); + struct tm gmt; + char st[32]; + + gmtime_r( &t, &gmt ); + strftime( st, 31, "%d%H%M", &gmt ); + + // Header, including station ID, time, latitude, and longitude + snprintf(packet, 64, "%s@%sz%s/%s", header, st, latitude.c_str(), + longitude.c_str()); + + // Wind Direction: _... + if ( wd->hasData( WeatherData::averageWindDirection ) ) + snprintf(buf, 32, "_%03d", + (int)wd->getValue(WeatherData::averageWindDirection, 0 )); + else + snprintf(buf, 32, "_..." ); + strcat( packet, buf ); + + // Wind Speed and Gust: /...g... + if ( wd->hasData( WeatherData::averageWindSpeed ) ) + snprintf(buf, 32, "/%03dg%03d", + (int)wd->getValue(WeatherData::averageWindSpeed, 0 ), + (int)wd->getValue(WeatherData::windGust, 0 )); + else + snprintf(buf, 32, "/...g..." ); + strcat( packet, buf ); + + // Outside Temp: t... + if ( wd->hasData( WeatherData::outsideTemp ) ) + snprintf(buf, 32, "t%03d", + (int)wd->getValue(WeatherData::outsideTemp, 0 )); + else + snprintf(buf, 32, "t..." ); + strcat( packet, buf ); + + // Daily Rain: P... + if ( wd->hasData( WeatherData::dailyRain ) ) + { + snprintf(buf, 32, "P%03d", + (int)(wd->getValue(WeatherData::dailyRain, 0 ))); + strcat( packet, buf ); + } + + // Hourly Rain: r... + if ( wd->hasData( WeatherData::rainRate ) ) + { + snprintf(buf, 32, "r%03d", + (int)(wd->getValue(WeatherData::rainRate, 0 ))); + strcat( packet, buf ); + } + + // 24 hour rain: p... + if ( wd->hasData( WeatherData::rain24Hour ) ) + { + snprintf(buf, 32, "p%03d", + (int)(wd->getValue(WeatherData::rain24Hour, 0 ))); + strcat( packet, buf ); + } + + // Altimeter: b..... + if ( wd->hasData( WeatherData::altimeter ) ) + { + snprintf(buf, 32, "b%05d", + (int)(inHg2hPa(wd->getValue(WeatherData::altimeter, 0 )) * 10)); + strcat( packet, buf ); + } + + // Humidity: h.. + if ( wd->hasData( WeatherData::outsideHumidity ) ) + { + int oh = (int)wd->getValue(WeatherData::outsideHumidity, 0 ); + snprintf(buf, 32, "h%02d", oh == 100 ? 0 : oh ); + strcat( packet, buf ); + } + + // Solar Radiation: L... or l... for >= 1000 + if ( wd->hasData( WeatherData::solarRadiation ) ) + { + int srad = (int)wd->getValue(WeatherData::solarRadiation, 0 ); + if ( srad >= 1000 ) + { + snprintf( buf, 32, "l%03d", srad - 1000 ); + } + else + { + snprintf( buf, 32, "L%03d", srad ); + } + strcat( packet, buf ); + } + + // Trailer. SW ID + strcat( packet, ".DsWP\n\r" ); + //strcat( packet, "/WX Report {WXPro}\n\r" ); + + return true; +} + +int CWOP::sendToServer( char *fmt, ... ) +{ + int rval = 0; + va_list args; + char message[1024]; + va_start( args, fmt ); + if ( vsnprintf(message, 1024, fmt, args ) > 1024 ) + { + dbg.printf(ALERT, "Message size to large!\n"); + return false; + } + va_end( args ); + rval = send( sock, message, strlen(message), MSG_EOR | MSG_DONTWAIT ); + dbg.printf(DEBUG, "send: %s\n", message); + return rval; +} + +int CWOP::receiveFromServer( char *message, size_t size, size_t max_size ) +{ + fd_set rfds; + FD_ZERO( &rfds ); + FD_SET( sock, &rfds ); + struct timeval tv; + tv.tv_sec = 5; + tv.tv_usec = 0; + bzero( message, max_size ); + + if ( select( sock + 1, &rfds, NULL, NULL, &tv ) > 0 ) + { + recv( sock, message, size, MSG_WAITALL ); + dbg.printf(DEBUG, "receive: %s\n", message ); + return true; + } + return false; + +} + +void CWOP::newData() +{ + pthread_mutex_unlock( ¬ify ); +} + +bool CWOP::connectToServer( char *serverName, size_t serverSize ) +{ + char errBuf[128]; + int gethostBuf[255]; + struct sockaddr_in cwopServerAddr; + struct hostent host; + struct hostent *res; + int fnord; + std::vector::iterator startingServer = currentServer; + + if (( sock = socket( AF_INET, SOCK_STREAM, 0 )) <= 0 ) + { + strerror_r( errno, errBuf, 128 ); + dbg.printf(EMERG, "Could not create socket: %s", errBuf ); + return false; + } + + const char *server = cfg.getString("cwop_server").c_str(); + memset( &cwopServerAddr, 0, sizeof( cwopServerAddr )); + + dbg.printf(DEBUG, "server: %s\n", server ); + if ( ( gethostbyname_r( server, + &host, (char*)&gethostBuf, sizeof(gethostBuf), &res, &fnord )) != 0 ) + { + dbg.printf(ERR, "gethostbyname failed: %s\n", hstrerror( fnord )); + return false; + } + + dbg.printf(INFO, "CWOP Servers:\n"); + for ( int i = 0; host.h_addr_list[i] != 0; i++ ) + { + dbg.printf(INFO, "\t%s\n",inet_ntoa(*((struct in_addr *)host.h_addr_list[i]))); + } + + + for ( int i = 0; host.h_addr_list[i] != 0; i++ ) + { + // Set the out IP address + memcpy( &cwopServerAddr.sin_addr, host.h_addr_list[i], host.h_length ); + cwopServerAddr.sin_family = AF_INET; + cwopServerAddr.sin_port = htons(cfg.getInteger("cwop_port")); + if ( connect( sock, ( const sockaddr *)&(cwopServerAddr), sizeof( struct sockaddr_in)) < 0 ) + { + strerror_r( errno, errBuf, 128 ); + dbg.printf(ERR, "could not connect to server: %s\n", inet_ntoa(*((struct in_addr *)host.h_addr_list[i]))); + } + else + { + strncpy( serverName, inet_ntoa(*((struct in_addr *)host.h_addr_list[i])), serverSize); + return true; + } + } + close( sock ); + return false; +} + +void CWOP::main() +{ + char buf[255]; + while( threadRunning ) + { + pthread_mutex_lock( ¬ify ); + if ( !threadRunning ) + { + dbg.printf(NOTICE, "Exiting\n"); + return; + } + + if ( !wd->now->isNewMinute() || wd->now->getMinute() % 10 != cfg.getInteger("cwop_ul_min")) + { + dbg.printf(DEBUG, "Not on minute bounary\n"); + continue; + } + + if ( cfg.getInteger("test_only") != 1 ) + { + createPacket(); + int randSleepTime = random() % 10; + dbg.printf(DEBUG, "Sleeping %d seconds before sending data\n", randSleepTime ); + sleep( randSleepTime ); + char serverName[64]; + if ( !connectToServer( serverName, 64) ) + { + dbg.printf(NOTICE, "Could not connect to any CWOP server\n"); + continue; + } + + dbg.printf(DEBUG, "sending data to CWOP (%s)\n", serverName); + if ( !receiveFromServer( buf, 20, 255 ) ) + { + dbg.printf(ERR, "Could not read from server\n"); + close(sock); + continue; + } + // Check if this is a real APRS server + for ( int i = 0; i < 20; i++ ) + { + if ( isalpha(buf[i]) ) + buf[i] = tolower( buf[i] ); + } + + if ( strstr( buf, "aprs" ) == 0 ) + { + dbg.printf(ERR, "Expected '*aprs*', received: %s\n", buf ); + close( sock ); + continue; + } + + if ( sendToServer( login ) != -1 ) + { + if (!receiveFromServer( buf, 25, 255 )) + { + dbg.printf(ERR, "Could not read from server\n"); + close(sock); + continue; + } + + sendToServer( packet ); + } + close( sock ); + } + else + { + dbg.printf(NOTICE, "pretending: CWOP data uploading to %s\n", cfg.getString("cwop_server").c_str()); + } + } +} diff --git a/CWOP.h b/CWOP.h new file mode 100644 index 0000000..3ebaa46 --- /dev/null +++ b/CWOP.h @@ -0,0 +1,37 @@ +#ifndef __CWOP_H__ +#define __CWOP_H__ +#include "WeatherSink.h" + +#define CWOP_PACKET_SIZE (1024) + +class CWOP: public WeatherSink +{ +public: + CWOP( Config cfg, WeatherData *wd ); + ~CWOP(); + + void newData(); + void main(); + + +private: + bool createPacket( void ); + int sendToServer( char *fmt, ... ); + int receiveFromServer( char *buffer, size_t size, size_t maxsize ); + bool connectToServer( char *server, size_t serverSize ); + +private: + int sock; + char packet[CWOP_PACKET_SIZE]; + char login[CWOP_PACKET_SIZE]; + std::string latitude; + std::string longitude; + pthread_mutex_t notify; + std::vector servers; + std::vector::iterator currentServer; +}; + + + + +#endif //__CWOP_H__ diff --git a/Config.cpp b/Config.cpp new file mode 100644 index 0000000..aecf76a --- /dev/null +++ b/Config.cpp @@ -0,0 +1,129 @@ +#include "Config.h" + +using namespace std; + + +#include "Config.h" +#include +#include +#include +#include + + +std::string Config::trim( std::string str ) +{ + unsigned int i = str.length(); + + // Erase space at the end + for ( ; i != 0; i-- ) + { + if ( !isspace(str[i]) && isprint(str[i]) ) + { + i++; + break; + } + } + str.erase( i, str.length() ); + + + // Erase space at the beginning + for ( i = 0; i != str.length(); i++ ) + { + if ( !isspace(str[i]) && isprint(str[i]) ) + { + break; + } + } + str.erase( 0, i ); + + return str; + +} + +Config::Config( const char *configFile ): + dbg("config") +{ + struct stat statBuf; + std::string line; + + if ( stat( configFile, &statBuf ) != -1 ) + { + std::ifstream inFile(configFile); + std::string key, value, assign; + if ( inFile.is_open() ) + { + while ( !inFile.eof() ) + { + getline( inFile, line ); + std::string::size_type comment = line.find("#", 0 ); + if ( comment != std::string::npos ) + { + line.erase( comment, line.length() ); + } + + line = trim( line ); + + + if ( line.empty() ) + continue; + + std::string::size_type assign = line.find('='); + if ( assign != std::string::npos ) + { + key = trim(line.substr(0,assign)); + value = trim(line.substr(assign+1)); + dbg.printf(NOTICE, "%s = '%s'\n", key.c_str(), value.c_str()); + // printf("line: \"%s\" (%d)\n", line.c_str(), line.length()); + //printf("key = \"%s\" value=\"%s\"\n", key.c_str(), + //value.c_str()); + cfg[key] = value; + } + } + + inFile.close(); + } + } + else + { + printf("\nERROR!\n"); + printf("Could not open config file: %s\n", configFile ); + dbg.printf(EMERG, "Error opening config file: %s\n", configFile ); + dbg.printf(EMERG, "Exiting!\n"); + exit(0); + } + + // +} + + + + +string Config::getString( string key ) +{ + if ( cfg.find(key) == cfg.end() ) + { + dbg.printf(EMERG, "Could not find config key: %s\n", key.c_str()); + return ""; + } + return cfg[key]; +} + +double Config::getDouble( string key ) +{ + if ( cfg.find(key) == cfg.end() ) + { + dbg.printf(EMERG, "Could not find config key: %s\n", key.c_str()); + return 0; + } + return atof(cfg[key].c_str()); +} + +int Config::getInteger( string key ) +{ + if ( cfg.find(key) == cfg.end() ) + { + dbg.printf(EMERG, "Could not find config key: %s\n", key.c_str()); + return 0; + } + return atoi( cfg[key].c_str() ); +} diff --git a/Config.h b/Config.h new file mode 100644 index 0000000..e416e7c --- /dev/null +++ b/Config.h @@ -0,0 +1,25 @@ +#ifndef __CONFIG_H__ +#define __CONFIG_H__ +#include +#include +#include "Debug.h" + + +class Config +{ + public: + Config( const char *configFile ); + + double getDouble( std::string ); + std::string getString( std::string ); + int getInteger( std::string ); + + private: + std::string trim( std::string str ); + + private: + std::map< std::string, std::string > cfg; + Debug dbg; +}; + +#endif // __CONFIG_H__ diff --git a/Database.cpp b/Database.cpp new file mode 100644 index 0000000..c5da906 --- /dev/null +++ b/Database.cpp @@ -0,0 +1,128 @@ +#include "Database.h" +#include +#include "sql.h" + + +using namespace std; +Database::Database( const char *_filename ): + filename( _filename ), + dbg("Database") +{ + string dbVersion; + int rc = sqlite3_open( filename, &db ); + if ( rc ) + { + fprintf(stderr, "Couldn't open database: %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + exit(0); + } + + dbg.printf(NOTICE, "We are operating at DB Version: %s\n", DB_VERSION ); + + dbVersion = query("select version from schema;")[0]["version"]; + dbg.printf(NOTICE, "File version is : %s\n", dbVersion.c_str()); + while ( dbVersion != DB_VERSION ) + { + int ver = atoi( dbVersion.c_str() ); + dbg.printf(WARNING,"Upgrading database schema to version: %d\n", ver + 1 ); + switch ( ver ) + { + case 0: + query(SQL_schema); + query(dailyObsSchema); + query(hourlySummarySchema); + query(dailySummarySchema); + query(windSchema); + query(rainSchema); + break; + case 1: + query(update1to2); + break; + case 2: + query(update2to3); + break; + case 3: + query(update3to4); + break; + case 4: + query(update4to5); + break; + case 5: + query(update5to6); + break; + case 6: + query(update6to7); + break; + case 7: + query(update7to8); + case 8: + query(update8to9); + } + + dbVersion = query("select version from schema;")[0]["version"]; + if (atoi( dbVersion.c_str() ) == ver ) + { + dbg.printf(EMERG, "Could not upgrade database to version %d. ABORTING!\n", ver + 1); + fprintf(stderr, "Could not upgrade database to version %d. ABORTING!\n", ver + 1); + exit(0); + } + } + +} + +dbResult Database::query( const char *format, ... ) +{ + dbResult rval; + int bufSize = 1024; + char *buf = (char*)malloc( bufSize ); + va_list args; + int newSize = 0; + char **resultp; + int nrow; + int ncolumn; + int rc; + char *zErrMsg = 0; + + va_start( args, format ); + + newSize = vsnprintf( buf, bufSize, format, args ); + if ( newSize >= bufSize ) + { + va_end( args ); + va_start( args, format ); + newSize += 1; // account for the \0 + free( buf ); + buf = (char*)malloc( newSize ); + newSize = vsnprintf( buf, newSize, format, args ); + + } + + dbg.printf(DEBUG, "query: %s\n", buf ); + rc = sqlite3_get_table( db, buf, &resultp, &nrow, &ncolumn, &zErrMsg ); + if ( rc != SQLITE_OK ) + { + dbg.printf(CRIT, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); + dbg.printf(CRIT, "SQL error: %s\n", zErrMsg ); + dbg.printf(CRIT, "QUERY: %s\n", buf ); + dbg.printf(CRIT, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); + sqlite3_free( zErrMsg ); + } + for ( int r = 0; r < nrow; r++ ) + { + for ( int c = 0; c < ncolumn; c++ ) + { + char *value = resultp[ncolumn*(r+1)+c]; + if ( value != NULL ) + { + dbg.printf(DEBUG, "(%d) %s = %s\n", r, resultp[c], resultp[ncolumn*(r+1)+c ]); + rval[r][resultp[c]] = resultp[ncolumn*(r+1)+c]; + } + } + } + + sqlite3_free_table(resultp); + + free( buf ); + va_end( args ); + return rval; +} diff --git a/Database.h b/Database.h new file mode 100644 index 0000000..76739a8 --- /dev/null +++ b/Database.h @@ -0,0 +1,38 @@ +#ifndef __DATABASE_H__ +#define __DATABASE_H__ +#include +#include +#include +#include "Debug.h" +#include +#include + + +class dbRow +{ + public: + std::string &operator[] ( std::string column ) { return m[column]; }; + double asDouble( std::string column ) { return atof(m[column].c_str()); }; + int asInteger( std::string column ) { return atoi(m[column].c_str()); }; + const char *asCString( std::string column ) { return m[column].c_str(); }; + + private: + std::map< std::string, std::string > m; +}; + +typedef std::map< int, dbRow > dbResult; + +class Database +{ + public: + Database( const char *filename = "/usr/local/var/wxhistory.db" ); + dbResult query( const char *format, ... ) + __attribute__((format(printf,2,3))); + + private: + sqlite3 *db; + const char *filename; + Debug dbg; +}; + +#endif // __DATABASE_H__ diff --git a/Debug.cpp b/Debug.cpp new file mode 100644 index 0000000..f590dad --- /dev/null +++ b/Debug.cpp @@ -0,0 +1,67 @@ +#include "Debug.h" +#include +#include +#include +#include +#include +#include +#include +#include + +FILE *Debug::fp = 0; + +void DebugReOpen( int sigNum ) +{ + if ( sigNum == SIGHUP ) + { + fclose(Debug::fp); + Debug::fp = fopen(LOGFILE, "a+" ); + } +} + +void Debug::setName( const char *_name ) +{ + name = _name; +} + +Debug::Debug( const char *_name): + name(_name) +{ + logLevel = NOTICE; + if ( fp == 0 ) + { + //::printf("Setting signal handler\n"); + signal( SIGHUP, DebugReOpen ); /* set own handler */ + fp = fopen(LOGFILE, "a+" ); + if ( fp == NULL ) + { + ::printf("Error opening %s: %s", LOGFILE, strerror(errno)); + } + //fp = stderr; + } +} + +int Debug::printf( DEBUG_LEVEL level, const char *format, ... ) +{ + int rval; + char buf[30]; + char buf2[60]; + if ( level <= logLevel ) + { + va_list args; + va_start( args, format ); + struct timeval tv; + struct timezone tz; + struct tm tm; + gettimeofday( &tv, &tz ); + localtime_r(&tv.tv_sec, &tm); + strftime(buf, 29, "%b %d %H:%M:%S", &tm); + snprintf(buf2, 59, "%s.%02d <%d>: %s: ", buf, (int)tv.tv_usec/10000, level, name ); + fprintf(fp, buf2); + rval = vfprintf( fp, format, args ); + va_end( args ); + fflush(fp); +// sync(); + } + return rval; +} diff --git a/Debug.h b/Debug.h new file mode 100644 index 0000000..7d21f91 --- /dev/null +++ b/Debug.h @@ -0,0 +1,36 @@ +#ifndef __WX_DEBUG_H__ +#define __WX_DEBUG_H__ +#include + +#define LOGFILE "/var/log/wxpro.log" + +enum DEBUG_LEVEL { EMERG=0, ALERT, CRIT, ERR, WARNING, NOTICE, INFO, DEBUG }; +/* +EMERG system is unusable +ALERT action must be taken immediately +CRIT critical conditions +ERR error conditions +WARNING warning conditions +NOTICE normal but significant condition +INFO informational +DEBUG debug-level messages +*/ + +class Debug +{ +public: + Debug( const char *name ); + + void setName( const char *name ); + + int printf( DEBUG_LEVEL level, const char * format, ... ); + + int logLevel; + + static FILE *fp; + +private: + const char *name; +}; + +#endif // __WX_DEBUG_H__ diff --git a/Hysteresis.cpp b/Hysteresis.cpp new file mode 100644 index 0000000..3062f3e --- /dev/null +++ b/Hysteresis.cpp @@ -0,0 +1,85 @@ +#include "Hysteresis.h" +#include + +// Thanks Woz: +// http://publib.boulder.ibm.com/infocenter/macxhelp/v6v81/index.jsp?topic=/com.ibm.vacpp6m.doc/language/ref/clrc15cplr385.htm + +using namespace std; + +Hysteresis::Hysteresis( int history ): + maxHistory( history ), + dbg("Hysteresis") +{ + if ( maxHistory < 60 && maxHistory > 1 ) + { + dbg.printf(CRIT, "Unreasonably low hysteresis. Setting to 60 seconds"); + maxHistory = 60; + } +} + +Hysteresis::~Hysteresis() +{ +} + +const double &Hysteresis::operator=( const double &val ) +{ + if ( maxHistory <= 1 ) + { + newVal = val; + return val; + } + time_t t = time(NULL); + double sum = 0; + history[t] = val; + + for ( map< time_t, double >::iterator iter = history.begin(); + iter != history.end(); ) + { + if ( iter->first < t - maxHistory ) + { + history.erase(iter++); + } + else + { + sum += iter->second; + ++iter; + } + } + + newVal = sum / history.size(); + + dbg.printf(DEBUG, "sum = %f size = %d newVal = %f\n", sum, history.size(), newVal ); + return newVal; +} + +void Hysteresis::setHistorySize( int history, const char *name ) +{ + if ( name != NULL ) + { + dbg.setName( name ); + } + if ( maxHistory < 60 && maxHistory > 1 ) + { + dbg.printf(CRIT, "Unreasonably low hysteresis. Setting to 60 seconds"); + maxHistory = 60; + } + else + { + maxHistory = history; + } +} + +Hysteresis::operator float() +{ + return newVal; +} + +Hysteresis::operator double() +{ + return newVal; +} + +Hysteresis::operator int() +{ + return (int)newVal; +} diff --git a/Hysteresis.h b/Hysteresis.h new file mode 100644 index 0000000..4e2f4fb --- /dev/null +++ b/Hysteresis.h @@ -0,0 +1,30 @@ +#ifndef __HYSTERESIS_H__ +#define __HYSTERESIS_H__ + +#include +#include "Debug.h" +#include + +class Hysteresis +{ +public: + Hysteresis( int history = 0); + ~Hysteresis(); + const double &operator =( const double &val ); + + void setHistorySize( int history, const char *name = NULL ); + + operator float(); + operator int(); + operator double(); + +private: + std::map< time_t, double > history; + int maxHistory; + double newVal; + Debug dbg; + +}; + + +#endif // __HYSTERESIS_H__ diff --git a/Now.cpp b/Now.cpp new file mode 100644 index 0000000..7aebe2d --- /dev/null +++ b/Now.cpp @@ -0,0 +1,115 @@ +#include "Now.h" +#include +#include + +Now::Now(): + dbg("Now") +{ + updateTime(); + srandom( currentTv.tv_usec ); +} + +Now::~Now() +{ +} + +void Now::updateTime() +{ + memcpy( &lastTs, ¤tTs, sizeof( struct tm )); + lastTv.tv_sec = currentTv.tv_sec; + lastTv.tv_usec = currentTv.tv_usec; + + gettimeofday( ¤tTv, &tz ); + localtime_r( ¤tTv.tv_sec, ¤tTs ); +} + +time_t Now::unixtime() +{ + return currentTv.tv_sec; +} + +size_t Now::RFC2822( char *s, size_t max ) +{ + return strftime( s, max, "%a, %d %b %Y %H:%M:%S %z", ¤tTs ); +} + +size_t Now::RFC1123( char *s, size_t max ) +{ + return strftime( s, max, "%a, %d %b %Y %T %Z", ¤tTs ); +} + +size_t Now::mySQL( char *s, size_t max ) +{ + return strftime( s, max, "%Y-%m-%d %H:%M:%S", ¤tTs ); +} + +float Now::timeSinceLast() +{ + struct timeval tv; + struct timezone tz; + gettimeofday( &tv, &tz ); + + float rval = (tv.tv_sec - currentTv.tv_sec) + ( tv.tv_usec - currentTv.tv_usec )/1000000.0; + return rval; +} + +bool Now::isNewMinute( ) +{ + if ( lastTs.tm_min != currentTs.tm_min ) + { + return true; + } + else + { + return false; + } +} + +bool Now::isNewHour( ) +{ + if ( lastTs.tm_hour != currentTs.tm_hour ) + { + return true; + } + else + { + return false; + } +} + +bool Now::isNewDay( ) +{ + if ( lastTs.tm_mday != currentTs.tm_mday ) + { + return true; + } + else + { + return false; + } +} + +int Now::getMinute() +{ + return currentTs.tm_min; +} + +int Now::getHour() +{ + return currentTs.tm_hour; +} + +int Now::getDayOfYear() +{ + return currentTs.tm_yday+1; +} + +int Now::isDST() +{ + return currentTs.tm_isdst; +} + +double Now::getDecimalTime() +{ + return (currentTs.tm_hour + currentTs.tm_min / 60.0); +} diff --git a/Now.h b/Now.h new file mode 100644 index 0000000..1a8cf85 --- /dev/null +++ b/Now.h @@ -0,0 +1,51 @@ +#ifndef __NOW_H__ +#define __NOW_H__ + +#include +#include +#include +#include "Debug.h" + +#define ONE_MINUTE (60) +#define TWO_MINUTES (2 * ONE_MINUTE) +#define TEN_MINUTES (10 * ONE_MINUTE) +#define ONE_HOUR (ONE_MINUTE * 60 ) +#define ONE_DAY (ONE_HOUR * 24 ) + +class Now +{ + public: + Now(); + + ~Now(); + + void updateTime(); + + time_t unixtime(); + size_t RFC2822( char *s, size_t max ); + size_t RFC1123( char *s, size_t max ); + size_t mySQL( char *s, size_t max ); + struct timeval timeval(); + float timeSinceLast( ); + + bool isNewMinute( ); + bool isNewHour( ); + bool isNewDay( ); + + int getMinute(); + int getHour(); + int getDayOfYear(); + int isDST(); + double getDecimalTime(); + + private: + struct tm currentTs; + struct tm lastTs; + struct timezone tz; + struct timeval currentTv; + struct timeval lastTv; + Debug dbg; +}; + + +#endif // __NOW_H__ diff --git a/Rain.cpp b/Rain.cpp new file mode 100644 index 0000000..37b0b06 --- /dev/null +++ b/Rain.cpp @@ -0,0 +1,148 @@ +#include "Rain.h" +#include "units.h" +#include "math.h" + +using namespace std; + +Rain::Rain( Now *_now, Database &_db ): + oldDayRain(-1), + now(_now), + dbg("Rain"), + db( _db ), + instantRain(0), + lastRainTime(0) +{ + restore(); +} + +Rain::~Rain() +{ + dump(); +} + + +void Rain::restore() +{ + dbResult result = db.query("select * from rainData;"); + for ( dbResult::iterator iter = result.begin(); iter!=result.end(); ++iter ) + { + time_t t = iter->second.asInteger("time"); + if ( t > now->unixtime() - MAX_RAIN_DURATION ) + { + unsigned int amount = iter->second.asInteger("amount"); + rainAccum[t] = amount; + } + } + dbg.printf(NOTICE, "Restored %d rain values (sum = %.2f)\n", result.size(), + getSum(MAX_RAIN_DURATION)/100.0); +} + +void Rain::dump() +{ + time_t t; + float amount; + dbg.printf(NOTICE, "Dumping %d rain values to database\n", rainAccum.size()); + + db.query("BEGIN TRANSACTION;"); + db.query("DELETE FROM rainData;"); + for ( rainAccumulation_t::iterator iter = rainAccum.begin(); + iter != rainAccum.end(); ++iter ) + { + t = iter->first; + amount = iter->second; + db.query("insert into rainData ( time, amount ) VALUES ( %d, %f );", (int)t, amount ); + } + db.query("COMMIT;"); +} + + + + +void Rain::update( unsigned int value ) +{ + if ( oldDayRain < 0 ) + { + dbg.printf(CRIT, "initializing oldDayRain to %f\n", value ); + oldDayRain = value; + return; + } + + int newRain = value - oldDayRain; + // Handle rollover midnight case. + if ( newRain < 0 ) + { + dbg.printf(CRIT, "Rain Rollover\n"); + newRain = value; + } + + // There is no need to fill up the table if we didn't get any data + if ( newRain != 0 ) + { + rainAccum[now->unixtime()] = newRain; + } + + for ( rainAccumulation_t::iterator iter = rainAccum.begin(); + iter != rainAccum.end(); ) + { + if ( iter->first <= now->unixtime() - MAX_RAIN_DURATION ) + { + rainAccum.erase(iter++); + } + else + { + ++iter; + } + } + + time_t timeSinceLastRain = now->unixtime()-lastRainTime; + if ( timeSinceLastRain < 60*ONE_MINUTE ) + { + if ( newRain ) + { + dbg.printf(DEBUG, "newRain: %d\n", newRain ); + dbg.printf(DEBUG, "timeSinceLastRain: %d\n", timeSinceLastRain ); + + // Only compute new amount if we have new rain + instantRain = (int)(newRain / (timeSinceLastRain/3600.0)); // convert to hours + dbg.printf(DEBUG, "instantRain: %d\n", instantRain ); + } + else // If we didn't get new rain, we need to compute the current rain + // rate as if the next round will have rain. + { + int newInstantRain = (int)(1 / ( timeSinceLastRain/3600.0)); + if ( newInstantRain < instantRain ) + { + dbg.printf(DEBUG, "Rain rate decay algorithm activated. New rain rate = %d\n", newInstantRain ); + instantRain = newInstantRain; + } + } + } + else + { + instantRain = 0; + } + + if ( instantRain < 0 ) + instantRain = 0; + + if ( newRain ) + lastRainTime = now->unixtime(); + + oldDayRain = value; +} + +unsigned int Rain::getSum( time_t duration ) +{ + unsigned int rval = 0; + int maxRateAge = now->unixtime() - duration; + for ( rainAccumulation_t::iterator iter = rainAccum.begin(); + iter != rainAccum.end(); ++iter ) + { + if ( iter->first > maxRateAge ) + { + rval += iter->second; + } + } + return rval; +} + diff --git a/Rain.h b/Rain.h new file mode 100644 index 0000000..7d1b134 --- /dev/null +++ b/Rain.h @@ -0,0 +1,47 @@ +#ifndef __RAIN_H__ +#define __RAIN_H__ + +#include "Now.h" +#include +#include +#include "Database.h" + +#define MAX_RAIN_DURATION ( 24 * ONE_HOUR ) + +class Rain +{ + typedef std::map< time_t, int > rainAccumulation_t; + + + public: + Rain( Now *now, Database &_db ); + ~Rain( ); + + void update( unsigned int value ); + + unsigned int getSum( time_t duration ); + + unsigned int getRate( void ) + { + return getSum( ONE_HOUR ); + } + + double getInstantRainrate() + { + return instantRain; + } + + void dump(); + void restore(); + + private: + rainAccumulation_t rainAccum; + int oldDayRain; + Now *now; + Debug dbg; + Database db; + int instantRain; + time_t lastRainTime; +}; + +#endif // __RAIN_H__ diff --git a/Sun.cpp b/Sun.cpp new file mode 100644 index 0000000..c86d278 --- /dev/null +++ b/Sun.cpp @@ -0,0 +1,10 @@ +#include "Sun.h" + +Sun::Sun( Config _cfg ): + cfg( _cfg ) +{ +} + +Sun::sunrise( double alt ) +{ +} diff --git a/Sun.h b/Sun.h new file mode 100644 index 0000000..e7103ee --- /dev/null +++ b/Sun.h @@ -0,0 +1,8 @@ +#include "Config.h" + +class Sun +{ +public: + Sun( Config cfg ); + sunrise( double alt = -0.833 ); +}; diff --git a/SunRise.c b/SunRise.c new file mode 100644 index 0000000..80f35cb --- /dev/null +++ b/SunRise.c @@ -0,0 +1,256 @@ +#include +#include + +#define DegPerRad 57.29577951308232087680 +#define RadPerDeg 0.01745329251994329576 + +double Glon, Glat, SinGlat, CosGlat, TimeZone; + +double cosEPS = 0.91748; +double sinEPS = 0.39778; +double P2 = 6.283185307; + + +SunRise(int year, int month, int day, double LocalHour, double *UTRise, double *UTSet){ + + double UT, ym, y0, yp, SinH0; + double xe, ye, z1, z2, SinH(), hour24(); + int Rise, Set, nz; + + SinH0 = sin( -50.0/60.0 * RadPerDeg ); + + + UT = 1.0+TimeZone; + *UTRise = -999.0; + *UTSet = -999.0; + Rise = Set = 0; + ym = SinH(year, month, day, UT-1.0) - SinH0; + + while ( (UT <= 24.0+TimeZone) ) { + + y0 = SinH(year, month, day, UT) - SinH0; + yp = SinH(year, month, day, UT+1.0) - SinH0; + + Interp(ym, y0, yp, &xe, &ye, &z1, &z2, &nz); + + switch(nz){ + + case 0: + break; + case 1: + if (ym < 0.0){ + *UTRise = UT + z1; + Rise = 1; + } else { + *UTSet = UT + z1; + Set = 1; + } + break; + case 2: + if (ye < 0.0){ + *UTRise = UT + z2; + *UTSet = UT + z1; + } else { + *UTRise = UT + z1; + *UTSet = UT + z2; + } + Rise = 1; + Set = 1; + break; + } + ym = yp; + UT += 2.0; + + } + + if (Rise){ + *UTRise -= TimeZone; + *UTRise = hour24(*UTRise); + } else { + *UTRise = -999.0; + } + + if (Set){ + *UTSet -= TimeZone; + *UTSet = hour24(*UTSet); + } else { + *UTSet = -999.0; + } + +} + + +UTTohhmm(double UT, int *h, int *m){ + + + if (UT < 0.0) { + *h = -1.0; + *m = -1.0; + } else { + *h = (int)UT; + *m = (int)((UT-(double)(*h))*60.0+0.5); + } + +} + + + + + + +Interp(double ym, double y0, double yp, double *xe, double *ye, double *z1, double *z2, int *nz){ + + double a, b, c, d, dx; + + *nz = 0; + a = 0.5*(ym+yp)-y0; + b = 0.5*(yp-ym); + c = y0; + *xe = -b/(2.0*a); + *ye = (a*(*xe) + b) * (*xe) + c; + d = b*b - 4.0*a*c; + + if (d >= 0){ + dx = 0.5*sqrt(d)/fabs(a); + *z1 = *xe - dx; + *z2 = *xe+dx; + if (fabs(*z1) <= 1.0) *nz += 1; + if (fabs(*z2) <= 1.0) *nz += 1; + if (*z1 < -1.0) *z1 = *z2; + } + + return(0); + + +} + + + + +double SinH(int year, int month, int day, double UT){ + + double TU0, TU, TU2, TU3, LambdaMoon, BetaMoon, R, AGE, frac(), jd(); + double RA_Sun, DEC_Sun, T0, gmst, lmst, Tau, epsilon; + double M, DL, L, SL, X, Y, Z, RHO; + + + TU0 = (jd(year, month, day, 0.0) - 2451545.0)/36525.0; + + TU = (jd(year, month, day, UT+62.0/3600.0) - 2451545.0)/36525.0; + TU2 = TU*TU; + TU3 = TU2*TU; + + M = P2*frac(0.993133 + 99.997361*TU); + DL = 6893.0*sin(M) + 72.0*sin(2.0*M); + L = P2*frac(0.7859453 + M/P2 + (6191.2*TU+DL)/1296e3); + SL = sin(L); + X = cos(L); Y = cosEPS*SL; Z = sinEPS*SL; RHO = sqrt(1.0-Z*Z); + DEC_Sun = atan2(Z, RHO); + RA_Sun = (48.0/P2)*atan(Y/(X+RHO)); + if (RA_Sun < 0) RA_Sun += 24.0; + + RA_Sun = RA_Sun*15.0*RadPerDeg; + + /* + * Compute Greenwich Mean Sidereal Time (gmst) + */ + UT = 24.0*frac( UT/24.0 ); +/* + gmst = 6.697374558 + 1.0027379093*UT + (8640184.812866*TU0 +(0.093104-6.2e-6*TU)*TU2)/3600.0; +*/ + gmst = 6.697374558 + 1.0*UT + (8640184.812866+(0.093104-6.2e-6*TU)*TU)*TU/3600.0; + lmst = 24.0*frac( (gmst-Glon/15.0) / 24.0 ); + + Tau = 15.0*lmst*RadPerDeg - RA_Sun; + return( SinGlat*sin(DEC_Sun) + CosGlat*cos(DEC_Sun)*cos(Tau) ); + + +} + + +/* + * Compute the Julian Day number for the given date. + * Julian Date is the number of days since noon of Jan 1 4713 B.C. + */ +double jd(ny, nm, nd, UT) +int ny, nm, nd; +double UT; +{ + double A, B, C, D, JD, MJD, day; + + day = nd + UT/24.0; + + + if ((nm == 1) || (nm == 2)){ + ny = ny - 1; + nm = nm + 12; + } + + if (((double)ny+nm/12.0+day/365.25)>=(1582.0+10.0/12.0+15.0/365.25)){ + A = ((int)(ny / 100.0)); + B = 2.0 - A + (int)(A/4.0); + } + else{ + B = 0.0; + } + + if (ny < 0.0){ + C = (int)((365.25*(double)ny) - 0.75); + } + else{ + C = (int)(365.25*(double)ny); + } + + D = (int)(30.6001*(double)(nm+1)); + + + JD = B + C + D + day + 1720994.5; + return(JD); + +} + +double hour24(hour) +double hour; +{ + int n; + + if (hour < 0.0){ + n = (int)(hour/24.0) - 1; + return(hour-n*24.0); + } + else if (hour > 24.0){ + n = (int)(hour/24.0); + return(hour-n*24.0); + } + else{ + return(hour); + } +} + +double frac(double x){ + + x -= (int)x; + return( (x<0) ? x+1.0 : x ); + +} + +int main( int argc, char ** argv ) +{ + double rise; + double set; + int H, M; + Glat = -77.5912; + Glon = 43.0520; + SinGlat = sin( Glat ); + CosGlat = cos( Glat ); + TimeZone = -4; + + SunRise( 2008, 6, 8, -4, &rise, &set ); + H = (int)rise; rise = ( rise-H)*60; + M = (int)rise; + printf("rise: %d:%d\n", H, M ); + H = (int)set; set = ( set-H)*60; + M = (int)set; + printf("set: %d:%d\n", H, M ); +} + diff --git a/VantagePro.cpp b/VantagePro.cpp new file mode 100644 index 0000000..c876624 --- /dev/null +++ b/VantagePro.cpp @@ -0,0 +1,360 @@ +#include "VantagePro.h" +#include "utils.h" +#include "units.h" +#include "ccitt.h" +#include // htons + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "WeatherSource.h" + +VantagePro::VantagePro( Config &cfg ): + dbg("VantagePro"), + fd(-1) +{ + VantagePro::pathName = cfg.getString("vantage path").c_str(); + openPath(); +} + +void VantagePro::openPath() +{ + dbg.printf(CRIT, "(Re)opening %s\n", pathName ); + if ( fd != -1 ) + close( fd ); + + + fd = open( pathName, O_RDWR | O_NONBLOCK | O_NDELAY); + if ( fd == -1 ) + { + dbg.printf(NOTICE, "open(): %s\n", strerror(errno)); + return; + } + portConfig(); +} + + +signed long VantagePro::twosComplement( UINT16 val ) +{ + long rval = val; + + dbg.printf(CRIT, "----------\n"); + dbg.printf(CRIT, "val: 0x%04x\n", val); + if ( val & 1<<15 == 1<<15 ) + { + dbg.printf(CRIT,"negative!\n"); + rval = -1* ( 0xffff & ~val + 1); + } + dbg.printf(CRIT, "new val: 0x%04x\n", rval ); + return rval; +} + +void VantagePro::portConfig() +{ + struct termios port; + + tcgetattr (fd, &port); + + cfsetispeed (&port, B19200); + cfsetospeed (&port, B19200); + + /* ... set port to 8N1 + */ + port.c_cflag &= ~PARENB; + port.c_cflag &= ~CSTOPB; + port.c_cflag &= ~CSIZE; + port.c_cflag |= CS8; + port.c_cflag |= (CREAD | CLOCAL); + port.c_cflag |= CRTSCTS; /* turn on H/W flow control */ + + port.c_iflag &= ~(IXON | IXOFF | IXANY); /* turn off SW flow control */ + + port.c_iflag &= ~(INLCR | ICRNL); /* turn off other input magic */ + + port.c_oflag = 0; /* NO output magic wanted */ + + port.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + + tcsetattr (fd, TCSAFLUSH, &port); + + return; +} + +bool VantagePro::wakeup() +{ + int retryCount = 3; + char buf[2]; + + if ( fd == -1 ) + { + openPath(); + } + if ( fd == -1 ) + return false; + + while ( retryCount ) + { + tcflush( fd, TCIOFLUSH ); + write( fd, "\n", 1 ); // send wakeup + + // read back \n\r + if ( 2 == readTimeout( fd, &buf, 2, 1 ) ) + { + if ( buf[0] == LF && buf[1] == CR ) + return true; + } + sleep(1); // Give the console some time to fix itself + retryCount--; + } + openPath(); + sleep(2); // Give the console a little more time to fix itself + return false; +} + +bool VantagePro::setClock() +{ + struct tm tm; + struct timeval tv; + struct timezone tz; + tSetTime packet; + unsigned char ack; + + if ( !wakeup() ) + { + dbg.printf(CRIT, "%s (%d): Could not wake up console\n", __FUNCTION__, __LINE__); + return false; + } + + gettimeofday( &tv, &tz ); + localtime_r( &tv.tv_sec, &tm ); + packet.seconds = tm.tm_sec; + packet.minutes = tm.tm_min; + packet.hours = tm.tm_hour; + packet.day = tm.tm_mday; + packet.month = tm.tm_mon + 1; + packet.year = tm.tm_year; + crc = 0; + + crcAccum( packet.seconds ); + crcAccum( packet.minutes ); + crcAccum( packet.hours ); + crcAccum( packet.day ); + crcAccum( packet.month ); + crcAccum( packet.year ); + packet.CRC = htons( crc ); + write( fd, "SETTIME\n", 8 ); + readTimeout( fd, &ack, 1, 2 ); + + if ( ack != ACK ) + { + dbg.printf(CRIT,"%s(%d): ACK not received. Received 0x%02x \n", __FUNCTION__, __LINE__, ack); + return false; + } + + write( fd, (char *)&packet, sizeof(packet) ) ; + //write( fd, "\n", 1 ); + + readTimeout( fd, &ack, 1, 2 ); + + if ( ack != ACK ) + { + dbg.printf(CRIT,"%s(%d): ACK not received. Received 0x%02x \n", __FUNCTION__, __LINE__, ack); + return false; + } + + dbg.printf(NOTICE, "Time on weather station set\n"); + return true; +} + +bool VantagePro::read( ) +{ + if ( !wakeup() ) + { + dbg.printf(CRIT, "%s (%d): Could not wake up console\n", __FUNCTION__, __LINE__); + return false; + } + + unsigned char ack; + + write( fd, "LOOP 1\n", 7 ); // send loop command + readTimeout( fd, &ack, 1, 2 ); + + if ( ack != ACK ) + { + dbg.printf(CRIT,"ACK not received. Received 0x%02x \n", ack); + return false; + } + + int rval = readTimeout( fd, (unsigned char*)&loop, sizeof(loop), 2 ); + crc = 0; + unsigned char *ch = (unsigned char*)&loop; + for ( unsigned int i = 0 ; i < sizeof( loop ); i++ ) + { + crcAccum( *ch++ ); + } + if ( rval != sizeof(loop) ) + { + dbg.printf(CRIT,"Read %d bytes, expected %d\n", rval, sizeof(loop)); + return false; + } + + if ( crc != 0 ) + { + dbg.printf(CRIT,"CRC error\n"); + return false; + } + + + // This is a kludge because my weather station isn't doing something right, + // and I don't have a lot of time to look into why I am getting dash values + // sometimes + + if ( loop.barometer == 0x00 ) + { + dbg.printf(CRIT, "Invalid barometer reading.\n"); + return false; + } + + if ( loop.insideTemp == 0x7fff ) + { + dbg.printf(CRIT, "Invalid inside temperature reading.\n"); + return false; + } + + if ( loop.outsideTemp == 0x7fff ) + { + dbg.printf(CRIT, "Invalid outside temperature reading.\n"); + return false; + } + + if ( loop.insideHumidity == 0xff ) + { + dbg.printf(CRIT, "Invalid inside humidity reading.\n"); + return false; + } + + if ( loop.outsideHumidity == 0xff ) + { + dbg.printf(CRIT, "Invalid outside humidity reading.\n"); + return false; + } + + wd->setValue(WeatherData::SLP, loop.barometer / 1000.0); + wd->setValue(WeatherData::insideTemp, loop.insideTemp / 10.0); + + wd->setValue(WeatherData::insideHumidity, loop.insideHumidity ); + + wd->setValue(WeatherData::outsideTemp, loop.outsideTemp / 10.0); + wd->setValue(WeatherData::outsideHumidity, loop.outsideHumidity ); + + wd->setValue(WeatherData::windSpeed, loop.windSpeed ); + wd->setValue(WeatherData::windDirection, loop.windDirection ); + + wd->setValue(WeatherData::dailyRain, loop.dayRain ); + + wd->setValue(WeatherData::dailyET, loop.dayET ); + + wd->setValue(WeatherData::solarRadiation, loop.solarRadiation ); + wd->setValue(WeatherData::UV, loop.UV/10.0 ); + + if ( wd->now->isNewMinute() ) + { + if ( !updateBarometer() ) + { + return false; + } + } + + if ( wd->now->isNewHour() && wd->now->getHour() == 12 ) + { + if ( !setClock() ) + { + return false; + } + } + + printRecord(); + return true; +} + +bool VantagePro::updateBarometer() +{ + dbg.printf(INFO, "Forcing barometer refresh\n"); + + if ( !wakeup() ) + { + dbg.printf(CRIT, "%s (%d): Could not wake up console\n", __FUNCTION__, __LINE__); + return false; + } + write( fd, "BARREAD\n", 8); + + char buf[6]; + readTimeout( fd, &buf, sizeof(buf), 3 ); + if ( buf[2] != 79 && buf[3] != 75 ) + { + dbg.printf(CRIT, "Could not force barometer update\n"); + return false; + } + + dbg.printf(DEBUG, "Barometer successfully refreshed\n"); + return true; +} + +void VantagePro::crcAccum( unsigned char data ) +{ + crc = crc_table [ ( crc >> 8 ) ^ data ] ^ ( crc << 8 ); +} + +void VantagePro::printRecord() +{ + dbg.printf(DEBUG,"L = %c\n", loop.L ); + dbg.printf(DEBUG,"O = %c\n", loop.O1 ); + dbg.printf(DEBUG,"O = %c\n", loop.O2 ); + dbg.printf(DEBUG,"barTrend = %d\n", loop.barTrend ); + dbg.printf(DEBUG,"packetType = %d\n", loop.packetType ); + dbg.printf(DEBUG,"nextRecord = %d\n", loop.nextRecord ); + dbg.printf(DEBUG,"barometer = %.2f\n", loop.barometer / 1000.0 ); + dbg.printf(DEBUG,"inside temp = %.1f\n", loop.insideTemp / 10.0 ); + dbg.printf(DEBUG,"inside humidity = %d%%\n", loop.insideHumidity ); + dbg.printf(DEBUG,"outside temp = %.1f\n", loop.outsideTemp / 10.0 ); + dbg.printf(DEBUG,"outside humidity = %d%%\n", loop.outsideHumidity ); + dbg.printf(DEBUG,"wind speed = %d\n", loop.windSpeed ); + dbg.printf(DEBUG,"10 min ave wind = %d\n", loop.tenMinAvgWind ); + dbg.printf(DEBUG,"wind direction = %d degrees\n", loop.windDirection ); + dbg.printf(DEBUG,"rain rate = %.2f in/hr\n", loop.rainRate/100.0 ); + dbg.printf(DEBUG,"storm rain = %.2f in\n", loop.stormRain/100.0 ); + dbg.printf(DEBUG,"storm start = xx/xx/xx\n" /* DO SOME MATH HERE */ ); + dbg.printf(DEBUG,"day rain = %.2f\n", loop.dayRain / 100.0 ); + dbg.printf(DEBUG,"month rain = %.2f\n", loop.monthRain / 100.0 ); + dbg.printf(DEBUG,"year rain = %.2f\n", loop.yearRain / 100.0 ); + dbg.printf(DEBUG,"day ET = %.2f\n", loop.dayET / 100.0 ); + dbg.printf(DEBUG,"month ET = %.2f\n", loop.monthET / 100.0 ); + dbg.printf(DEBUG,"year ET = %.2f\n", loop.yearET / 100.0 ); + dbg.printf(DEBUG,"UV = %.1f\n", loop.UV/10.0 ); + dbg.printf(DEBUG,"solar radiation = %d W/m^2\n", loop.solarRadiation ); + dbg.printf(DEBUG,"console voltage = %.2f V\n", ((loop.consoleBatteryVoltage * 300 )/512.0)/100.0); + dbg.printf(DEBUG,"forecast icon = %d\n", loop.forecastIcon); + dbg.printf(DEBUG,"forecast rule = %d\n", loop.forecastRuleNumber ); + dbg.printf(DEBUG,"sunrise = %d\n", loop.sunrise ); + dbg.printf(DEBUG,"sunset = %d\n", loop.sunset ); + + // Extra Temp + // Soil Temp + // Leaf Temp + // Extra Humidities + // soil moisture + // leaf wetness + // inside alarms + // rain alarms + // outside alarms + // extra alarms + // soil and leaf alarms + // transmitter battery status + // +} diff --git a/VantagePro.h b/VantagePro.h new file mode 100644 index 0000000..b62e095 --- /dev/null +++ b/VantagePro.h @@ -0,0 +1,142 @@ +#ifndef __VANTAGE_PRO_H__ +#define __VANTAGE_PRO_H__ + +#include "types.h" +#include "Debug.h" +#include "WeatherSource.h" +//#include "Instantaneous.h" + +#define CR 0x0D +#define LF 0x0A +#define ACK 0x06 +#define NAK 0x21 +#define CANCEL 0x18 + +// Forward declarations + +struct Instantaneous; + +typedef struct tagForecastIcon +{ + UINT8 rain:1; + UINT8 cloud:1; + UINT8 partlyCouldy:1; + UINT8 sun:1; + UINT8 snow:1; + UINT8 reserved:3; +} tForecastIcon; + +typedef struct tagSETTIME +{ + UINT8 seconds; + UINT8 minutes; + UINT8 hours; + UINT8 day; + UINT8 month; + UINT8 year; + UINT16 CRC; +} __attribute ((packed)) tSetTime; + +typedef struct tagLOOP +{ + UINT8 L; + UINT8 O1; + UINT8 O2; + INT8 barTrend; + UINT8 packetType; + UINT16 nextRecord __attribute__ ((packed)); + UINT16 barometer __attribute__ ((packed)); + INT16 insideTemp __attribute__ ((packed)); + UINT8 insideHumidity; + INT16 outsideTemp __attribute__ ((packed)); + UINT8 windSpeed; + UINT8 tenMinAvgWind; + UINT16 windDirection __attribute__ ((packed)); + + // Offset 18 + UINT8 extraTemp[7]; + UINT8 soilTemp[4]; + UINT8 leafTemp[4]; + + // Offset 33 + UINT8 outsideHumidity; + UINT8 extraHumidity[7]; + UINT16 rainRate __attribute__ ((packed)); + + // Offset 43 + UINT8 UV; + UINT16 solarRadiation __attribute__ ((packed)); + UINT16 stormRain __attribute__ ((packed)); + UINT16 stormStartDate __attribute__ ((packed)); + UINT16 dayRain __attribute__ ((packed)); + UINT16 monthRain __attribute__ ((packed)); + UINT16 yearRain __attribute__ ((packed)); + UINT16 dayET __attribute__ ((packed)); + UINT16 monthET __attribute__ ((packed)); + UINT16 yearET __attribute__ ((packed)); + + // Offset 62 + UINT8 soilMoisture[4]; + UINT8 leafWetness[4]; + UINT8 insideAlarms; + UINT8 rainAlarms; + UINT16 outsideAlarms __attribute__ ((packed)); + + // Offset 74 + UINT8 extraTempHumidAlarms[8]; + UINT8 soilLeafAlarms[4]; + + // Offset 86 + UINT8 transmitterBattery; + UINT16 consoleBatteryVoltage __attribute__ ((packed)); + UINT8 forecastIcon; + UINT8 forecastRuleNumber; + UINT16 sunrise __attribute__ ((packed)); + UINT16 sunset __attribute__ ((packed)); + UINT8 linefeed; + UINT8 carriageReturn; + UINT16 CRC __attribute__ ((packed)); +} __attribute ((packed)) LOOP; + + +class VantagePro:public WeatherSource +{ +public: + VantagePro( Config &cfg ); + + bool read( ); + +private: // Helper Functions + + void portConfig(); + + bool wakeup(); + + void printRecord(); + + void openPath(); + + void crcAccum( unsigned char data ); + + bool updateBarometer(); + + bool setClock(); + + signed long twosComplement( UINT16 val ); + + Debug dbg; + +private: // State Variables + + int fd; + const char *pathName; + + LOOP loop; + + UINT16 crc; + int lastMinute; + +private: +}; + +#endif // __VANTAGE_PRO_H__ diff --git a/WeatherData.cpp b/WeatherData.cpp new file mode 100644 index 0000000..dc2ff30 --- /dev/null +++ b/WeatherData.cpp @@ -0,0 +1,887 @@ +#include "WeatherData.h" +#include "units.h" +#include "math.h" +#include "WeatherSink.h" + +/* Solar Constant - The solar constant is the amount of energy received at the + * top of the Earth's atmosphere on a surface oriented perpendicular to the + * Sun.s rays (at the mean distance of the Earth from the Sun). The generally + * accepted solar constant of 1368 W/m2 is a satellite measured yearly + * average. */ +/* http://edmall.gsfc.nasa.gov/inv99Project.Site/Pages/science-briefs/ed-stickler/ed-irradiance.html + */ +#define MAX_SOLAR (1368) + +using namespace std; + + +WeatherData::WeatherData( Config _cfg ): + now( new Now ), + cfg( _cfg ), + db(), + wind( now, db ), + rain( now, db ), + dbg("Weather Data"), + baro(cfg.getDouble("elevation")), + almanacYesterday( cfg, now->unixtime() - 24*60*60 ), + almanacToday( cfg, now->unixtime() ), + almanacTomorrow( cfg, now->unixtime() + 24*60*60 ), + dayTime( false ) +{ + pthread_rwlock_init( &rwLock, 0 ); + for ( int p = insideTemp; p != END; p++ ) + { + dataAvailable[ (PROPERTY)p ] = false; + setValues[ (PROPERTY)p ] = NO_VALUE; + } + inst[insideTemp].setHistorySize( 5 * ONE_MINUTE, "insideTemp" ); + inst[insideHumidity].setHistorySize( 1 * ONE_MINUTE, "insideHumidity" ); + inst[outsideTemp].setHistorySize( 5 * ONE_MINUTE, "outsideTemp" ); + inst[outsideHumidity].setHistorySize( 1 * ONE_MINUTE, "outsideHumidity" ); + refreshSolarMax(); + computeTrends(); + + time_t t; + t = almanacYesterday.sunrise; + dbg.printf(NOTICE, "Sunrise yesterday: %s", ctime(&t)); + t = almanacYesterday.sunset; + dbg.printf(NOTICE, "Sunset yesterday: %s", ctime(&t)); + + t = almanacToday.sunrise; + dbg.printf(NOTICE, "Sunrise today: %s", ctime(&t)); + t = almanacToday.sunset; + dbg.printf(NOTICE, "Sunset today: %s", ctime(&t)); + + t = almanacTomorrow.sunrise; + dbg.printf(NOTICE, "Sunrise tomorrow: %s", ctime(&t)); + t = almanacTomorrow.sunset; + dbg.printf(NOTICE, "Sunset Tomorrow: %s", ctime(&t)); + + if ( almanacToday.lengthOfDay < almanacYesterday.lengthOfDay ) + { + dbg.printf(NOTICE, "Today will be %d seconds shorter than yesterday\n", + almanacYesterday.lengthOfDay - almanacToday.lengthOfDay ); + } + else if ( almanacToday.lengthOfDay > almanacYesterday.lengthOfDay ) + { + dbg.printf(NOTICE, "Today will be %d seconds longer than yesterday\n", + almanacToday.lengthOfDay - almanacYesterday.lengthOfDay ); + } + else + { + dbg.printf(NOTICE, "Today will be same length as yesterday\n"); + } + + if ( almanacTomorrow.lengthOfDay < almanacToday.lengthOfDay ) + { + dbg.printf(NOTICE, "Tomorrow will be %d seconds shorter than today\n", + almanacToday.lengthOfDay - almanacTomorrow.lengthOfDay ); + } + else if ( almanacTomorrow.lengthOfDay > almanacToday.lengthOfDay ) + { + dbg.printf(NOTICE, "Tomorrow will be %d seconds longer than today\n", + almanacTomorrow.lengthOfDay - almanacToday.lengthOfDay ); + } + else + { + dbg.printf(NOTICE, "Tomorrow will be same length as today\n"); + } + + if ( now->unixtime() > almanacToday.sunrise && + now->unixtime() < almanacToday.sunset ) + { + dbg.printf(NOTICE, "Initializing in daytime mode\n"); + dayTime = true; + } + else + { + dbg.printf(NOTICE, "Initializing in nighttime mode\n"); + dayTime = false; + } +} + +WeatherData::~WeatherData() +{ +} + + +double WeatherData::getCurrent( PROPERTY property ) +{ + if ( !dataAvailable[ property ] ) + { + dbg.printf(INFO, "No data available for %s\n", + propertyToString(property)); + return NO_VALUE; + } + return inst[property]; +} + +double WeatherData::getFromObs( PROPERTY property, time_t howLongAgo, time_t + *actualTime ) +{ + // Bracket it between +- 30 seconds. This should give us a good time. + dbResult result = db.query("select time, %s from dailyObs where time < %d and time > %d order by time desc limit 1;", + propertyToString(property), + (int)howLongAgo + 30, + (int)howLongAgo - ONE_MINUTE*5 ); + + if ( result.size() == 0 ) + { + return NO_VALUE; + } + else + { + if ( actualTime != NULL ) + *actualTime = result[0].asInteger("time"); + return result[0].asDouble(propertyToString(property)); + } +} + +double WeatherData::getFromHourly( PROPERTY property, time_t howLongAgo, + time_t *actualTime ) +{ + dbResult result = db.query("select time, %s from hourlySummary where time < %d and time > %d order by time desc limit 1;", + propertyToString(property), + (int)howLongAgo + ONE_HOUR/2, + (int)howLongAgo - ONE_HOUR*5 ); + + if ( result.size() == 0 ) + { + return NO_VALUE; + } + else + { + if ( actualTime != NULL ) + *actualTime = result[0].asInteger("time"); + return result[0].asDouble(propertyToString(property)); + } +} + +double WeatherData::getFromDaily( PROPERTY property, time_t howLongAgo, + time_t *actualTime ) +{ + dbResult result = db.query("select time, %s from dailySummary where time < %d and time > %d and summaryType == 0 order by time desc limit 1;", + propertyToString(property), + (int)howLongAgo + ONE_DAY/2, + (int)howLongAgo - ONE_DAY/2 ); + + if ( result.size() == 0 ) + { + return NO_VALUE; + } + else + { + if ( actualTime != NULL ) + *actualTime = result[0].asInteger("time"); + return result[0].asDouble(propertyToString(property)); + } +} + +void WeatherData::copyData( void ) +{ + for ( int p = insideTemp; p != END; p++ ) + { + PROPERTY property = (PROPERTY)p; + if ( setValues[ property ] != NO_VALUE ) + { + inst[ property ] = setValues[ property ]; + dataAvailable[ property ] = true; + } + } + +} + +bool WeatherData::setValue( PROPERTY property, double value ) +{ + bool rval = false; + switch ( property ) + { + case windGust: + case windGustDir: + case solarPercent: + case rain24Hour: + case rainRate: + dbg.printf(CRIT, "windGust/solarPecent are not settable!\n"); + rval = false; + break; + default: + setValues[property] = value; + rval = true; + break; + } + return rval; +} + +double WeatherData::getTrend( PROPERTY property ) +{ + pthread_rwlock_rdlock( &rwLock ); + double f = trend[property]; + pthread_rwlock_unlock( &rwLock ); + return f; +} + +double WeatherData::getValue( PROPERTY property, time_t howLongAgo, time_t + *actualTime ) +{ + double rval = 0; + pthread_rwlock_rdlock( &rwLock ); + if ( howLongAgo == 0 ) + rval = getCurrent( property ); + else if ( howLongAgo < ONE_DAY*7 ) + rval = getFromObs( property, now->unixtime() - howLongAgo, actualTime ); + else if ( howLongAgo < ONE_DAY*90 ) + rval = getFromHourly( property, now->unixtime() - howLongAgo, actualTime); + else + rval = getFromDaily( property, now->unixtime() - howLongAgo, actualTime); + pthread_rwlock_unlock( &rwLock ); + + return rval; +} + +double WeatherData::getSolarMax() +{ + return currentSolarMax; +} + +double WeatherData::getApparentTemp() +{ + // Humidex is valid for temps > 68 + if ( (double)inst[humidex] != NO_VALUE ) + { + return inst[humidex]; + } + // Windchill is valid for temps < 50 and > -50 + else if ( (double)inst[windChill] != NO_VALUE ) + { + return inst[windChill]; + } + // Otherwise, there is no effect on the human body + else + { + return inst[outsideTemp]; + } +} + +double WeatherData::getCloudHeight() +{ + return trunc((( (float)inst[outsideTemp] - (float)inst[dewPoint])/4.5)*1000); +} + + +double WeatherData::getStationPressure() +{ + int meanTemp; + float oldTemp = getFromObs( outsideTemp, now->unixtime()-12*ONE_HOUR, NULL ); + if ( oldTemp == NO_VALUE ) + { + dbg.printf(NOTICE, "No temperature data for 12 hours ago. Using current temp only.\n"); + meanTemp = (int)inst[outsideTemp]; + } + else + { + meanTemp = ((int)oldTemp + (int)inst[outsideTemp]) / 2; + } + + return baro.SLPtoStationPressure( inst[SLP], inst[outsideTemp], + (float)meanTemp, (int)inst[outsideHumidity] ) + cfg.getDouble("barometer_offset"); + +} + +double WeatherData::getHeatIndex() +{ + double T = inst[outsideTemp]; + double R = inst[outsideHumidity]; + + if ( T < 68 || R < 40 ) + { + return NO_VALUE; + } + + double T2 = pow(T,2); + double R2 = pow(R,2); + return -42.379 + +(2.04901523*T) + +(10.14333127*R) + -(0.22475541*T*R) + -(0.00683783*T2) + -(0.05481717*R2) + +(0.00122874*T2*R) + +(0.00085282*T*R2) + -(0.000001998*T2*R2); + + +} + +double WeatherData::getHumidex() +{ + if ( (double)inst[outsideTemp] < 68 || (double)inst[outsideHumidity] < 40 ) + { + return NO_VALUE; + } + double temp = F2C(inst[outsideTemp]); + double dp = F2K( inst[dewPoint] ); + double e = 6.11 * exp(5417.7530 * ((1/273.16)-(1/dp))); + double h = (0.5555)*(e - 10.0 ); + return C2F( temp + h ); + +} + +double WeatherData::getWindChill() +{ + double v = inst[averageWindSpeed]; + double T = inst[outsideTemp]; + if ( ( v < 3 ) || T > 50 || T < -50 ) + return NO_VALUE; + + return ( 35.74 + 0.6215*T - 35.75*pow(v, 0.16) + 0.4275*T*pow(v, 0.16 )); +} + +double WeatherData::getDewPoint() +{ + double temp = F2C(inst[outsideTemp]); + double humidity = (double)inst[outsideHumidity]/100.0; + + double a = 17.27; + double b = 237.7; + double theta = ((a*temp)/(b+temp))+log(humidity); + + return C2F((( b * theta )/(a - theta ))); + +} + +bool WeatherData::newData() +{ + pthread_rwlock_wrlock( &rwLock ); + copyData(); + + if ( dataAvailable[dailyRain] ) + { + rain.update( (int)(inst[dailyRain]) ); + inst[rainRate] = rain.getRate(); + inst[rain24Hour] = rain.getSum( 24 * ONE_HOUR ); + inst[instantRain] = rain.getInstantRainrate(); + dataAvailable[rainRate] = true; + dataAvailable[rain24Hour] = true; + dataAvailable[instantRain] = true; + } + + if ( dataAvailable[ windSpeed ] && dataAvailable[ windDirection ] ) + { + wind.update( inst[windSpeed], inst[windDirection] ); + inst[averageWindDirection] = wind.getAverageDirection( TWO_MINUTES ); + inst[averageWindSpeed] = wind.getAverageSpeed( TWO_MINUTES ); + inst[windGust] = wind.getMaxSpeed( ONE_MINUTE * 10 ); + inst[windGustDir] = wind.getDirectionAtMaxSpeed( ONE_MINUTE * 10 ); + + dataAvailable[averageWindDirection] = true; + dataAvailable[averageWindSpeed] = true; + dataAvailable[windGust] = true; + dataAvailable[windGustDir] = true; + } + + // We were in daytime mode, but time has transitions to night (We are past + // sunset). + if ( dayTime && now->unixtime() > almanacToday.sunset ) + { + dbg.printf(NOTICE, "Transitioning to nighttime mode\n"); + dayTime = false; + updateSummary( DAILY, DAYTIMESUMMARY ); + + } + // We were in night mode, but are now in daytime ( sunrise HAS happened, + // but sunset hasn't) + else if ( !dayTime && + now->unixtime() > almanacToday.sunrise && + now->unixtime() < almanacToday.sunset ) + { + dbg.printf(NOTICE, "Transitioning to daytime mode\n"); + dayTime = true; + updateSummary( DAILY, NIGHTTIMESUMMARY ); + } + updateDB(); + pthread_rwlock_unlock( &rwLock ); + + + if ( now->isNewDay() ) + { + dbg.printf(EMERG, "refreshing almanacs\n"); + refreshAlmanacs(); + } + /*for ( std::vector< WeatherSink *>::iterator iter = sinks.begin(); + iter != sinks.end(); ++iter ) + { + (*iter)->newData(); + }*/ + return true; +} + +double WeatherData::computeTrend( PROPERTY property, int duration /* hours */) +{ + + const char * key = propertyToString( property ); + int age = (int)(now->unixtime() - duration*ONE_HOUR ); + + dbResult result = db.query( + " select ( " + " select sum( %s *(" + " select time/3600.0 - (" + " select avg(time/3600.0) from dailyObs where time > %d) )) " + " from dailyObs where time > %d ) " + " / " + " ( select sum((time/3600.0 - ( " + " select avg(time/3600.0) from dailyObs where time > %d)) * " + " (time/3600.0 - ( " + " select avg(time/3600.0) from dailyObs where time > %d))) " + " from dailyObs where time > %d) as M;", + key, age, age, age, age, age ); + + if ( result.size() == 0 ) + return 0; + else + return result[0].asDouble("M"); +} + +void WeatherData::doMaintenance() +{ + static bool firstMaintenanceRun = true; + if ( !now->isNewMinute( ) && !firstMaintenanceRun ) + { + return; + } + + firstMaintenanceRun = false; + + if ( dataAvailable[SLP] ) + { + inst[stationPressure] = getStationPressure(); + inst[altimeter] = baro.StationPressureToAltimeter( inst[stationPressure] ); + dataAvailable[ stationPressure ] = true; + dataAvailable[ altimeter ] = true; + + dbg.printf(INFO, " SLP: %f (%.1f)\n", + (float)inst[SLP], inHg2hPa( inst[SLP] ) ); + dbg.printf(INFO, "station pressure: %f (%.1f)\n", + (float)inst[stationPressure], inHg2hPa( inst[stationPressure] ) ); + dbg.printf(INFO, " altimeter: %f (%.1f)\n", + (float)inst[altimeter], inHg2hPa( inst[ altimeter] ) ); + } + refreshSolarMax(); + computeTrends(); +} + +void WeatherData::updateDB() +{ + double sradMax = getSolarMax(); + int sradPercent = 0; + if ( sradMax > 0 ) + sradPercent = (int)round( ( (double)inst[solarRadiation] / sradMax) * 100 ); + if ( sradPercent > 100 ) + sradPercent = 100; + + if ( dataAvailable[ solarRadiation ] ) + { + dataAvailable[ solarPercent ] = true; + inst[solarPercent] = sradPercent; + } + + if ( dataAvailable[ outsideTemp ] && dataAvailable[ outsideHumidity ] ) + { + inst[dewPoint] = getDewPoint(); + inst[humidex] = getHumidex(); + inst[heatIndex] = getHeatIndex(); + + // cloud height depends on dew point, so it should come after that + inst[cloudHeight] = getCloudHeight(); + + dataAvailable[heatIndex] = true; + dataAvailable[cloudHeight] = true; + dataAvailable[humidex] = true; + dataAvailable[dewPoint] = true; + } + + if ( dataAvailable[ outsideTemp ] && dataAvailable[ windSpeed ] ) + { + inst[windChill] = getWindChill(); + dataAvailable[windChill] = true; + } + + if ( dataAvailable[ humidex ] && dataAvailable[ windChill ] ) + { + // Apparent Temp depends on windchill and heat index, so it should come + // after those + inst[apparentTemp] = getApparentTemp(); + dataAvailable[apparentTemp] = true; + } + + doMaintenance(); + + if ( !now->isNewMinute( )) + { + return; + } + + + + dbg.printf(DEBUG, "Logging minute summary\n"); + db.query("delete from dailyObs where time < %d;", (int)(now->unixtime() - ONE_DAY*7 )); + db.query("insert into dailyObs ( time, insideTemp, insideHumidity, outsideTemp, outsideHumidity, windSpeed, windDirection, windGust, rainRate, dailyRain, dailyET, UV, solarRadiation, solarRadiationPercent, barometricTrend, rawBarometer, SLP, altimeter, dewPoint, heatIndex, humidex, windChill, apparentTemp, cloudHeight, windGustDir, rain24Hour, instantRain ) VALUES ( " + "%d, " // unixTime + "%.02f," // insideTemp + "%.0f," // insideHumidity + "%.02f," // outsideTemp + "%.0f," // outsideHumidity + "%.02f," // averageWindSpeed + "%.0f," // averageWindDirection + "%.02f," // windGust + "%.02f," // rainRate + "%.02f," // dailyRain + "%.02f," // dailyET + "%.02f," // UV + "%.0f," // solarRadiation + "%.0f," // solarPercent + "%.04f," // barometer trend + "%.02f," // barometer + "%.02f," // SLP + "%.02f," // altimeter + "%.02f," // dewPoint + "%.02f," // heatIndex + "%.02f," // humidex + "%.02f," // windChill + "%.02f," // apparentTemp + "%.0f," // cloudHeight + "%.0f," // windGustDir + "%.02f," // rain24Hour + "%.02f);", // instantRain + (int)now->unixtime(), + (float)inst[insideTemp], + (float)inst[insideHumidity], + (float)inst[outsideTemp], + (float)inst[outsideHumidity], + (float)inst[averageWindSpeed], + (float)(float)inst[averageWindDirection], + (float)inst[windGust], + (float)inst[rainRate], + (float)inst[dailyRain], + (float)inst[dailyET], + (float)inst[UV], + (float)inst[solarRadiation], + (float)inst[solarPercent], + trend[altimeter], + (float)inst[stationPressure], + (float)inst[SLP], + (float)inst[altimeter], + (float)inst[dewPoint], + (float)inst[heatIndex], + (float)inst[humidex], + (float)inst[windChill], + (float)inst[apparentTemp], + (float)inst[cloudHeight], + (float)inst[windGustDir], + (float)inst[rain24Hour], + (float)inst[instantRain] + ); + + if ( now->getMinute() != 59 ) + { + return; + } + dbg.printf(DEBUG, "Logging hourly summary\n"); + + db.query("delete from hourlySummary where time < %d;", (int)(now->unixtime() - ONE_DAY*90 )); + updateSummary( HOURLY ); + + if ( now->getHour() != 23 ) + { + return; + } + dbg.printf(DEBUG, "Logging daily summary\n"); + + updateSummary( DAILY ); + db.query("VACUUM;"); +} + +void WeatherData::refreshAlmanacs() +{ + dbg.printf(NOTICE, "Refreshing almanacs\n"); + almanacYesterday.setNewTime(now->unixtime() - 12*60*60 ); + almanacToday.setNewTime(now->unixtime()); + almanacTomorrow.setNewTime(now->unixtime() + 36*60*60); + + time_t t; + t = almanacYesterday.sunrise; + dbg.printf(NOTICE, "Sunrise yesterday: %s", ctime(&t)); + t = almanacYesterday.sunset; + dbg.printf(NOTICE, "Sunset yesterday: %s", ctime(&t)); + + t = almanacToday.sunrise; + dbg.printf(NOTICE, "Sunrise today: %s", ctime(&t)); + t = almanacToday.sunset; + dbg.printf(NOTICE, "Sunset today: %s", ctime(&t)); + + t = almanacTomorrow.sunrise; + dbg.printf(NOTICE, "Sunrise tomorrow: %s", ctime(&t)); + t = almanacTomorrow.sunset; + dbg.printf(NOTICE, "Sunset Tomorrow: %s", ctime(&t)); + +} + +void WeatherData::updateSummary( DBTable table, SUMMARYTYPE type ) +{ + const char *table_s; + int currentTime = now->unixtime(); + int startTime; + const char *keys[] = {"insideTemp", + "insideHumidity", + "outsideTemp", + "outsideHumidity", + "windSpeed", + "windGust", + "UV", + "solarRadiation", + "solarRadiationPercent", + "SLP", + "rainRate", + "altimeter", + "rawBarometer", + "dewPoint", + "humidex", + "heatIndex", + "windChill", + "apparentTemp", + "cloudHeight", + "rain24Hour"}; + + + switch( table ) + { + case HOURLY: + table_s = "hourlySummary"; + startTime = currentTime - ONE_HOUR; + if ( type != DAILYSUMMARY ) + { + dbg.printf(ERR, "Cannot set type for hourlySummary!\n"); + type = DAILYSUMMARY; + } + db.query("insert into %s ( time ) VALUES ( %d );", + table_s, + (int)now->unixtime()); + break; + case DAILY: + table_s = "dailySummary"; + switch ( type ) + { + case DAILYSUMMARY: + startTime = currentTime - ONE_DAY; + break; + case DAYTIMESUMMARY: + startTime = almanacToday.sunrise; + break; + case NIGHTTIMESUMMARY: + startTime = almanacYesterday.sunset; + break; + } + db.query("insert into %s ( time, summaryType ) VALUES ( %d, %d );", + table_s, + (int)now->unixtime(), + type); + break; + } + + db.query("BEGIN TRANSACTION;"); + + // TODO: We need to check if this fails, and do something if it does + + + for ( size_t i = 0; i < sizeof( keys )/sizeof( char * ); i++ ) + { + db.query("update %s set %s%s = ( select %s( %s ) from dailyObs where time > %d ), %s%sTime = ( select time from dailyObs where %s = ( select %s( %s ) from dailyObs where time > %d )) where time = %d;", + table_s, + keys[i], + "min", + "min", + keys[i], + startTime, + keys[i], + "min", + keys[i], + "min", + keys[i], + startTime, + currentTime ); + db.query("update %s set %s%s = ( select %s( %s ) from dailyObs where time > %d ), %s%sTime = ( select time from dailyObs where %s = ( select %s( %s ) from dailyObs where time > %d )) where time = %d;", + table_s, + keys[i], + "max", + "max", + keys[i], + startTime, + keys[i], + "max", + keys[i], + "max", + keys[i], + startTime, + currentTime ); + + db.query("update %s set %s%s = ( select %s( %s ) from dailyObs where time > %d ) where time = %d;", + table_s, + keys[i], + "Avg", + "avg", + keys[i], + startTime, + currentTime ); + } + + // Update instantRain + db.query("update %s set instantRainMax = ( select max(instantRain) from dailyObs where time > %d ), instantRainMaxTime = ( select time from dailyObs where instantRain = ( select max( instantRain ) from dailyObs where time > %d )) where time = %d\n", table_s, currentTime - 60*5, currentTime - 60*5, currentTime); + + // Update dailyrain/ET + db.query("update %s set dailyRain = ( select max(dailyRain) from dailyObs where time > %d ) where time = %d\n", table_s, currentTime - 60*5, currentTime); + db.query("update %s set dailyET = ( select max(dailyET) from dailyObs where time > %d ) where time = %d\n", table_s, currentTime - 60*5, currentTime); + + db.query("COMMIT;"); + + // Update wind direction + double windDirSin = 0; + double windDirCos = 0; + dbResult result = + db.query("select windDirection from dailyObs where time > %d;", + startTime ); + + for ( dbResult::iterator iter = result.begin(); iter!=result.end();++iter ) + { + windDirSin = sin( deg2rad( iter->second.asDouble("windDirection"))); + windDirCos = cos( deg2rad( iter->second.asDouble("windDirection"))); + } + int avgWindDir = (int)rad2deg( atan2(windDirSin, windDirCos )); + if ( avgWindDir < 0 ) + avgWindDir += 360; + db.query("update %s set windDirectionAvg=%d where time = %d;", + table_s, avgWindDir, currentTime ); + + // Update wind gust direction + windDirSin = 0; + windDirCos = 0; + result = + db.query("select windGustDir from dailyObs where time > %d;", + startTime ); + + for ( dbResult::iterator iter = result.begin(); iter!=result.end();++iter ) + { + windDirSin = sin( deg2rad( iter->second.asDouble("windGustDir"))); + windDirCos = cos( deg2rad( iter->second.asDouble("windGustDir"))); + } + avgWindDir = (int)rad2deg( atan2(windDirSin, windDirCos )); + if ( avgWindDir < 0 ) + avgWindDir += 360; + db.query("update %s set windGustDirAvg=%d where time = %d;", + table_s, avgWindDir, currentTime ); +} + + +double WeatherData::refreshSolarMax() +{ + static double timezone = cfg.getDouble("timezone"); + static double longitude = cfg.getDouble("longitude"); + static double latitude = cfg.getDouble("latitude"); + + float LSTM = 15 * timezone; + float B = 360.0/365.0 * ( now->getDayOfYear() - 81 ); + float EoT = 9.8 * sin ( deg2rad(2* B)) - 7.53 * cos( deg2rad(B)) - 1.5 * sin( deg2rad( B )); + float TC = ( -4 * ( LSTM - longitude ) + EoT ) / 60.0; + float LT = now->getDecimalTime() - now->isDST(); + float LST = LT + TC; + + float solDec = 23.45 * sin( deg2rad( 360.0/365.0 * (284 + now->getDayOfYear()))); + float hourAngle = 15 * ( LST - 12 ); + float l = sin( deg2rad( latitude ))*sin(deg2rad(solDec)); + float r = cos( deg2rad( latitude ))*cos(deg2rad(solDec))*cos(deg2rad(hourAngle)); + float Z = acos( l + r ); + + currentSolarMax = MAX_SOLAR * cos(( Z )); + return currentSolarMax; +} + +void WeatherData::computeTrends() +{ + trend[SLP] = computeTrend(SLP, 3 ); + trend[stationPressure] = computeTrend(stationPressure, 3 ); + trend[altimeter] = computeTrend(altimeter, 3 ); + trend[outsideTemp] = computeTrend(outsideTemp, 1 ); + trend[outsideHumidity] = computeTrend(outsideHumidity, 1 ); + trend[dewPoint] = computeTrend(dewPoint, 1 ); +} + +const char *WeatherData::propertyToString( PROPERTY property ) +{ + switch ( property ) + { + case insideTemp: + return "insideTemp"; + case insideHumidity: + return "insideHumidity"; + case outsideTemp: + return "outsideTemp"; + case outsideHumidity: + return "outsideHumidity"; + case windSpeed: + return "windSpeed"; + case windDirection: + return "windDirection"; + case averageWindDirection: + return NULL; + case averageWindSpeed: + return NULL; + case windGust: + return "windGust"; + case windGustDir: + return "windGustDir"; + case dailyRain: + return "dailyRain"; + case rainRate: + return "rainRate"; + case dailyET: + return "dailyET"; + case UV: + return "UV"; + case solarRadiation: + return "solarRadiation"; + case solarPercent: + return "solarRadiationPercent"; + case stationPressure: + return "rawBarometer"; + case SLP: + return "SLP"; + case altimeter: + return "altimeter"; + case dewPoint: + return "dewPoint"; + case heatIndex: + return "heatIndex"; + case humidex: + return "humidex"; + case windChill: + return "windChill"; + case apparentTemp: + return "apparentTemp"; + case cloudHeight: + return "cloudHeight"; + case rain24Hour: + return "rain24Hour"; + case instantRain: + return "instantRain"; + case END: + return NULL; + } + return NULL; + +} + +bool WeatherData::hasData( PROPERTY property ) +{ + pthread_rwlock_rdlock( &rwLock ); + bool rval= dataAvailable[ property ]; + pthread_rwlock_unlock( &rwLock ); + return rval; +} diff --git a/WeatherData.h b/WeatherData.h new file mode 100644 index 0000000..bcc4d20 --- /dev/null +++ b/WeatherData.h @@ -0,0 +1,141 @@ +/** + * @author Travis E. Brown + * @version 1.0 + * + * WeatherData is the data aggregator. Sources put data into the structure + * using setValue, and sinks pull it out using getValue. Sources don't need + * to set all the properties, and in fact, shouldn't set some of them. The + * setValue will complain if this happens. + */ + +#ifndef __WEATHER_DATA_H__ +#define __WEATHER_DATA_H__ + +#include "Wind.h" +#include "Rain.h" +#include "Database.h" +#include "Now.h" +#include "Config.h" +#include "Hysteresis.h" +#include +#include +#include "Barometer.h" +#include +#include "Almanac.h" + +#define NO_VALUE (0xffffffff) + +class WeatherData +{ + public: + enum PROPERTY { insideTemp, insideHumidity, outsideTemp, outsideHumidity, + windSpeed, windDirection, averageWindDirection, averageWindSpeed, + windGust, windGustDir, dailyRain, rainRate, dailyET, UV, + solarRadiation, solarPercent, stationPressure, SLP, altimeter, + dewPoint, heatIndex, humidex, windChill, apparentTemp, + cloudHeight, rain24Hour, instantRain, END }; + + public: + /** + * Default constructor for Weather Data. + * + * @param cfg Configuration item + */ + WeatherData( Config cfg ); + + /** + * Default constructor for Weather Data. + */ + ~WeatherData(); + + /** + * Sets a weather property to the specified value. This should be used + * by sources to add new data + * + * @param property weather property + * @param value value to set the property to + * @return bool TRUE on succes, FALSE on failure + */ + bool setValue( PROPERTY property, double value ); + + + bool hasData( PROPERTY property ); + double getValue( PROPERTY property, time_t howLongAgo, + time_t *actualTime = NULL ); + + double getHigh( PROPERTY property, time_t howLongAgo, + time_t *actualTime ); + double getLow( PROPERTY property, time_t howLongAgo, time_t + *actualTime ); + double getTrend( PROPERTY property ); + bool newData(); + + const char *propertyToString( PROPERTY property ); + + + public: // public data members + Now *now; + + private: // types + enum DBTable { HOURLY, DAILY }; + enum MINMAX { MIN, MAX }; + enum SUMMARYTYPE { DAILYSUMMARY = 0, DAYTIMESUMMARY = 1, + NIGHTTIMESUMMARY =2 }; + + private: // helper functions + + // Copy data from temporary set structure to actual structure + void copyData( void ); + + double getSolarMax(); + double getDewPoint(); + double getWindChill(); + double getHumidex(); + double getHeatIndex(); + double getApparentTemp(); + double getCloudHeight(); + + void updateDB(); + void updateSummary( DBTable table, SUMMARYTYPE type = DAILYSUMMARY ); + void doMaintenance(); + double computeTrend( PROPERTY property, int duration /* hours */ ); + double getCurrent( PROPERTY property ); + double getFromObs( PROPERTY property, time_t howLongAgo, time_t + *actualTime ); + double getFromHourly( PROPERTY property, time_t howLongAgo, time_t + *actualTime ); + double getFromDaily( PROPERTY property, time_t howLongAgo, time_t + *actualTime ); + void computeTrends(); + double refreshSolarMax(); + double getStationPressure(); + double getStationVaporPressure( double ); + double getActualVaporPressure( double, double ); + double humidityCorrection( double, double, double ); + + void refreshAlmanacs(); + + private: // data members + Config cfg; + Database db; + double currentSolarMax; + Wind wind; + Rain rain; + std::map< PROPERTY, double > setValues; + std::map< PROPERTY, Hysteresis > inst; + std::map< PROPERTY, double > trend; + std::map< PROPERTY, bool > dataAvailable; + Debug dbg; + Barometer baro; + + pthread_rwlock_t rwLock; + + Almanac almanacYesterday; + Almanac almanacToday; + Almanac almanacTomorrow; + + bool dayTime; + +}; + +#endif // __WEATHER_DATA_H__ diff --git a/WeatherSink.cpp b/WeatherSink.cpp new file mode 100644 index 0000000..75876d0 --- /dev/null +++ b/WeatherSink.cpp @@ -0,0 +1,38 @@ +#include "WeatherSink.h" + +WeatherSink::WeatherSink( Config _cfg, WeatherData *_wd, const char *_name ): + workerTime(), + cfg( _cfg ), + wd( _wd ), + name( _name ), + threadRunning( true ), + dbg( _name ) +{ + dbg.printf(NOTICE, "Initializing module\n" ); +} + +void WeatherSink::startMain() +{ + pthread_create( &mainThreadID, NULL, startWeatherSinkMainThread, this ); +} + +void WeatherSink::endMain() +{ + threadRunning = false; + this->newData(); + dbg.printf(NOTICE, "Waiting for worker thread to finish\n" ); + pthread_join( mainThreadID, NULL ); + dbg.printf(NOTICE, "Worker thread finished\n" ); +} + +WeatherSink::~WeatherSink() +{ +} + +void *startWeatherSinkMainThread( void *ptr ) +{ + WeatherSink *ws = ( WeatherSink * )ptr; + ws->main(); + return NULL; +} + diff --git a/WeatherSink.h b/WeatherSink.h new file mode 100644 index 0000000..406228a --- /dev/null +++ b/WeatherSink.h @@ -0,0 +1,46 @@ +#ifndef __WEATHER_SINK_H__ +#define __WEATHER_SINK_H__ + + +#include "Config.h" +#include "Debug.h" +#include +#include +#include "WeatherData.h" + +class WeatherSink +{ + public: + WeatherSink( Config cfg, WeatherData *wd, const char *name ); + virtual ~WeatherSink(); + + // This is a function back that is called when new data is available + // from WeatherData + virtual void newData( void ) = 0; + + // This function WILL be on it's own thread. It should probably loop + // forever. + virtual void main( ) = 0; + + private: + pthread_t mainThreadID; + Now workerTime; + + protected: + void endMain(); + void startMain(); + + protected: + Config cfg; + WeatherData *wd; + sem_t notifyWorker; + sem_t working; + const char *name; + bool threadRunning; + Debug dbg; +}; + +extern "C" { + void *startWeatherSinkMainThread( void *ptr ); +} +#endif // __WEATHER_SINK_H__ diff --git a/WeatherSource.cpp b/WeatherSource.cpp new file mode 100644 index 0000000..2a4d730 --- /dev/null +++ b/WeatherSource.cpp @@ -0,0 +1,25 @@ +#include "WeatherSource.h" + +WeatherSource::WeatherSource() +{ +} + +WeatherSource::~WeatherSource() +{ +} + +bool WeatherSource::read( WeatherData *_wd ) +{ + wd = _wd; + + bool rval = false; + rval = this->read(); + + if ( rval ) + { + wd->newData(); + } + + return rval; + +} diff --git a/WeatherSource.h b/WeatherSource.h new file mode 100644 index 0000000..8d0c465 --- /dev/null +++ b/WeatherSource.h @@ -0,0 +1,23 @@ +#ifndef __WEATHER_SOURCE_H__ +#define __WEATHER_SOURCE_H__ + +#include "WeatherData.h" +#include "Config.h" + +class WeatherSource +{ + public: + WeatherSource( Config &cfg ); + WeatherSource( ); + + virtual ~WeatherSource(); + bool read( WeatherData *wd ); + + public: + virtual bool read() = 0; + + protected: + WeatherData *wd; +}; + +#endif // __WEATHER_SOURCE_H__ diff --git a/Wind.cpp b/Wind.cpp new file mode 100644 index 0000000..2471e6e --- /dev/null +++ b/Wind.cpp @@ -0,0 +1,180 @@ +#include "Wind.h" +#include "units.h" +#include "math.h" + +using namespace std; + +Wind::Wind( Now *_now, Database &_db ): + now(_now), + dbg("Wind"), + db( _db ) +{ + restore(); +} + +Wind::~Wind() +{ + dump(); +} + + +void Wind::restore() +{ + dbResult result = db.query("select * from windData;"); + for ( dbResult::iterator iter = result.begin(); iter!=result.end(); ++iter ) + { + time_t t = iter->second.asInteger("time"); + float speed = iter->second.asDouble("speed"); + pair< double, double > pr( iter->second.asDouble("dirSin"), + iter->second.asDouble("dirCos")); + windSpeed[t] = speed; + windDirection[t] = pr; + } + dbg.printf(NOTICE, "Restored %d wind values\n", result.size()); +} + +void Wind::dump() +{ + time_t t; + float speed; + float dirSin; + float dirCos; + dbg.printf(NOTICE, "Dumping %d wind values to database\n", windSpeed.size()); + + db.query("BEGIN TRANSACTION;"); + db.query("DELETE FROM windData;"); + for ( windSpeed_t::iterator iter = windSpeed.begin(); + iter != windSpeed.end(); ++iter ) + { + t = iter->first; + speed = iter->second; + dirSin = windDirection[t].first; + dirCos = windDirection[t].second; + db.query("insert into windData ( time, speed, dirSin, dirCos ) VALUES ( %d, %f, %f, %f );", (int)t, speed, dirSin, dirCos ); + } + db.query("COMMIT;"); +} + + + + +void Wind::update( double speed, double direction ) +{ + pair pr( sin( deg2rad( direction )), + cos( deg2rad( direction ))); + windDirection[now->unixtime()] = pr ; + windSpeed[now->unixtime()] = speed; + + for ( windSpeed_t::iterator iter = windSpeed.begin(); + iter != windSpeed.end(); ) + { + if ( iter->first <= now->unixtime() - MAX_WIND_DURATION ) + { + windSpeed.erase(iter++); + } + else + { + ++iter; + } + } + + for ( windDirection_t::iterator iter = windDirection.begin(); + iter != windDirection.end(); ) + { + if ( iter->first <= now->unixtime() - MAX_WIND_DURATION ) + { + windDirection.erase(iter++); + } + else + { + ++iter; + } + } +} + +int Wind::getAverageDirection( time_t duration ) +{ + int rval = 0; + double sinSum = 0; + double cosSum = 0; + + for ( windDirection_t::iterator iter = windDirection.begin(); + iter != windDirection.end(); + ++iter ) + { + if ( iter->first > now->unixtime() - duration ) + { + sinSum += iter->second.first; + cosSum += iter->second.second; + } + } + rval = (int)rad2deg( atan2( sinSum, cosSum )); + if ( rval < 0 ) + rval += 360; + + return rval; + +} + +double Wind::getAverageSpeed( time_t duration ) +{ + double rval = 0; + int count = 0; + for ( windSpeed_t::iterator iter = windSpeed.begin(); + iter != windSpeed.end(); + ++iter ) + { + if ( iter->first > now->unixtime() - duration ) + { + rval += iter->second; + count ++; + } + } + return rval / count; +} + +int Wind::getDirectionAtMaxSpeed( time_t duration ) +{ + double maxSpeed = -1; + time_t timeAtMax = 0; + int rval; + for ( windSpeed_t::iterator iter = windSpeed.begin(); + iter != windSpeed.end(); + ++iter ) + { + if ( iter->first > now->unixtime() - duration ) + { + if ( iter->second > maxSpeed ) + { + maxSpeed = iter->second; + timeAtMax = iter->first; + } + } + } + + dbg.printf(DEBUG, "timeAtMax: %d\n", timeAtMax ); + dbg.printf(DEBUG, "maxSpeed: %f\n", maxSpeed ); + rval = (int)rad2deg( atan2( windDirection[ timeAtMax ].first, windDirection[ timeAtMax ].second )); + if ( rval < 0 ) + rval += 360; + + return rval; +} + +double Wind::getMaxSpeed( time_t duration ) +{ + double rval = -1; + for ( windSpeed_t::iterator iter = windSpeed.begin(); + iter != windSpeed.end(); + ++iter ) + { + if ( iter->first > now->unixtime() - duration ) + { + if ( iter->second > rval ) + { + rval = iter->second; + } + } + } + return rval; +} diff --git a/Wind.h b/Wind.h new file mode 100644 index 0000000..b40a142 --- /dev/null +++ b/Wind.h @@ -0,0 +1,38 @@ +#ifndef __WIND_H__ +#define __WIND_H__ + +#include "Now.h" +#include +#include +#include "Database.h" + +#define MAX_WIND_DURATION ( 60 * ONE_MINUTE ) + +class Wind +{ + typedef std::map< time_t, std::pair< double, double> > windDirection_t; + typedef std::map< time_t, double > windSpeed_t; + + public: + Wind( Now *now, Database &_db ); + ~Wind( ); + + void update( double speed, double direction ); + + double getAverageSpeed( time_t duration ); + int getAverageDirection( time_t duration ); + double getMaxSpeed( time_t duration ); + int getDirectionAtMaxSpeed( time_t duration ); + + void dump(); + void restore(); + + private: + windDirection_t windDirection; + windSpeed_t windSpeed; + Now *now; + Debug dbg; + Database db; +}; + +#endif // __WIND_H__ diff --git a/XML.cpp b/XML.cpp new file mode 100644 index 0000000..6c6c82b --- /dev/null +++ b/XML.cpp @@ -0,0 +1,69 @@ +#include "XML.h" +#include +#include + +using namespace std; + +XML::XML( const string &tag ): + dbg("XML"), + tag( tag ) +{ +} + +XML::~XML() +{ +} + +void XML::addNode( const XML &node ) +{ + value.clear(); + nodes.push_back( node ); +} + +string XML::str( int depth, string transform ) +{ + std::ostringstream oss; + if (depth == 0 ) + { + oss << "" << endl << endl; + if ( transform.size() > 0 ) + { + oss << "" << endl; + } + oss << endl; + } + oss << string(depth, ' ' ); + oss << "<" << tag; + + // Add attributes + for ( map::iterator iter = attributes.begin(); + iter != attributes.end(); + ++iter ) + { + oss << " " << iter->first << "=\"" << iter->second << "\""; + } + + if ( value.size() != 0 ) + { + oss << ">" << value << "" << endl; + } + else if ( nodes.size() != 0 ) + { + oss << ">" << endl; + for ( vector< XML >::iterator iter = nodes.begin(); + iter != nodes.end(); + ++iter ) + { + oss << (iter)->str( depth + 1 ); + } + oss << "" << endl; + } + else // No value or no subnodes + { + oss << "/>" << endl; + } + + + return oss.str(); +} diff --git a/XML.h b/XML.h new file mode 100644 index 0000000..ea0348f --- /dev/null +++ b/XML.h @@ -0,0 +1,90 @@ +#ifndef __XML_H__ +#define __XML_H__ + +#include +#include +#include +#include +#include +#include "Debug.h" + +class XML +{ + public: + XML( const std::string &tag ); + + XML( const std::string &_tag, + const std::string &attributeKey, + const std::string &attributeValue ): + dbg("XML") + { + tag = _tag; + attributes[attributeKey] = attributeValue; + } + + template < typename T > XML( const std::string &_tag, T _value, int + precision = 2 ): + dbg("XML") + { + tag = _tag; + nodes.clear(); + std::ostringstream oss; + oss << std::setprecision(precision) << std::fixed << _value; + value = oss.str(); + } + + template < typename T > XML ( const std::string &_tag, + const std::string &attributeKey, + const std::string &attributeValue, + T _value, int precision = 2 ) + { + nodes.clear(); + std::ostringstream oss; + oss << std::setprecision(precision) << std::fixed << _value; + value = oss.str(); + + tag = _tag; + attributes[attributeKey] = attributeValue; + } + + ~XML(); + + public: + template < typename T > void setValue( T _value, int precision = 2, + std::ios_base::fmtflags flags = (std::ios_base::fmtflags)0 ) + { + nodes.clear(); + std::ostringstream oss; + oss << std::setprecision(precision) << std::fixed ; + oss.setf( flags ); + oss << _value; + value = oss.str(); + } + + void addNode( const XML &node ); + + void addAttributes( std::map _attributes ) + { + attributes.insert(_attributes.begin(), _attributes.end()); + } + + template < typename T > void addAttribute( const std::string &key, + T value, + int precision = 2 ) + { + std::ostringstream oss; + oss << std::setprecision(precision) << std::fixed << value; + attributes[key] = oss.str(); + } + + std::string str( int depth = 0, std::string = ""); + + private: + Debug dbg; + std::string tag; + std::map attributes; + std::vector< XML > nodes; + std::string value; +}; + +#endif // __XML_H__ diff --git a/ccitt.h b/ccitt.h new file mode 100644 index 0000000..1dfc65f --- /dev/null +++ b/ccitt.h @@ -0,0 +1,36 @@ +unsigned short crc_table [] = { + +0x0, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, +0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, +0x1231, 0x210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, +0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, +0x2462, 0x3443, 0x420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, +0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, +0x3653, 0x2672, 0x1611, 0x630, 0x76d7, 0x66f6, 0x5695, 0x46b4, +0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, +0x48c4, 0x58e5, 0x6886, 0x78a7, 0x840, 0x1861, 0x2802, 0x3823, +0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, +0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0xa50, 0x3a33, 0x2a12, +0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, +0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0xc60, 0x1c41, +0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, +0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0xe70, +0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, +0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, +0x1080, 0xa1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, +0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, +0x2b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, +0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, +0x34e2, 0x24c3, 0x14a0, 0x481, 0x7466, 0x6447, 0x5424, 0x4405, +0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, +0x26d3, 0x36f2, 0x691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, +0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, +0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x8e1, 0x3882, 0x28a3, +0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, +0x4a75, 0x5a54, 0x6a37, 0x7a16, 0xaf1, 0x1ad0, 0x2ab3, 0x3a92, +0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, +0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0xcc1, +0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, +0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0xed1, 0x1ef0, +}; + diff --git a/client.cpp b/client.cpp new file mode 100644 index 0000000..20657a7 --- /dev/null +++ b/client.cpp @@ -0,0 +1,70 @@ +#include "units.h" +#include +#include +#include +#include /* for socket(), connect(), send(), and recv() */ +#include /* for sockaddr_in and inet_addr() */ +#include /* for gethostbyname */ +#include /* for perror */ +#include "types.h" +#include "packet.h" + +// Cygwin doesn't have these. Why not? +#include "gethostbyname_r.h" +#ifndef MSG_EOR +#define MSG_EOR (0) +#endif + +int main() +{ + char errBuf[128]; + int gethostBuf[255]; + struct sockaddr_in cwopServerAddr; + struct hostent host; + struct hostent *res; + packet_t packet; + int fnord; + int sock; + + if (( sock = socket( AF_INET, SOCK_STREAM, 0 )) <= 0 ) + { + strerror_r( errno, errBuf, 128 ); + printf("socket failed\n"); + return 0; + } + + memset( &cwopServerAddr, 0, sizeof( cwopServerAddr )); + + if ( ( gethostbyname_r( "127.0.0.1", + &host, (char*)&gethostBuf, sizeof(gethostBuf), &res, &fnord )) != 0 ) + { + printf("gethostbyname failed\n"); + return 0; + } + + // Set the out IP address + memcpy( &cwopServerAddr.sin_addr, host.h_addr, host.h_length ); + cwopServerAddr.sin_family = AF_INET; + cwopServerAddr.sin_port = htons(9999); + if ( connect( sock, ( const sockaddr *)&(cwopServerAddr), sizeof( struct sockaddr_in)) < 0 ) + { + strerror_r( errno, errBuf, 128 ); + close( sock ); + printf("could not connect: %s\n", errBuf); + return 0; + } + + packet.reqType = getCurrent; + packet.version = 0x01; + packet.numArgs = 4; + argType arg1; + arg1.i = 0xdead; + send(sock, (void*)&packet, sizeof(packet), MSG_EOR ); + send(sock, (void*)&arg1, sizeof( arg1), MSG_EOR ); + send(sock, (void*)&arg1, sizeof( arg1), MSG_EOR ); + send(sock, (void*)&arg1, sizeof( arg1), MSG_EOR ); + send(sock, (void*)&arg1, sizeof( arg1), MSG_EOR ); + close( sock ); +} + + diff --git a/gethostbyname_r.c b/gethostbyname_r.c new file mode 100644 index 0000000..309db81 --- /dev/null +++ b/gethostbyname_r.c @@ -0,0 +1,113 @@ +// http://www.cygwin.com/ml/cygwin/2004-04/msg00532.html +#include +#include +#include "gethostbyname_r.h" +#include +#include + +#ifdef LOCAL_GETHOSTBYNAME_R +/* duh? ERANGE value copied from web... */ +#define ERANGE 34 +int gethostbyname_r (const char *name, + struct hostent *ret, + char *buf, + size_t buflen, + struct hostent **result, + int *h_errnop) { + + int hsave; + struct hostent *ph; + static pthread_mutex_t __mutex = PTHREAD_MUTEX_INITIALIZER; + pthread_mutex_lock(&__mutex); /* begin critical area */ + hsave = h_errno; + ph = gethostbyname(name); + *h_errnop = h_errno; /* copy h_errno to *h_herrnop */ + if (ph == NULL) { + *result = NULL; + } else { + char **p, **q; + char *pbuf; + size_t nbytes=0; + int naddr=0, naliases=0; + /* determine if we have enough space in buf */ + + /* count how many addresses */ + for (p = ph->h_addr_list; *p != 0; p++) { + nbytes += ph->h_length; /* addresses */ + nbytes += sizeof(*p); /* pointers */ + naddr++; + } + nbytes += sizeof(*p); /* one more for the terminating NULL */ + + /* count how many aliases, and total length of strings */ + + for (p = ph->h_aliases; *p != 0; p++) { + nbytes += (strlen(*p)+1); /* aliases */ + nbytes += sizeof(*p); /* pointers */ + naliases++; + } + nbytes += sizeof(*p); /* one more for the terminating NULL */ + + /* here nbytes is the number of bytes required in buffer */ + /* as a terminator must be there, the minimum value is ph->h_length */ + if(nbytes > buflen) { + *result = NULL; + pthread_mutex_unlock(&__mutex); /* end critical area */ + return ERANGE; /* not enough space in buf!! */ + } + + /* There is enough space. Now we need to do a deep copy! */ + /* Allocation in buffer: + from [0] to [(naddr-1) * sizeof(*p)]: + pointers to addresses + at [naddr * sizeof(*p)]: + NULL + from [(naddr+1) * sizeof(*p)] to [(naddr+naliases) * sizeof(*p)] : + pointers to aliases + at [(naddr+naliases+1) * sizeof(*p)]: + NULL + then naddr addresses (fixed length), and naliases aliases (asciiz). + */ + + *ret = *ph; /* copy whole structure (not its address!) */ + + /* copy addresses */ + q = (char **)buf; /* pointer to pointers area (type: char **) */ + ret->h_addr_list = q; /* update pointer to address list */ + pbuf = buf + ((naddr+naliases+2)*sizeof(*p)); /* skip that area */ + for (p = ph->h_addr_list; *p != 0; p++) { + memcpy(pbuf, *p, ph->h_length); /* copy address bytes */ + *q++ = pbuf; /* the pointer is the one inside buf... */ + pbuf += ph->h_length; /* advance pbuf */ + } + *q++ = NULL; /* address list terminator */ + + /* copy aliases */ + + ret->h_aliases = q; /* update pointer to aliases list */ + for (p = ph->h_aliases; *p != 0; p++) { + strcpy(pbuf, *p); /* copy alias strings */ + *q++ = pbuf; /* the pointer is the one inside buf... */ + pbuf += strlen(*p); /* advance pbuf */ + *pbuf++ = 0; /* string terminator */ + } + *q++ = NULL; /* terminator */ + + strcpy(pbuf, ph->h_name); /* copy alias strings */ + ret->h_name = pbuf; + pbuf += strlen(ph->h_name); /* advance pbuf */ + *pbuf++ = 0; /* string terminator */ + + *result = ret; /* and let *result point to structure */ + + } + h_errno = hsave; /* restore h_errno */ + + pthread_mutex_unlock(&__mutex); /* end critical area */ + + return (*result == NULL); + +} + +#endif /* LOCAL_GETHOSTBYNAME_R */ + diff --git a/gethostbyname_r.h b/gethostbyname_r.h new file mode 100644 index 0000000..14e3cde --- /dev/null +++ b/gethostbyname_r.h @@ -0,0 +1,15 @@ +/* including netdb.h suppresses warnings from the use of hostent */ +#include + +#ifndef __LINUX__ +#define LOCAL_GETHOSTBYNAME_R + +int gethostbyname_r (const char *name, + struct hostent *ret, + char *buf, + size_t buflen, + struct hostent **result, + int *h_errnop); + +#endif /* gethostbyname_r */ + diff --git a/httpserver.cpp b/httpserver.cpp new file mode 100644 index 0000000..b674169 --- /dev/null +++ b/httpserver.cpp @@ -0,0 +1,514 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "httpserver.h" +#include "XML.h" +#include +#include + +using namespace std; + +#define MAXBUF (1024) + +HTTPServer::HTTPServer( Config cfg, WeatherData *_wd ): + WeatherSink( cfg, _wd, "HTTP Server") +{ + startMain(); +} + +HTTPServer::~HTTPServer() +{ + endMain(); +} + +void HTTPServer::return404( FILE *fp, string url ) +{ + std::ostringstream oss; + oss << "" << endl; + oss << "404 Not Found" << endl; + oss << "" << endl; + oss << "

Not Found

" << endl; + oss << "

The request URL " << url << " was not found on the server.

" + << endl; + oss << "" << endl; + + fprintf(fp, "HTTP/1.1 404 Not Found\n"); + fprintf(fp, "Content-Type: text/html\n"); + fprintf(fp, "Content-Length: %d\n\n", oss.str().size()); + fprintf(fp, "%s", oss.str().c_str()); + fflush(fp); +} + +int HTTPServer::outputXMLFile( FILE *outFp, const char *filename ) +{ + char buf[ 1024 ]; + struct stat statBuf; + + if ( stat( filename, &statBuf ) == -1 ) + { + dbg.printf(NOTICE, "error stat'ing %s: %s\n", filename, + strerror(errno)); + return -1; + } + int fileSize = statBuf.st_size; + int len=0; + + fprintf(outFp, "HTTP/1.1 200 OK\n"); + fprintf(outFp, "Content-type: text/xml\n" ); + fprintf(outFp, "Content-length: %d\n\n", fileSize ); + + int inFD = open(filename, O_RDONLY ); + if ( inFD != -1 ) + { + while( (len = read( inFD, buf, sizeof( buf ))) > 0 ) + { + fwrite( buf, len, 1, outFp ); + } + } + else + { + dbg.printf(NOTICE, "Error reading %s: %s\n", filename, strerror( errno + )); + return -1; + } + close( inFD ); + fflush(outFp); + return fileSize; +} + +string HTTPServer::getCurrent() +{ + //char rfc2822_timestamp[ 64 ]; + char rfc1123_timestamp[ 64 ]; + char secs[64]; + wd->now->RFC1123( rfc1123_timestamp, 64 ); + snprintf(secs, 64, "%d", (int)wd->now->unixtime()); + XML weather("weather", "version", "1.0"); + XML location("location"); + XML observation("observation"); + observation.addAttribute("timestamp", rfc1123_timestamp ); + observation.addAttribute("secs", secs ); + + { + XML sensor( "sensor", wd->getValue(WeatherData::outsideTemp, 0 )); + sensor.addAttribute("type", "outsideTemperature" ); + sensor.addAttribute("units", "°F" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::outsideHumidity, 0 )); + sensor.addAttribute("type", "outsideHumidity" ); + sensor.addAttribute("units", "%" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::insideTemp, 0 )); + sensor.addAttribute("type", "insideTemperature" ); + sensor.addAttribute("units", "°F" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::insideHumidity, 0 )); + sensor.addAttribute("type", "insideHumidity" ); + sensor.addAttribute("units", "%" ); + observation.addNode( sensor ); + } + + { + double val = wd->getValue(WeatherData::heatIndex, 0 ); + XML sensor( "sensor" ); + if ( val != NO_VALUE ) + { + sensor.setValue( val ); + } + sensor.addAttribute("type", "heatIndex" ); + sensor.addAttribute("units", "°F" ); + observation.addNode( sensor ); + } + + { + double val = wd->getValue(WeatherData::humidex, 0 ); + XML sensor( "sensor" ); + if ( val != NO_VALUE ) + { + sensor.setValue( val ); + } + sensor.addAttribute("type", "humidex" ); + sensor.addAttribute("units", "°F" ); + observation.addNode( sensor ); + } + + { + double val = wd->getValue(WeatherData::windChill, 0 ); + XML sensor( "sensor" ); + if ( val != NO_VALUE ) + { + sensor.setValue( val ); + } + sensor.addAttribute("type", "windChill" ); + sensor.addAttribute("units", "°F" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::apparentTemp, 0 )); + sensor.addAttribute("type", "apparentTemp" ); + sensor.addAttribute("units", "°F" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::averageWindDirection, 0 )); + sensor.addAttribute("type", "averageWindDirection" ); + sensor.addAttribute("units", "°" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::averageWindSpeed, 0 )); + sensor.addAttribute("type", "averageWindSpeed" ); + sensor.addAttribute("units", "mph" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::windDirection, 0 )); + sensor.addAttribute("type", "instantWindDirection" ); + sensor.addAttribute("units", "°" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::windSpeed, 0 )); + sensor.addAttribute("type", "instantWindSpeed" ); + sensor.addAttribute("units", "mph" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::windGustDir, 0 )); + sensor.addAttribute("type", "windGustDirection" ); + sensor.addAttribute("units", "°" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::windGust, 0 )); + sensor.addAttribute("type", "windGustSpeed" ); + sensor.addAttribute("units", "mph" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::rainRate, 0 )/100.0); + sensor.addAttribute("type", "rainRate" ); + sensor.addAttribute("units", "in/hr" ); + observation.addNode( sensor ); + + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::instantRain, 0 )/100.0); + sensor.addAttribute("type", "instantRainRate" ); + sensor.addAttribute("units", "in/hr" ); + observation.addNode( sensor ); + + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::dailyRain, 0 )/100.0); + sensor.addAttribute("type", "dailyRain" ); + sensor.addAttribute("units", "in" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::rain24Hour, 0 )/100.0); + sensor.addAttribute("type", "rainLast24Hours" ); + sensor.addAttribute("units", "in" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::stationPressure, 0 )); + sensor.addAttribute("type", "stationPressure" ); + sensor.addAttribute("units", "inHg" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::SLP, 0 )); + sensor.addAttribute("type", "SLP" ); + sensor.addAttribute("units", "inHg" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::altimeter, 0 )); + sensor.addAttribute("type", "altimeter" ); + sensor.addAttribute("units", "inHg" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::dewPoint, 0 )); + sensor.addAttribute("type", "dewPoint" ); + sensor.addAttribute("units", "°F" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::cloudHeight, 0 )); + sensor.addAttribute("type", "cloudHeight" ); + sensor.addAttribute("units", "ft" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::solarRadiation, 0 )); + sensor.addAttribute("type", "solarRadiation" ); + sensor.addAttribute("units", "W/m^2" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::solarPercent, 0 )); + sensor.addAttribute("type", "solarRadiationPercent" ); + sensor.addAttribute("units", "%" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::UV, 0 )); + sensor.addAttribute("type", "UV" ); + sensor.addAttribute("units", "index" ); + observation.addNode( sensor ); + } + + { + XML sensor( "trend" ); + sensor.setValue( wd->getTrend(WeatherData::altimeter ), 3, ios_base::showpos ); + sensor.addAttribute("type", "altimeter" ); + sensor.addAttribute("units", "inHg/hr" ); + observation.addNode( sensor ); + } + + { + XML sensor( "trend" ); + sensor.setValue(wd->getTrend(WeatherData::outsideTemp ), 3, ios_base::showpos ); + sensor.addAttribute("type", "outsideTemperature" ); + sensor.addAttribute("units", "°F/hr" ); + observation.addNode( sensor ); + } + + { + XML sensor( "trend" ); + sensor.setValue(wd->getTrend(WeatherData::outsideHumidity ), 3, ios_base::showpos ); + sensor.addAttribute("type", "outsideHumidity" ); + sensor.addAttribute("units", "%/hr" ); + observation.addNode( sensor ); + } + + { + XML sensor( "trend" ); + sensor.setValue(wd->getTrend(WeatherData::dewPoint ), 3, ios_base::showpos ); + sensor.addAttribute("type", "dewPoint" ); + sensor.addAttribute("units", "°F/hr" ); + observation.addNode( sensor ); + } + + location.addAttribute("postal_code", cfg.getString("postal_code")); + location.addAttribute("city", cfg.getString("city")); + location.addAttribute("state", cfg.getString("state")); + location.addAttribute("latitude", cfg.getDouble("latitude"), 4); + location.addAttribute("longitude", cfg.getDouble("longitude"), 4); + + location.addNode( observation ); + weather.addNode( location ); + + return weather.str( 0, "weather.xsl" ); +} + +void HTTPServer::processRequest( FILE *fp, std::string req ) +{ + string content; + if ( req == "/" ) + { + content = getCurrent(); + } + + else if ( req == "/weather.xsl" ) + { + if ( outputXMLFile(fp, "weather.xsl") == -1 ) + { + return404( fp, req ); + } + // Return early because we already sent the output + return; + } + + else + { + return404( fp, req ); + return; + } + + + fprintf(fp, "HTTP/1.1 200 OK\n"); + fprintf(fp, "Content-type: text/xml\n" ); + fprintf(fp, "Content-length: %d\n\n", content.size()); + fprintf(fp, "%s", content.c_str() ); + fflush(fp); + +} + +void HTTPServer::worker( int client ) +{ + int len; + int timeout = 15; + FILE* fp = fdopen(client, "w"); + fd_set rfds; + FD_ZERO( &rfds ); + FD_SET( client, &rfds ); + struct timeval tv; + tv.tv_sec = timeout; + tv.tv_usec = 0; + + while ( select( client + 1, &rfds, NULL, NULL, &tv ) > 0 ) + { + + if ( ( len = recv(client, buffer, MAXBUF, 0)) <= 0 ) + break; + if ( fp == NULL ) + { + dbg.printf(EMERG, "fpopen failed: %s\n", strerror( errno )); + } + else + { + char req[PATH_MAX] = ""; + sscanf(buffer, "GET %s HTTP", req); + dbg.printf(INFO, "Request: \"%s\"\n", req); + if ( req[0] != 0 ) + processRequest( fp, req ); + + } + tv.tv_sec = timeout; + tv.tv_usec = 0; + FD_ZERO( &rfds ); + FD_SET( client, &rfds ); + } + dbg.printf(NOTICE, "Closing HTTP connection\n"); + fclose(fp); + close(client); +} + +void HTTPServer::main(void) +{ + struct sockaddr_in addr; + int sd, addrlen = sizeof(addr); + fd_set rfds; + int on = 1; + pthread_t workerId; + + if ( (sd = socket(PF_INET, SOCK_STREAM, 0)) < 0 ) + { + dbg.printf(EMERG, "Error creating socket: %s\n", strerror( errno )); + return; + } + if ( setsockopt( sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof( on )) < 0 ) + { + dbg.printf(EMERG, "setsockopt: %s\n", strerror( errno )); + return; + }; + addr.sin_family = AF_INET; + int port = cfg.getInteger("http_port"); + if ( port == 0 ) + port = 6100; + dbg.printf(NOTICE, "Listening on port: %d\n", port ); + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + if ( bind(sd, (struct sockaddr*)&addr, addrlen) < 0 ) + { + dbg.printf(EMERG, "Error binding: %s\n", strerror( errno )); + return; + } + if ( listen(sd, 20) < 0 ) + { + dbg.printf(EMERG, "Error listening: %s\n", strerror( errno )); + return; + } + + + struct timeval tv; + while ( threadRunning ) + { + tv.tv_sec = 1; + tv.tv_usec = 0; + FD_ZERO( &rfds ); + FD_SET( sd, &rfds ); + + int rval = select ( sd +1, &rfds, NULL, NULL, &tv ); + + if ( rval <= 0 ) + { + if ( !threadRunning ) + { + dbg.printf(EMERG, "Leaving\n"); + break; + } + else + { + continue; + } + } + int client = accept(sd, (struct sockaddr*)&addr, (socklen_t*)&addrlen); + if ( client < 0 ) + { + dbg.printf(EMERG, "Error accept connection: %s\n", strerror( errno )); + } + else + { + dbg.printf(NOTICE, "Connected: %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + HTTPThread_t *t = new HTTPThread_t; + t->server = this; + t->client = client; + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED ); + pthread_create( &workerId, &attr, startHTTPThread, (void*)t); + } + } + dbg.printf(DEBUG, "closing socket\n"); + close(sd); + return; +} + +void *startHTTPThread( void *ptr ) +{ + Debug dbg("HTTP Thread"); + HTTPThread_t *p = (HTTPThread_t *)ptr; + HTTPServer *http = p->server; + http->worker( p->client ); + delete( p ); + return NULL; +} + diff --git a/httpserver.h b/httpserver.h new file mode 100644 index 0000000..4df7fae --- /dev/null +++ b/httpserver.h @@ -0,0 +1,40 @@ +#ifndef __HTTP_SERVER_H__ +#define __HTTP_SERVER_H__ +#include "WeatherSink.h" + +#define MAX_HTTP_BUF (1024) + + +class HTTPServer:public WeatherSink +{ +public: + HTTPServer( Config cfg, WeatherData *wd ); + ~HTTPServer(); + + void worker( int client ); + void main( void ); + void newData() {}; + +private: + void processRequest( FILE *fp, std::string req ); + std::string getCurrent(); + int outputXMLFile( FILE *outFp, const char *filename ); + void return404( FILE *fp, std::string url ); + +private: + char buffer[MAX_HTTP_BUF]; +}; + +typedef struct +{ + HTTPServer *server; + int client; +} HTTPThread_t; + +extern "C" { + void *startHTTPThread( void * ptr ); +}; + + +#endif // __HTTP_SERVER_H__ + diff --git a/httptest.py b/httptest.py new file mode 100644 index 0000000..e6e6652 --- /dev/null +++ b/httptest.py @@ -0,0 +1,36 @@ +import httplib, socket, time +host = "localhost" +port = 61000 +path = "/" + +host = host.replace("http://", "", 1 ) +print host + +try: + conn = httplib.HTTPConnection(host, port) +except socket.error as ex: + print ex + + +while ( 1 ): + try: + print "Fetching: %s" % path + conn.request("GET", path ) + except socket.gaierror as ex: + foo = "%s" % ex + print "Error: %s" % (foo) + break + except socket.error as ex: + print "%s:%d: %s" % (path, port, ex) + except httplib.CannotSendRequest: + conn.close() + conn = httplib.HTTPConnection(host, port) + else: + r1 = conn.getresponse() + print r1.status, r1.reason + data1 = r1.read() + print data1 + + time.sleep(2) + +conn.close() diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..effc8b3 --- /dev/null +++ b/main.cpp @@ -0,0 +1,156 @@ +#include +#include +#include "VantagePro.h" +#include "testWriter.h" +#include "testReader.h" +#include "wunderground.h" +#include "weatherbug.h" +#include "pwsweather.h" +#include "WeatherSink.h" +#include "WeatherSource.h" +#include "CWOP.h" +#include "Config.h" +#include +#include "Debug.h" +#include "httpserver.h" +#include "socket.h" +#include "Alarms.h" +#include + +using namespace std; + +int running = true; + +void SigHandler( int sigNum ) +{ + if ( sigNum == SIGTERM || sigNum == SIGINT ) + { + running = false; + } + +} + +int main( int argc, char **argv ) +{ + char *configFile = NULL; + Debug dbg("main"); + dbg.printf(EMERG, "WXPro DR1\n"); + dbg.printf(EMERG, "Copyright (C) 2008 Travis E. Brown. All right reserved.\n"); + dbg.printf(EMERG, "Initializing....\n"); + + dbg.printf(EMERG, "\n\n"); + dbg.printf(EMERG, "---------------------------------------\n"); + dbg.printf(EMERG, "WxPro DR1 Initilizing\n"); + + int c; + extern char *optarg; + int pid; + + if (fork()) return 0; + pid = fork(); + + if ( pid ) + { + dbg.printf(EMERG, "Backgrounding process. PID = %d\n", pid ); + return 0; + } + + + + + while (( c= getopt( argc, argv, "c:")) != -1 ) + { + switch (c) + { + case 'c': + configFile = optarg; + break; + } + } + + if ( configFile == NULL ) + { + configFile = strdup("wxpro.cfg"); + } + dbg.printf(EMERG, "Reading config file: %s\n", configFile ); + signal( SIGINT, SigHandler ); + signal( SIGUSR1, SigHandler ); + signal( SIGUSR2, SigHandler ); + signal( SIGTERM, SigHandler ); + signal( SIGPIPE, SigHandler ); + + Config cfg(configFile); + if ( cfg.getInteger("test_only") == 1 ) + { + printf( "Test Only. No data will be sent to wunderground/CWOP\n"); + dbg.printf(EMERG, "Test Only. No data will be sent to wunderground/CWOP\n"); + } + WeatherData wd( cfg ); + vector< WeatherSource * > sources; + vector< WeatherSink * > sinks; + if ( cfg.getInteger("test_only") == 1 ) + { + sources.push_back( new TestReader(cfg )); + } + else + { + sources.push_back( new VantagePro(cfg )); + } + + sinks.push_back( new Wunderground(cfg, &wd)); + sinks.push_back( new WeatherBug(cfg, &wd)); + sinks.push_back( new PWSWeather(cfg, &wd)); + //sinks.push_back( new TestWriter(cfg, &wd)); + sinks.push_back( new CWOP(cfg, &wd)); + sinks.push_back( new HTTPServer(cfg, &wd)); + sinks.push_back( new Socket(cfg, &wd)); + sinks.push_back( new Alarms(cfg, &wd)); + + bool successfulRead = false; + + + while ( running ) + { + successfulRead = false; + wd.now->updateTime(); + for ( vector< WeatherSource *>::iterator iter = sources.begin(); + iter!=sources.end(); + ++iter ) + { + successfulRead |= (*iter)->read( &wd ); + } + + if ( successfulRead ) + { + for ( vector< WeatherSink *>::iterator iter = sinks.begin(); + iter!=sinks.end(); + ++iter ) + { + (*iter)->newData( ); + } + } + else + { + dbg.printf(CRIT, "No successful read. Skipping writes\n"); + } + sleep(1); + } + + + // Delete all our sources and sinks + for ( vector< WeatherSource *>::iterator iter = sources.begin(); + iter!=sources.end(); ++iter ) + { + delete ( *iter ); + } + sources.erase( sources.begin(), sources.end() ); + for ( vector< WeatherSink *>::iterator iter = sinks.begin(); + iter!=sinks.end(); ++iter ) + { + delete ( *iter ); + } + sinks.erase( sinks.begin(), sinks.end() ); + //printf("Cleaning up...\n\r\n"); + + return 0; +} diff --git a/makefile b/makefile new file mode 100644 index 0000000..76c1515 --- /dev/null +++ b/makefile @@ -0,0 +1,121 @@ +SRCS = sql.cpp \ + Config.cpp \ + WeatherData.cpp \ + WeatherSource.cpp \ + WeatherSink.cpp \ + units.c \ + utils.c \ + Database.cpp \ + VantagePro.cpp \ + Debug.cpp \ + Wind.cpp \ + Rain.cpp \ + Now.cpp \ + testReader.cpp \ + Hysteresis.cpp \ + Barometer.cpp \ + wunderground.cpp \ + weatherbug.cpp \ + pwsweather.cpp \ + CWOP.cpp \ + gethostbyname_r.c \ + httpserver.cpp \ + XML.cpp \ + Almanac.cpp \ + Alarms.cpp \ + socket.cpp + + +LD_OPTS=-lsqlite3 -lncurses -lcurl + +DEBUG=-g + +CFLAGS=$(DEBUG) -Wall +LDFLAGS=$(DEBUG) + +CC = gcc +CPP = g++ + +OBJDIR = .objs +DEPEND = -MD +DEPDIR = .deps +df = $(DEPDIR)/$(*F) + +OBJS:=$(addprefix $(OBJDIR)/,$(patsubst %.cpp,%.o,$(patsubst %.c,%.o,$(SRCS)))) + +EXES = wxprod + +ifdef WINDOWS_STYLE_MAKE +__CYGWINPATH_TO_WINDOWS := 's@/cygdrive/\([A-z]\)@\1:@g' +else +__CYGWINPATH_TO_WINDOWS := '' +endif + +define makedeps + sed -e 's@\([A-z]\):/@/cygdrive/\1/@' $*.d >> $*.d.trans; \ + cp $*.d.trans $(df).P.tmp; \ + sed -e 's/#.*//' \ + -e 's/^[^:]*: *//' \ + -e 's/ *\\$$//' \ + -e '/^$$/ d' \ + -e 's/$$/ :/' \ + < $*.d.trans >> $(df).P.tmp; \ + sed -e $(__CYGWINPATH_TO_WINDOWS) < $(df).P.tmp > $(df).P; \ + rm -f $*.d $*.d.trans $(df).P.tmp +endef + +all: prepare-0 $(EXES) + +weatherlink: FORCE + gcc -Wall -g -I/usr/local/include weatherlink.c ./curl-7.22.0/lib/.libs/libcurl.a /usr/lib/libidn.a /usr/lib/i386-linux-gnu/librt.a /usr/lib/i386-linux-gnu/libc.a /usr/lib/i386-linux-gnu/libz.a /usr/lib/libtidy.a -o weatherlink + + +prepare-0:: FORCE + @-mkdir -p $(DEPDIR) $(OBJDIR) > /dev/null + +%.o:../%.c + @echo " CC $(subst $(OBJDIR)/../,,$<)" + @$(CPP) $(DEFS) $(DEPEND) $(CFLAGS) -c -o $@ $< + @$(call makedeps) + +%.o:../%.cpp + @echo " CPP $(subst $(OBJDIR)/../,,$<)" + @$(CPP) $(DEFS) $(DEPEND) $(CFLAGS) -c -o $@ $< + @$(call makedeps) + + +wxprod: $(OBJS) main.o + @echo " LD $@" + @$(CPP) $(DEFS) $(LDFLAGS) -o $@ $^ $(LD_OPTS) + +wxpro: .objs/testWriter.o + $(CPP) $(DEFS) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LD_OPTS) + +test: .objs/Debug.o .objs/Almanac.o .objs/units.o .objs/utils.o .objs/Config.o test.cpp + $(CPP) $(DEFS) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LD_OPTS) + +t: client server + +server: .objs/server.o .objs/gethostbyname_r.o + $(CPP) $(DEFS) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LD_OPTS) + +client: .objs/client.o .objs/gethostbyname_r.o + $(CPP) $(DEFS) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LD_OPTS) + +clean: FORCE + @rm -f $(OBJS) wxprod + +realclean: FORCE + @rm -f $(OBJS) wxprod + @rm -f $(DEPS) wxprod + +deps: $(SRCS) + $(CPP) -MM -E $^ > /dev/null + mv $(DEPS) .deps/ + +FORCE: ; + +.PHONY: clean + +-include $(addprefix $(DEPDIR)/,$(patsubst %.cxx,%.P, $(patsubst %.cpp,%.P, $(patsubst %.c,%.P,$(subst /,-,$(SRCS)))))) + diff --git a/packet.h b/packet.h new file mode 100644 index 0000000..9959d76 --- /dev/null +++ b/packet.h @@ -0,0 +1,16 @@ +typedef enum { getCurrent = 0, getValue, getTrend, getAll } requestType; +typedef struct +{ + requestType reqType __attribute__ ((packed)); + UINT8 version; + UINT8 numArgs; + UINT16 crc; +} packet_t; + +typedef struct +{ + union { + UINT64 i; + F64 f; + }; +} argType; diff --git a/pwsweather.cpp b/pwsweather.cpp new file mode 100644 index 0000000..926b364 --- /dev/null +++ b/pwsweather.cpp @@ -0,0 +1,164 @@ +#include "pwsweather.h" +#include "time.h" +#include +#include "utils.h" +#include + +size_t PWSWeather::curl_write( void *buffer, size_t size, size_t nmemb, void* userp ) +{ + return size*nmemb; +} + + +PWSWeather::PWSWeather( Config cfg, WeatherData *_wd ): + WeatherSink( cfg, _wd, "PWSWeather" ), + failureCount( 0 ), + updateFrequency( 60 ) +{ + pthread_mutex_init( ¬ify, NULL ); + uploadTime.updateTime(); + startMain(); +} + +PWSWeather::~PWSWeather() +{ + endMain(); + +} + + +void PWSWeather::newData() +{ + pthread_mutex_unlock( ¬ify ); +} + +void PWSWeather::generatePacket() +{ + struct tm gmt; + time_t t; + t = (time_t) wd->now->unixtime() ; + char st[32]; + + + gmtime_r( &t, &gmt ); + strftime( st, 31, "%Y-%m-%d+%H%%3A%M%%3A%S", &gmt ); + + snprintf(uploadString, PWSWEATHER_STRING_SIZE, + "%s?ID=%s&PASSWORD=%s&dateutc=%s", + PWSWEATHER_RT_URL, + cfg.getString("pwsweather_id").c_str(), + cfg.getString("pwsweather_password").c_str(), + st ); + + appendData( "&winddir=%.0f", WeatherData::windDirection ); + appendData( "&windspeedmph=%.2f",WeatherData::averageWindSpeed ); + appendData( "&windgustmph=%.2f", WeatherData::windGust ); + appendData( "&tempf=%.2f", WeatherData::outsideTemp ); + appendData( "&rainin=%.2f", WeatherData::rainRate, 100.0 ); + appendData( "&dailyrainin=%.2f", WeatherData::dailyRain, 100.0 ); + appendData( "&baromin=%.2f", WeatherData::SLP ); + appendData( "&dewptf=%.2f", WeatherData::dewPoint ); + appendData( "&humidity=%.0f", WeatherData::outsideHumidity ); + appendData( "&solarradiation=%.0f", WeatherData::solarRadiation ); + appendData( "&UV=%.1f", WeatherData::UV ); + + strnfcat( uploadString, PWSWEATHER_STRING_SIZE, + "&softwaretype=%s%s&action=updateraw", + "WxPro%20", + "DR1"); + +} + +void PWSWeather::uploadPacket() +{ + if ( cfg.getInteger("test_only") != 1 ) + { + curl = curl_easy_init(); + curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, PWSWeather::curl_write); + curl_easy_setopt( curl, CURLOPT_TIMEOUT, 5 ); + curl_easy_setopt( curl, CURLOPT_ERRORBUFFER, curlErrorBuf ); + curl_easy_setopt( curl, CURLOPT_NOSIGNAL, 1 ); + curl_easy_setopt( curl, CURLOPT_URL, uploadString ); + dbg.printf( DEBUG, "CURL upload string: '%s'\n", uploadString ); + res=curl_easy_perform(curl); + if ( (int)res != 0 ) + { + failureCount++; + if ( failureCount < 10 ) + { + dbg.printf(DEBUG, "Could not write to PWSWeather. Error: %s (%d)\n", + curlErrorBuf, res ); + } + else + { + dbg.printf(CRIT, "Could not write to PWSWeather after 10 attempts. Error: %s (%d)\n", + curlErrorBuf, res ); + failureCount = 0; + } + } + else + { + failureCount = 0; + } + if ( curl ) + curl_easy_cleanup( curl ); + } + else + { + dbg.printf(NOTICE,"Pretending to Uploading to PWSWeather\n"); + dbg.printf(DEBUG, "uploadString: %s\n", uploadString ); + } + uploadTime.updateTime(); +} + +void PWSWeather::main() +{ + + while( threadRunning ) + { + pthread_mutex_lock( ¬ify ); + if ( !threadRunning ) + return; + + float timeSinceLast = uploadTime.timeSinceLast(); + dbg.printf(DEBUG, "time since last: %f\n", timeSinceLast); + if ( timeSinceLast <= 2 ) + { + dbg.printf(DEBUG, "too soon since last update.. not doing anything\n"); + continue; + } + if ( timeSinceLast > 2 && timeSinceLast < updateFrequency ) + { + int sleepTime = (int)((updateFrequency - timeSinceLast ) ); + dbg.printf(DEBUG, "too soon since last update.. sleeping for %d usec\n", sleepTime); + for ( int i=0; i < sleepTime; i++ ) + { + sleep( 1 ); + if ( !threadRunning ) + return; + } + } + + generatePacket(); + uploadPacket(); + } +} + + +bool PWSWeather::appendData( const char *format, WeatherData::PROPERTY property, float divider ) +{ + char buf[255]; + if ( wd->hasData( property ) ) + { + snprintf(buf, 255, format, wd->getValue( property, 0 ) / divider ); + if ( strlen(buf) + strlen( uploadString ) >= PWSWEATHER_STRING_SIZE ) + { + dbg.printf(CRIT, "Buffer size exceeded.\n"); + return false; + } + strcat( uploadString, buf ); + return true; + } + return false; + +} diff --git a/pwsweather.h b/pwsweather.h new file mode 100644 index 0000000..a6e06b4 --- /dev/null +++ b/pwsweather.h @@ -0,0 +1,74 @@ +#ifndef __PWSWeather_H__ +#define __PWSWeather_H__ +#include "WeatherSink.h" +#include + +#define PWSWEATHER_STRING_SIZE (512) +#define PWSWEATHER_RT_URL "http://www.pwsweather.com/pwsupdate/pwsupdate.php" + +class PWSWeather: public WeatherSink +{ +public: + PWSWeather( Config cfg, WeatherData *wd ); + ~PWSWeather(); + + void newData(); + void main(); + + static size_t curl_write( void *buffer, size_t size, size_t nmemb, void* userp ); + +private: + bool appendData( const char * format, WeatherData::PROPERTY, float divider = 1 ); + void generatePacket( void ); + void uploadPacket( void ); + +private: + pthread_mutex_t notify; + CURL *curl; + CURLcode res; + int failureCount; + float updateFrequency; + Now uploadTime; + + char curlErrorBuf[CURL_ERROR_SIZE]; + char uploadString[PWSWEATHER_STRING_SIZE]; +}; + +#endif //__PWSWeather_H__ +#ifndef __PWSWeather_H__ +#define __PWSWeather_H__ +#include "WeatherSink.h" +#include + +#define PWSWEATHER_STRING_SIZE (512) +#define PWSWEATHER_RT_URL "http://www.pwsweather.com/pwsupdate/pwsupdate.php" + +class PWSWeather: public WeatherSink +{ +public: + PWSWeather( Config cfg, WeatherData *wd ); + ~PWSWeather(); + + void newData(); + void main(); + + static size_t curl_write( void *buffer, size_t size, size_t nmemb, void* userp ); + +private: + bool appendData( const char * format, WeatherData::PROPERTY, float divider = 1 ); + void generatePacket( void ); + void uploadPacket( void ); + +private: + pthread_mutex_t notify; + CURL *curl; + CURLcode res; + int failureCount; + float updateFrequency; + Now uploadTime; + + char curlErrorBuf[CURL_ERROR_SIZE]; + char uploadString[PWSWEATHER_STRING_SIZE]; +}; + +#endif //__PWSWeather_H__ diff --git a/rscalc.c b/rscalc.c new file mode 100644 index 0000000..2ac5652 --- /dev/null +++ b/rscalc.c @@ -0,0 +1,210 @@ +// C program calculating the sunrise and sunset for +// the current date and a fixed location(latitude,longitude) +// Note, twilight calculation gives insufficient accuracy of results +// Jarmo Lammi 1999 - 2001 +// Last update July 21st, 2001 + +#include +#include +#include +#include + +double pi = 3.14159; +double degs; +double rads; + +double L,g,daylen; +double SunDia = 0.53; // Sunradius degrees + +double AirRefr = 34.0/60.0; // athmospheric refraction degrees // + +// Get the days to J2000 +// h is UT in decimal hours +// FNday only works between 1901 to 2099 - see Meeus chapter 7 + +double FNday (int y, int m, int d, float h) { +long int luku = - 7 * (y + (m + 9)/12)/4 + 275*m/9 + d; +// type casting necessary on PC DOS and TClite to avoid overflow +luku+= (long int)y*367; +return (double)luku - 730531.5 + h/24.0; +}; + +// the function below returns an angle in the range +// 0 to 2*pi + +double FNrange (double x) { + double b = 0.5*x / pi; + double a = 2.0*pi * (b - (long)(b)); + if (a < 0) a = 2.0*pi + a; + return a; +}; + +// Calculating the hourangle +// +double f0(double lat, double declin) { +double fo,dfo; +// Correction: different sign at S HS +dfo = rads*(0.5*SunDia + AirRefr); if (lat < 0.0) dfo = -dfo; +fo = tan(declin + dfo) * tan(lat*rads); +if (fo>0.99999) fo=1.0; // to avoid overflow // +fo = asin(fo) + pi/2.0; +return fo; +}; + +// Calculating the hourangle for twilight times +// +double f1(double lat, double declin) { +double fi,df1; +// Correction: different sign at S HS +df1 = rads * 6.0; if (lat < 0.0) df1 = -df1; +fi = tan(declin + df1) * tan(lat*rads); +if (fi>0.99999) fi=1.0; // to avoid overflow // +fi = asin(fi) + pi/2.0; +return fi; +}; + + +// Find the ecliptic longitude of the Sun + +double FNsun (double d) { + +// mean longitude of the Sun + +L = FNrange(280.461 * rads + .9856474 * rads * d); + +// mean anomaly of the Sun + +g = FNrange(357.528 * rads + .9856003 * rads * d); + +// Ecliptic longitude of the Sun + +return FNrange(L + 1.915 * rads * sin(g) + .02 * rads * sin(2 * g)); +}; + + +// Display decimal hours in hours and minutes +void showhrmn(double dhr) { +int hr,mn; +hr=(int) dhr; +mn = (dhr - (double) hr)*60; + +printf("%0d:%0d",hr,mn); +}; + +int main(void){ + +double y,m,day,h,latit,longit; +float inlat,inlon,intz; +double tzone,d,lambda; +double obliq,alpha,delta,LL,equation,ha,hb,twx; +double twam,altmax,noont,settm,riset,twpm; +time_t sekunnit; +struct tm *p; + +degs = 180.0/pi; +rads = pi/180.0; +// get the date and time from the user +// read system date and extract the year + +/** First get time **/ +time(&sekunnit); + +/** Next get localtime **/ + + p=localtime(&sekunnit); + + y = p->tm_year; + // this is Y2K compliant method + y+= 1900; + m = p->tm_mon + 1; + + day = p->tm_mday; + + h = 12; + + printf("year %4d month %2d\n",(int)y,(int)m); + printf("Input latitude, longitude and timezone\n"); + scanf("%f", &inlat); scanf("%f", &inlon); + scanf("%f", &intz); + latit = (double)inlat; longit = (double)inlon; + tzone = (double)intz; + +// testing +// m=6; day=10; + + +d = FNday(y, m, day, h); + +// Use FNsun to find the ecliptic longitude of the +// Sun + +lambda = FNsun(d); + +// Obliquity of the ecliptic + +obliq = 23.439 * rads - .0000004 * rads * d; + +// Find the RA and DEC of the Sun + +alpha = atan2(cos(obliq) * sin(lambda), cos(lambda)); +delta = asin(sin(obliq) * sin(lambda)); + +// Find the Equation of Time +// in minutes +// Correction suggested by David Smith +LL = L - alpha; +if (L < pi) LL += 2.0*pi; +equation = 1440.0 * (1.0 - LL / pi/2.0); +ha = f0(latit,delta); +hb = f1(latit,delta); +twx = hb - ha; // length of twilight in radians +twx = 12.0*twx/pi; // length of twilight in hours +printf("ha= %.2f hb= %.2f \n",ha,hb); +// Conversion of angle to hours and minutes // +daylen = degs*ha/7.5; + if (daylen<0.0001) {daylen = 0.0;} +// arctic winter // + +riset = 12.0 - 12.0 * ha/pi + tzone - longit/15.0 + equation/60.0; +settm = 12.0 + 12.0 * ha/pi + tzone - longit/15.0 + equation/60.0; +noont = riset + 12.0 * ha/pi; +altmax = 90.0 + delta * degs - latit; +// Correction for S HS suggested by David Smith +// to express altitude as degrees from the N horizon +if (latit < delta * degs) altmax = 180.0 - altmax; + +twam = riset - twx; // morning twilight begin +twpm = settm + twx; // evening twilight end + +if (riset > 24.0) riset-= 24.0; +if (settm > 24.0) settm-= 24.0; + +puts("\n Sunrise and set"); +puts("==============="); + +printf(" year : %d \n",(int)y); +printf(" month : %d \n",(int)m); +printf(" day : %d \n\n",(int)day); +printf("Days since Y2K : %d \n",(int)d); + +printf("Latitude : %3.1f, longitude: %3.1f, timezone: %3.1f \n",(float)latit,(float)longit,(float)tzone); +printf("Declination : %.2f \n",delta * degs); +printf("Daylength : "); showhrmn(daylen); puts(" hours \n"); +printf("Civil twilight: "); +showhrmn(twam); puts(""); +printf("Sunrise : "); +showhrmn(riset); puts(""); + +printf("Sun altitude "); +// Amendment by D. Smith +printf(" %.2f degr",altmax); +printf(latit>=0.0 ? " South" : " North"); +printf(" at noontime "); showhrmn(noont); puts(""); +printf("Sunset : "); +showhrmn(settm); puts(""); +printf("Civil twilight: "); +showhrmn(twpm); puts("\n"); + +return 0; +} + diff --git a/socket.cpp b/socket.cpp new file mode 100644 index 0000000..6ebaab1 --- /dev/null +++ b/socket.cpp @@ -0,0 +1,472 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "socket.h" +#include "XML.h" +#include +#include + +using namespace std; + +#define MAXBUF (1024) + +Socket::Socket( Config cfg, WeatherData *_wd ): + WeatherSink( cfg, _wd, "Socket Server") +{ + startMain(); +} + +Socket::~Socket() +{ + endMain(); +} + +void Socket::return404( FILE *fp, string url ) +{ + std::ostringstream oss; + oss << "" << endl; + oss << "404 Not Found" << endl; + oss << "" << endl; + oss << "

Not Found

" << endl; + oss << "

The request URL " << url << " was not found on the server.

" + << endl; + oss << "" << endl; + + fprintf(fp, "HTTP/1.1 404 Not Found\n"); + fprintf(fp, "Content-Type: text/html\n"); + fprintf(fp, "Content-Length: %d\n\n", oss.str().size()); + fprintf(fp, "%s", oss.str().c_str()); + fflush(fp); +} + +int Socket::outputXMLFile( FILE *outFp, const char *filename ) +{ + char buf[ 1024 ]; + struct stat statBuf; + + if ( stat( filename, &statBuf ) == -1 ) + { + dbg.printf(NOTICE, "error stat'ing %s: %s\n", filename, + strerror(errno)); + return -1; + } + int fileSize = statBuf.st_size; + int len=0; + + fprintf(outFp, "HTTP/1.1 200 OK\n"); + fprintf(outFp, "Content-type: text/xml\n" ); + fprintf(outFp, "Content-length: %d\n\n", fileSize ); + + int inFD = open(filename, O_RDONLY ); + if ( inFD != -1 ) + { + while( (len = read( inFD, buf, sizeof( buf ))) > 0 ) + { + fwrite( buf, len, 1, outFp ); + } + } + else + { + dbg.printf(NOTICE, "Error reading %s: %s\n", filename, strerror( errno + )); + return -1; + } + close( inFD ); + fflush(outFp); + return fileSize; +} + +string Socket::getCurrent() +{ + char timestamp[ 64 ]; + wd->now->RFC2822( timestamp, 64 ); + XML weather("weather", "version", "1.0"); + XML location("location"); + XML observation("observation"); + observation.addAttribute("timestamp", timestamp ); + + { + XML sensor( "sensor", wd->getValue(WeatherData::outsideTemp, 0 )); + sensor.addAttribute("type", "outsideTemperature" ); + sensor.addAttribute("units", "°F" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::outsideHumidity, 0 )); + sensor.addAttribute("type", "outsideHumidity" ); + sensor.addAttribute("units", "%" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::insideTemp, 0 )); + sensor.addAttribute("type", "insideTemperature" ); + sensor.addAttribute("units", "°F" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::insideHumidity, 0 )); + sensor.addAttribute("type", "insideHumidity" ); + sensor.addAttribute("units", "%" ); + observation.addNode( sensor ); + } + + { + double val = wd->getValue(WeatherData::heatIndex, 0 ); + XML sensor( "sensor" ); + if ( val != NO_VALUE ) + { + sensor.setValue( val ); + } + sensor.addAttribute("type", "heatIndex" ); + sensor.addAttribute("units", "°F" ); + observation.addNode( sensor ); + } + + { + double val = wd->getValue(WeatherData::humidex, 0 ); + XML sensor( "sensor" ); + if ( val != NO_VALUE ) + { + sensor.setValue( val ); + } + sensor.addAttribute("type", "humidex" ); + sensor.addAttribute("units", "°F" ); + observation.addNode( sensor ); + } + + { + double val = wd->getValue(WeatherData::windChill, 0 ); + XML sensor( "sensor" ); + if ( val != NO_VALUE ) + { + sensor.setValue( val ); + } + sensor.addAttribute("type", "windChill" ); + sensor.addAttribute("units", "°F" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::apparentTemp, 0 )); + sensor.addAttribute("type", "apparentTemp" ); + sensor.addAttribute("units", "°F" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::windDirection, 0 )); + sensor.addAttribute("type", "windDirection" ); + sensor.addAttribute("units", "°" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::windSpeed, 0 )); + sensor.addAttribute("type", "windSpeed" ); + sensor.addAttribute("units", "mph" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::averageWindDirection, 0 )); + sensor.addAttribute("type", "averageWindDirection" ); + sensor.addAttribute("units", "°" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::averageWindSpeed, 0 )); + sensor.addAttribute("type", "averageWindSpeed" ); + sensor.addAttribute("units", "mph" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::windGustDir, 0 )); + sensor.addAttribute("type", "windGustDirection" ); + sensor.addAttribute("units", "°" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::windGust, 0 )); + sensor.addAttribute("type", "windGustSpeed" ); + sensor.addAttribute("units", "mph" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::rainRate, 0 )/100.0); + sensor.addAttribute("type", "rainRate" ); + sensor.addAttribute("units", "in/hr" ); + observation.addNode( sensor ); + + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::instantRain, 0 )/100.0); + sensor.addAttribute("type", "instantRainRate" ); + sensor.addAttribute("units", "in/hr" ); + observation.addNode( sensor ); + + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::dailyRain, 0 )/100.0); + sensor.addAttribute("type", "dailyRain" ); + sensor.addAttribute("units", "in" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::rain24Hour, 0 )/100.0); + sensor.addAttribute("type", "rainLast24Hours" ); + sensor.addAttribute("units", "in" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::stationPressure, 0 )); + sensor.addAttribute("type", "stationPressure" ); + sensor.addAttribute("units", "inHg" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::SLP, 0 )); + sensor.addAttribute("type", "SLP" ); + sensor.addAttribute("units", "inHg" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::altimeter, 0 )); + sensor.addAttribute("type", "altimeter" ); + sensor.addAttribute("units", "inHg" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::dewPoint, 0 )); + sensor.addAttribute("type", "dewPoint" ); + sensor.addAttribute("units", "°F" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::solarRadiation, 0 )); + sensor.addAttribute("type", "solarRadiation" ); + sensor.addAttribute("units", "W/m^2" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::solarPercent, 0 )); + sensor.addAttribute("type", "solarRadiationPercent" ); + sensor.addAttribute("units", "%" ); + observation.addNode( sensor ); + } + + { + XML sensor( "sensor", wd->getValue(WeatherData::UV, 0 )); + sensor.addAttribute("type", "UV" ); + sensor.addAttribute("units", "index" ); + observation.addNode( sensor ); + } + + { + XML sensor( "trend" ); + sensor.setValue( wd->getTrend(WeatherData::altimeter ), 3, ios_base::showpos ); + sensor.addAttribute("type", "altimeter" ); + sensor.addAttribute("units", "inHg/hr" ); + observation.addNode( sensor ); + } + + { + XML sensor( "trend" ); + sensor.setValue(wd->getTrend(WeatherData::outsideTemp ), 3, ios_base::showpos ); + sensor.addAttribute("type", "outsideTemperature" ); + sensor.addAttribute("units", "°F/hr" ); + observation.addNode( sensor ); + } + + { + XML sensor( "trend" ); + sensor.setValue(wd->getTrend(WeatherData::outsideHumidity ), 3, ios_base::showpos ); + sensor.addAttribute("type", "outsideHumiduty" ); + sensor.addAttribute("units", "%/hr" ); + observation.addNode( sensor ); + } + + { + XML sensor( "trend" ); + sensor.setValue(wd->getTrend(WeatherData::dewPoint ), 3, ios_base::showpos ); + sensor.addAttribute("type", "dewPoint" ); + sensor.addAttribute("units", "°F/hr" ); + observation.addNode( sensor ); + } + + location.addAttribute("postal_code", cfg.getString("postal_code")); + location.addAttribute("city", cfg.getString("city")); + location.addAttribute("state", cfg.getString("state")); + location.addAttribute("latitude", cfg.getDouble("latitude"), 4); + location.addAttribute("longitude", cfg.getDouble("longitude"), 4); + + location.addNode( observation ); + weather.addNode( location ); + + return weather.str( 0, "weather.xsl" ); +} + +int Socket::processRequest( FILE *fp ) +{ + string content; + content = getCurrent(); + if ( fprintf(fp, "%s", content.c_str() ) == EOF ) + return EOF; + if ( fflush(fp) == EOF ) + return EOF; + return 0; + +} + +void Socket::worker( int client ) +{ + int timeout = 5; + FILE* fp = fdopen(client, "w"); + fd_set wfds; + FD_ZERO( &wfds ); + FD_SET( client, &wfds ); + struct timeval tv; + tv.tv_sec = timeout; + tv.tv_usec = 0; + + while ( select( client + 1, NULL, &wfds, NULL, &tv ) > 0 ) + { + if ( processRequest( fp ) == EOF ) + { + return; + } + + sleep(1); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + FD_ZERO( &wfds ); + FD_SET( client, &wfds ); + } + dbg.printf(NOTICE, "Closing socket connection\n"); + fclose(fp); + close(client); +} + +void Socket::main(void) +{ + struct sockaddr_in addr; + int sd, addrlen = sizeof(addr); + fd_set rfds; + int on = 1; + pthread_t workerId; + + if ( (sd = socket(PF_INET, SOCK_STREAM, 0)) < 0 ) + { + dbg.printf(EMERG, "Error creating socket: %s\n", strerror( errno )); + return; + } + if ( setsockopt( sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof( on )) < 0 ) + { + dbg.printf(EMERG, "setsockopt: %s\n", strerror( errno )); + return; + }; + addr.sin_family = AF_INET; + int port = cfg.getInteger("socket_port"); + if ( port == 0 ) + port = 6101; + dbg.printf(NOTICE, "Listening on port: %d\n", port ); + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + if ( bind(sd, (struct sockaddr*)&addr, addrlen) < 0 ) + { + dbg.printf(EMERG, "Error binding: %s\n", strerror( errno )); + return; + } + if ( listen(sd, 20) < 0 ) + { + dbg.printf(EMERG, "Error listening: %s\n", strerror( errno )); + return; + } + + + struct timeval tv; + while ( threadRunning ) + { + tv.tv_sec = 1; + tv.tv_usec = 0; + FD_ZERO( &rfds ); + FD_SET( sd, &rfds ); + + int rval = select ( sd +1, &rfds, NULL, NULL, &tv ); + + if ( rval <= 0 ) + { + if ( !threadRunning ) + { + dbg.printf(EMERG, "Leaving\n"); + break; + } + else + { + continue; + } + } + int client = accept(sd, (struct sockaddr*)&addr, (socklen_t*)&addrlen); + if ( client < 0 ) + { + dbg.printf(EMERG, "Error accept connection: %s\n", strerror( errno )); + } + else + { + dbg.printf(NOTICE, "Connected: %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + SocketThread_t *t = new SocketThread_t; + t->server = this; + t->client = client; + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED ); + pthread_create( &workerId, &attr, startSocketThread, (void*)t); + } + } + dbg.printf(DEBUG, "closing socket\n"); + close(sd); + return; +} + +void *startSocketThread( void *ptr ) +{ + Debug dbg("Socket Thread"); + SocketThread_t *p = (SocketThread_t *)ptr; + Socket *sock = p->server; + sock->worker( p->client ); + delete( p ); + return NULL; +} + diff --git a/socket.h b/socket.h new file mode 100644 index 0000000..e5ad174 --- /dev/null +++ b/socket.h @@ -0,0 +1,40 @@ +#ifndef __SOCKET_SERVER_H__ +#define __SOCKET_SERVER_H__ +#include "WeatherSink.h" + +#define MAX_SOCKET_BUF (1024) + + +class Socket:public WeatherSink +{ +public: + Socket( Config cfg, WeatherData *wd ); + ~Socket(); + + void worker( int client ); + void main( void ); + void newData() {}; + +private: + int processRequest( FILE *fp ); + std::string getCurrent(); + int outputXMLFile( FILE *outFp, const char *filename ); + void return404( FILE *fp, std::string url ); + +private: + char buffer[MAX_SOCKET_BUF]; +}; + +typedef struct +{ + Socket *server; + int client; +} SocketThread_t; + +extern "C" { + void *startSocketThread( void * ptr ); +}; + + +#endif // __SOCKET_SERVER_H__ + diff --git a/sql.cpp b/sql.cpp new file mode 100644 index 0000000..d1fc210 --- /dev/null +++ b/sql.cpp @@ -0,0 +1,906 @@ +#include "sql.h" + +const char *SQL_schema = "CREATE TABLE SCHEMA ( version INTEGER ); INSERT INTO \"SCHEMA\" VALUES(" DB_VERSION ");"; + +const char *dailyObsSchema = "CREATE TABLE dailyObs ( time INTEGER PRIMARY KEY," + "insideTemp FLOAT," + "insideHumidity FLOAT," + "outsideTemp FLOAT," + "outsideHumidity FLOAT," + "windSpeed FLOAT," + "windDirection FLOAT," + "windGust FLOAT," + "windGustDir FLOAT," + "rainRate FLOAT," + "rain24Hour FLOAT," + "dailyRain FLOAT," + "dailyET FLOAT," + "instantRain FLOAT," + "UV FLOAT," + "solarRadiation FLOAT," + "solarRadiationPercent FLOAT," + "barometricTrend TREND," + "rawBarometer FLOAT," + "SLP FLOAT," + "altimeter FLOAT," + "dewPoint FLOAT," + "heatIndex FLOAT," + "humidex FLOAT," + "windChill FLOAT," + "apparentTemp FLOAT," + "cloudHeight INT," + "extraTemp1 FLOAT," + "extraTemp2 FLOAT," + "extraTemp3 FLOAT," + "extraTemp4 FLOAT," + "extraTemp5 FLOAT," + "extraTemp6 FLOAT," + "extraTemp7 FLOAT," + "extraTemp8 FLOAT," + "extraHumidity1 FLOAT," + "extraHumidity2 FLOAT," + "extraHumidity3 FLOAT," + "extraHumidity4 FLOAT," + "extraHumidity5 FLOAT," + "extraHumidity6 FLOAT," + "extraHumidity7 FLOAT," + "extraHumidity8 FLOAT," + "soilTemp1 FLOAT," + "soilTemp2 FLOAT," + "soilTemp3 FLOAT," + "soilTemp4 FLOAT," + "soilTemp5 FLOAT," + "soilTemp6 FLOAT," + "soilTemp7 FLOAT," + "soilTemp8 FLOAT," + "soilMoisture1 FLOAT," + "soilMoisture2 FLOAT," + "soilMoisture3 FLOAT," + "soilMoisture4 FLOAT," + "soilMoisture5 FLOAT," + "soilMoisture6 FLOAT," + "soilMoisture7 FLOAT," + "soilMoisture8 FLOAT," + "leafTemp1 FLOAT," + "leafTemp2 FLOAT," + "leafTemp3 FLOAT," + "leafTemp4 FLOAT," + "leafTemp5 FLOAT," + "leafTemp6 FLOAT," + "leafTemp7 FLOAT," + "leafTemp8 FLOAT," + "leafWetness1 FLOAT," + "leafWetness2 FLOAT," + "leafWetness3 FLOAT," + "leafWetness4 FLOAT," + "leafWetness5 FLOAT," + "leafWetness6 FLOAT," + "leafWetness7 FLOAT," + "leafWetness8 FLOAT );"; + +const char *hourlySummarySchema = + "CREATE TABLE hourlySummary ( time INTEGER PRIMARY KEY," + "insideTempMin FLOAT," + "insideTempMinTime TIME," + "insideTempMax FLOAT," + "insideTempMaxTime TIME," + "insideTempAvg FLOAT," + "insideHumidityMin FLOAT," + "insideHumidityMinTime TIME," + "insideHumidityMax FLOAT," + "insideHumidityMaxTime TIME," + "insideHumidityAvg FLOAT," + "outsideTempMin FLOAT," + "outsideTempMinTime TIME," + "outsideTempMax FLOAT," + "outsideTempMaxTime TIME," + "outsideTempAvg FLOAT," + "outsideHumidityMin FLOAT," + "outsideHumidityMinTime TIME," + "outsideHumidityMax FLOAT," + "outsideHumidityMaxTime TIME," + "outsideHumidityAvg FLOAT," + "windSpeedMin FLOAT," + "windSpeedMinTime TIME," + "windSpeedMax FLOAT," + "windSpeedMaxTime TIME," + "windSpeedAvg FLOAT," + "windGustMin FLOAT," + "windGustMinTime TIME," + "windGustMax FLOAT," + "windGustMaxTime TIME," + "windGustAvg FLOAT," + "windDirectionAvg FLOAT," + "windGustDirAvg FLOAT," + "rainRateMin FLOAT," + "rainRateMinTime TIME," + "rainRateMax FLOAT," + "rainRateMaxTime TIME," + "rainRateAvg FLOAT," + "rain24HourMin FLOAT," + "rain24HourMinTime TIME," + "rain24HourMax FLOAT," + "rain24HourMaxTime TIME," + "rain24HourAvg FLOAT," + "dailyRain FLOAT," + "dailyET FLOAT," + "instantRainMax FLOAT," + "instantRainMaxTime TIME," + "UVMin FLOAT," + "UVMinTime TIME," + "UVMax FLOAT," + "UVMaxTime TIME," + "UVAvg FLOAT," + "solarRadiationMin FLOAT," + "solarRadiationMinTime TIME," + "solarRadiationMax FLOAT," + "solarRadiationMaxTime TIME," + "solarRadiationAvg FLOAT," + "solarRadiationPercentMin FLOAT," + "solarRadiationPercentMinTime TIME," + "solarRadiationPercentMax FLOAT," + "solarRadiationPercentMaxTime TIME," + "solarRadiationPercentAvg FLOAT," + "rawBarometerMin FLOAT," + "rawBarometerMinTime TIME," + "rawBarometerMax FLOAT," + "rawBarometerMaxTime TIME," + "rawBarometerAvg FLOAT," + "SLPMin FLOAT," + "SLPMinTime TIME," + "SLPMax FLOAT," + "SLPMaxTime TIME," + "SLPAvg FLOAT," + "altimeterMin FLOAT," + "altimeterMinTime TIME," + "altimeterMax FLOAT," + "altimeterMaxTime TIME," + "altimeterAvg FLOAT," + "heatIndexMin FLOAT," + "heatIndexMinTime TIME," + "heatIndexMax FLOAT," + "heatIndexMaxTime TIME," + "heatIndexAvg FLOAT," + "humidexMin FLOAT," + "humidexMinTime TIME," + "humidexMax FLOAT," + "humidexMaxTime TIME," + "humidexAvg FLOAT," + "windChillMin FLOAT," + "windChillMinTime TIME," + "windChillMax FLOAT," + "windChillMaxTime TIME," + "windChillAvg FLOAT," + "apparentTempMin FLOAT," + "apparentTempMinTime TIME," + "apparentTempMax FLOAT," + "apparentTempMaxTime TIME," + "apparentTempAvg FLOAT," + "cloudHeightMin FLOAT," + "cloudHeightMinTime TIME," + "cloudHeightMax FLOAT," + "cloudHeightMaxTime TIME," + "cloudHeightAvg FLOAT," + "dewPointMin FLOAT," + "dewPointMinTime TIME," + "dewPointMax FLOAT," + "dewPointMaxTime TIME," + "dewPointAvg FLOAT," + "extraTemp1Min FLOAT," + "extraTemp1MinTime TIME," + "extraTemp1Max FLOAT," + "extraTemp1MaxTime TIME," + "extraTemp1Avg FLOAT," + "extraTemp2Min FLOAT," + "extraTemp2MinTime TIME," + "extraTemp2Max FLOAT," + "extraTemp2MaxTime TIME," + "extraTemp2Avg FLOAT," + "extraTemp3Min FLOAT," + "extraTemp3MinTime TIME," + "extraTemp3Max FLOAT," + "extraTemp3MaxTime TIME," + "extraTemp3Avg FLOAT," + "extraTemp4Min FLOAT," + "extraTemp4MinTime TIME," + "extraTemp4Max FLOAT," + "extraTemp4MaxTime TIME," + "extraTemp4Avg FLOAT," + "extraTemp5Min FLOAT," + "extraTemp5MinTime TIME," + "extraTemp5Max FLOAT," + "extraTemp5MaxTime TIME," + "extraTemp5Avg FLOAT," + "extraTemp6Min FLOAT," + "extraTemp6MinTime TIME," + "extraTemp6Max FLOAT," + "extraTemp6MaxTime TIME," + "extraTemp6Avg FLOAT," + "extraTemp7Min FLOAT," + "extraTemp7MinTime TIME," + "extraTemp7Max FLOAT," + "extraTemp7MaxTime TIME," + "extraTemp7Avg FLOAT," + "extraTemp8Min FLOAT," + "extraTemp8MinTime TIME," + "extraTemp8Max FLOAT," + "extraTemp8MaxTime TIME," + "extraTemp8Avg FLOAT," + "extraHumidity1Min FLOAT," + "extraHumidity1MinTime TIME," + "extraHumidity1Max FLOAT," + "extraHumidity1MaxTime TIME," + "extraHumidity1Avg FLOAT," + "extraHumidity2Min FLOAT," + "extraHumidity2MinTime TIME," + "extraHumidity2Max FLOAT," + "extraHumidity2MaxTime TIME," + "extraHumidity2Avg FLOAT," + "extraHumidity3Min FLOAT," + "extraHumidity3MinTime TIME," + "extraHumidity3Max FLOAT," + "extraHumidity3MaxTime TIME," + "extraHumidity3Avg FLOAT," + "extraHumidity4Min FLOAT," + "extraHumidity4MinTime TIME," + "extraHumidity4Max FLOAT," + "extraHumidity4MaxTime TIME," + "extraHumidity4Avg FLOAT," + "extraHumidity5Min FLOAT," + "extraHumidity5MinTime TIME," + "extraHumidity5Max FLOAT," + "extraHumidity5MaxTime TIME," + "extraHumidity5Avg FLOAT," + "extraHumidity6Min FLOAT," + "extraHumidity6MinTime TIME," + "extraHumidity6Max FLOAT," + "extraHumidity6MaxTime TIME," + "extraHumidity6Avg FLOAT," + "extraHumidity7Min FLOAT," + "extraHumidity7MinTime TIME," + "extraHumidity7Max FLOAT," + "extraHumidity7MaxTime TIME," + "extraHumidity7Avg FLOAT," + "extraHumidity8Min FLOAT," + "extraHumidity8MinTime TIME," + "extraHumidity8Max FLOAT," + "extraHumidity8MaxTime TIME," + "extraHumidity8Avg FLOAT," + "soilTemp1Min FLOAT," + "soilTemp1MinTime TIME," + "soilTemp1Max FLOAT," + "soilTemp1MaxTime TIME," + "soilTemp1Avg FLOAT," + "soilTemp2Min FLOAT," + "soilTemp2MinTime TIME," + "soilTemp2Max FLOAT," + "soilTemp2MaxTime TIME," + "soilTemp2Avg FLOAT," + "soilTemp3Min FLOAT," + "soilTemp3MinTime TIME," + "soilTemp3Max FLOAT," + "soilTemp3MaxTime TIME," + "soilTemp3Avg FLOAT," + "soilTemp4Min FLOAT," + "soilTemp4MinTime TIME," + "soilTemp4Max FLOAT," + "soilTemp4MaxTime TIME," + "soilTemp4Avg FLOAT," + "soilTemp5Min FLOAT," + "soilTemp5MinTime TIME," + "soilTemp5Max FLOAT," + "soilTemp5MaxTime TIME," + "soilTemp5Avg FLOAT," + "soilTemp6Min FLOAT," + "soilTemp6MinTime TIME," + "soilTemp6Max FLOAT," + "soilTemp6MaxTime TIME," + "soilTemp6Avg FLOAT," + "soilTemp7Min FLOAT," + "soilTemp7MinTime TIME," + "soilTemp7Max FLOAT," + "soilTemp7MaxTime TIME," + "soilTemp7Avg FLOAT," + "soilTemp8Min FLOAT," + "soilTemp8MinTime TIME," + "soilTemp8Max FLOAT," + "soilTemp8MaxTime TIME," + "soilTemp8Avg FLOAT," + "soilMoisture1Min FLOAT," + "soilMoisture1MinTime TIME," + "soilMoisture1Max FLOAT," + "soilMoisture1MaxTime TIME," + "soilMoisture1Avg FLOAT," + "soilMoisture2Min FLOAT," + "soilMoisture2MinTime TIME," + "soilMoisture2Max FLOAT," + "soilMoisture2MaxTime TIME," + "soilMoisture2Avg FLOAT," + "soilMoisture3Min FLOAT," + "soilMoisture3MinTime TIME," + "soilMoisture3Max FLOAT," + "soilMoisture3MaxTime TIME," + "soilMoisture3Avg FLOAT," + "soilMoisture4Min FLOAT," + "soilMoisture4MinTime TIME," + "soilMoisture4Max FLOAT," + "soilMoisture4MaxTime TIME," + "soilMoisture4Avg FLOAT," + "soilMoisture5Min FLOAT," + "soilMoisture5MinTime TIME," + "soilMoisture5Max FLOAT," + "soilMoisture5MaxTime TIME," + "soilMoisture5Avg FLOAT," + "soilMoisture6Min FLOAT," + "soilMoisture6MinTime TIME," + "soilMoisture6Max FLOAT," + "soilMoisture6MaxTime TIME," + "soilMoisture6Avg FLOAT," + "soilMoisture7Min FLOAT," + "soilMoisture7MinTime TIME," + "soilMoisture7Max FLOAT," + "soilMoisture7MaxTime TIME," + "soilMoisture7Avg FLOAT," + "soilMoisture8Min FLOAT," + "soilMoisture8MinTime TIME," + "soilMoisture8Max FLOAT," + "soilMoisture8MaxTime TIME," + "soilMoisture8Avg FLOAT," + "leafTemperature1Min FLOAT," + "leafTemperature1MinTime TIME," + "leafTemperature1Max FLOAT," + "leafTemperature1MaxTime TIME," + "leafTemperature1Avg FLOAT," + "leafTemperature2Min FLOAT," + "leafTemperature2MinTime TIME," + "leafTemperature2Max FLOAT," + "leafTemperature2MaxTime TIME," + "leafTemperature2Avg FLOAT," + "leafTemperature3Min FLOAT," + "leafTemperature3MinTime TIME," + "leafTemperature3Max FLOAT," + "leafTemperature3MaxTime TIME," + "leafTemperature3Avg FLOAT," + "leafTemperature4Min FLOAT," + "leafTemperature4MinTime TIME," + "leafTemperature4Max FLOAT," + "leafTemperature4MaxTime TIME," + "leafTemperature4Avg FLOAT," + "leafTemperature5Min FLOAT," + "leafTemperature5MinTime TIME," + "leafTemperature5Max FLOAT," + "leafTemperature5MaxTime TIME," + "leafTemperature5Avg FLOAT," + "leafTemperature6Min FLOAT," + "leafTemperature6MinTime TIME," + "leafTemperature6Max FLOAT," + "leafTemperature6MaxTime TIME," + "leafTemperature6Avg FLOAT," + "leafTemperature7Min FLOAT," + "leafTemperature7MinTime TIME," + "leafTemperature7Max FLOAT," + "leafTemperature7MaxTime TIME," + "leafTemperature7Avg FLOAT," + "leafTemperature8Min FLOAT," + "leafTemperature8MinTime TIME," + "leafTemperature8Max FLOAT," + "leafTemperature8MaxTime TIME," + "leafTemperature8Avg FLOAT," + "leafWetness1Min FLOAT," + "leafWetness1MinTime TIME," + "leafWetness1Max FLOAT," + "leafWetness1MaxTime TIME," + "leafWetness1Avg FLOAT," + "leafWetness2Min FLOAT," + "leafWetness2MinTime TIME," + "leafWetness2Max FLOAT," + "leafWetness2MaxTime TIME," + "leafWetness2Avg FLOAT," + "leafWetness3Min FLOAT," + "leafWetness3MinTime TIME," + "leafWetness3Max FLOAT," + "leafWetness3MaxTime TIME," + "leafWetness3Avg FLOAT," + "leafWetness4Min FLOAT," + "leafWetness4MinTime TIME," + "leafWetness4Max FLOAT," + "leafWetness4MaxTime TIME," + "leafWetness4Avg FLOAT," + "leafWetness5Min FLOAT," + "leafWetness5MinTime TIME," + "leafWetness5Max FLOAT," + "leafWetness5MaxTime TIME," + "leafWetness5Avg FLOAT," + "leafWetness6Min FLOAT," + "leafWetness6MinTime TIME," + "leafWetness6Max FLOAT," + "leafWetness6MaxTime TIME," + "leafWetness6Avg FLOAT," + "leafWetness7Min FLOAT," + "leafWetness7MinTime TIME," + "leafWetness7Max FLOAT," + "leafWetness7MaxTime TIME," + "leafWetness7Avg FLOAT," + "leafWetness8Min FLOAT," + "leafWetness8MinTime TIME," + "leafWetness8Max FLOAT," + "leafWetness8MaxTime TIME," + "leafWetness8Avg FLOAT );"; + +const char *windSchema = "create table windData ( time INTEGEER PRIMARY KEY," + "speed FLOAT," + "dirSin FLOAT," + "dirCos FLOAT);"; + +const char *rainSchema = "create table rainData ( time INTEGEER PRIMARY KEY," + "amount FLOAT);"; + +const char *dailySummarySchema = + "CREATE TABLE dailySummary ( time INTEGER PRIMARY KEY," + "insideTempMin FLOAT," + "insideTempMinTime TIME," + "insideTempMax FLOAT," + "insideTempMaxTime TIME," + "insideTempAvg FLOAT," + "insideHumidityMin FLOAT," + "insideHumidityMinTime TIME," + "insideHumidityMax FLOAT," + "insideHumidityMaxTime TIME," + "insideHumidityAvg FLOAT," + "outsideTempMin FLOAT," + "outsideTempMinTime TIME," + "outsideTempMax FLOAT," + "outsideTempMaxTime TIME," + "outsideTempAvg FLOAT," + "outsideHumidityMin FLOAT," + "outsideHumidityMinTime TIME," + "outsideHumidityMax FLOAT," + "outsideHumidityMaxTime TIME," + "outsideHumidityAvg FLOAT," + "windSpeedMin FLOAT," + "windSpeedMinTime TIME," + "windSpeedMax FLOAT," + "windSpeedMaxTime TIME," + "windSpeedAvg FLOAT," + "windGustMin FLOAT," + "windGustMinTime TIME," + "windGustMax FLOAT," + "windGustMaxTime TIME," + "windGustAvg FLOAT," + "windDirectionAvg FLOAT," + "windGustDirAvg FLOAT," + "rainRateMin FLOAT," + "rainRateMinTime TIME," + "rainRateMax FLOAT," + "rainRateMaxTime TIME," + "rainRateAvg FLOAT," + "rain24HourMin FLOAT," + "rain24HourMinTime TIME," + "rain24HourMax FLOAT," + "rain24HourMaxTime TIME," + "rain24HourAvg FLOAT," + "instantRainMax FLOAT," + "instantRainMaxTime TIME," + "dailyRain FLOAT," + "dailyET FLOAT," + "UVMin FLOAT," + "UVMinTime TIME," + "UVMax FLOAT," + "UVMaxTime TIME," + "UVAvg FLOAT," + "solarRadiationMin FLOAT," + "solarRadiationMinTime TIME," + "solarRadiationMax FLOAT," + "solarRadiationMaxTime TIME," + "solarRadiationAvg FLOAT," + "solarRadiationPercentMin FLOAT," + "solarRadiationPercentMinTime TIME," + "solarRadiationPercentMax FLOAT," + "solarRadiationPercentMaxTime TIME," + "solarRadiationPercentAvg FLOAT," + "rawBarometerMin FLOAT," + "rawBarometerMinTime TIME," + "rawBarometerMax FLOAT," + "rawBarometerMaxTime TIME," + "rawBarometerAvg FLOAT," + "SLPMin FLOAT," + "SLPMinTime TIME," + "SLPMax FLOAT," + "SLPMaxTime TIME," + "SLPAvg FLOAT," + "altimeterMin FLOAT," + "altimeterMinTime TIME," + "altimeterMax FLOAT," + "altimeterMaxTime TIME," + "altimeterAvg FLOAT," + "heatIndexMin FLOAT," + "heatIndexMinTime TIME," + "heatIndexMax FLOAT," + "heatIndexMaxTime TIME," + "heatIndexAvg FLOAT," + "humidexMin FLOAT," + "humidexMinTime TIME," + "humidexMax FLOAT," + "humidexMaxTime TIME," + "humidexAvg FLOAT," + "windChillMin FLOAT," + "windChillMinTime TIME," + "windChillMax FLOAT," + "windChillMaxTime TIME," + "windChillAvg FLOAT," + "apparentTempMin FLOAT," + "apparentTempMinTime TIME," + "apparentTempMax FLOAT," + "apparentTempMaxTime TIME," + "apparentTempAvg FLOAT," + "cloudHeightMin FLOAT," + "cloudHeightMinTime TIME," + "cloudHeightMax FLOAT," + "cloudHeightMaxTime TIME," + "cloudHeightAvg FLOAT," + "dewPointMin FLOAT," + "dewPointMinTime TIME," + "dewPointMax FLOAT," + "dewPointMaxTime TIME," + "dewPointAvg FLOAT," + "extraTemp1Min FLOAT," + "extraTemp1MinTime TIME," + "extraTemp1Max FLOAT," + "extraTemp1MaxTime TIME," + "extraTemp1Avg FLOAT," + "extraTemp2Min FLOAT," + "extraTemp2MinTime TIME," + "extraTemp2Max FLOAT," + "extraTemp2MaxTime TIME," + "extraTemp2Avg FLOAT," + "extraTemp3Min FLOAT," + "extraTemp3MinTime TIME," + "extraTemp3Max FLOAT," + "extraTemp3MaxTime TIME," + "extraTemp3Avg FLOAT," + "extraTemp4Min FLOAT," + "extraTemp4MinTime TIME," + "extraTemp4Max FLOAT," + "extraTemp4MaxTime TIME," + "extraTemp4Avg FLOAT," + "extraTemp5Min FLOAT," + "extraTemp5MinTime TIME," + "extraTemp5Max FLOAT," + "extraTemp5MaxTime TIME," + "extraTemp5Avg FLOAT," + "extraTemp6Min FLOAT," + "extraTemp6MinTime TIME," + "extraTemp6Max FLOAT," + "extraTemp6MaxTime TIME," + "extraTemp6Avg FLOAT," + "extraTemp7Min FLOAT," + "extraTemp7MinTime TIME," + "extraTemp7Max FLOAT," + "extraTemp7MaxTime TIME," + "extraTemp7Avg FLOAT," + "extraTemp8Min FLOAT," + "extraTemp8MinTime TIME," + "extraTemp8Max FLOAT," + "extraTemp8MaxTime TIME," + "extraTemp8Avg FLOAT," + "extraHumidity1Min FLOAT," + "extraHumidity1MinTime TIME," + "extraHumidity1Max FLOAT," + "extraHumidity1MaxTime TIME," + "extraHumidity1Avg FLOAT," + "extraHumidity2Min FLOAT," + "extraHumidity2MinTime TIME," + "extraHumidity2Max FLOAT," + "extraHumidity2MaxTime TIME," + "extraHumidity2Avg FLOAT," + "extraHumidity3Min FLOAT," + "extraHumidity3MinTime TIME," + "extraHumidity3Max FLOAT," + "extraHumidity3MaxTime TIME," + "extraHumidity3Avg FLOAT," + "extraHumidity4Min FLOAT," + "extraHumidity4MinTime TIME," + "extraHumidity4Max FLOAT," + "extraHumidity4MaxTime TIME," + "extraHumidity4Avg FLOAT," + "extraHumidity5Min FLOAT," + "extraHumidity5MinTime TIME," + "extraHumidity5Max FLOAT," + "extraHumidity5MaxTime TIME," + "extraHumidity5Avg FLOAT," + "extraHumidity6Min FLOAT," + "extraHumidity6MinTime TIME," + "extraHumidity6Max FLOAT," + "extraHumidity6MaxTime TIME," + "extraHumidity6Avg FLOAT," + "extraHumidity7Min FLOAT," + "extraHumidity7MinTime TIME," + "extraHumidity7Max FLOAT," + "extraHumidity7MaxTime TIME," + "extraHumidity7Avg FLOAT," + "extraHumidity8Min FLOAT," + "extraHumidity8MinTime TIME," + "extraHumidity8Max FLOAT," + "extraHumidity8MaxTime TIME," + "extraHumidity8Avg FLOAT," + "soilTemp1Min FLOAT," + "soilTemp1MinTime TIME," + "soilTemp1Max FLOAT," + "soilTemp1MaxTime TIME," + "soilTemp1Avg FLOAT," + "soilTemp2Min FLOAT," + "soilTemp2MinTime TIME," + "soilTemp2Max FLOAT," + "soilTemp2MaxTime TIME," + "soilTemp2Avg FLOAT," + "soilTemp3Min FLOAT," + "soilTemp3MinTime TIME," + "soilTemp3Max FLOAT," + "soilTemp3MaxTime TIME," + "soilTemp3Avg FLOAT," + "soilTemp4Min FLOAT," + "soilTemp4MinTime TIME," + "soilTemp4Max FLOAT," + "soilTemp4MaxTime TIME," + "soilTemp4Avg FLOAT," + "soilTemp5Min FLOAT," + "soilTemp5MinTime TIME," + "soilTemp5Max FLOAT," + "soilTemp5MaxTime TIME," + "soilTemp5Avg FLOAT," + "soilTemp6Min FLOAT," + "soilTemp6MinTime TIME," + "soilTemp6Max FLOAT," + "soilTemp6MaxTime TIME," + "soilTemp6Avg FLOAT," + "soilTemp7Min FLOAT," + "soilTemp7MinTime TIME," + "soilTemp7Max FLOAT," + "soilTemp7MaxTime TIME," + "soilTemp7Avg FLOAT," + "soilTemp8Min FLOAT," + "soilTemp8MinTime TIME," + "soilTemp8Max FLOAT," + "soilTemp8MaxTime TIME," + "soilTemp8Avg FLOAT," + "soilMoisture1Min FLOAT," + "soilMoisture1MinTime TIME," + "soilMoisture1Max FLOAT," + "soilMoisture1MaxTime TIME," + "soilMoisture1Avg FLOAT," + "soilMoisture2Min FLOAT," + "soilMoisture2MinTime TIME," + "soilMoisture2Max FLOAT," + "soilMoisture2MaxTime TIME," + "soilMoisture2Avg FLOAT," + "soilMoisture3Min FLOAT," + "soilMoisture3MinTime TIME," + "soilMoisture3Max FLOAT," + "soilMoisture3MaxTime TIME," + "soilMoisture3Avg FLOAT," + "soilMoisture4Min FLOAT," + "soilMoisture4MinTime TIME," + "soilMoisture4Max FLOAT," + "soilMoisture4MaxTime TIME," + "soilMoisture4Avg FLOAT," + "soilMoisture5Min FLOAT," + "soilMoisture5MinTime TIME," + "soilMoisture5Max FLOAT," + "soilMoisture5MaxTime TIME," + "soilMoisture5Avg FLOAT," + "soilMoisture6Min FLOAT," + "soilMoisture6MinTime TIME," + "soilMoisture6Max FLOAT," + "soilMoisture6MaxTime TIME," + "soilMoisture6Avg FLOAT," + "soilMoisture7Min FLOAT," + "soilMoisture7MinTime TIME," + "soilMoisture7Max FLOAT," + "soilMoisture7MaxTime TIME," + "soilMoisture7Avg FLOAT," + "soilMoisture8Min FLOAT," + "soilMoisture8MinTime TIME," + "soilMoisture8Max FLOAT," + "soilMoisture8MaxTime TIME," + "soilMoisture8Avg FLOAT," + "leafTemperature1Min FLOAT," + "leafTemperature1MinTime TIME," + "leafTemperature1Max FLOAT," + "leafTemperature1MaxTime TIME," + "leafTemperature1Avg FLOAT," + "leafTemperature2Min FLOAT," + "leafTemperature2MinTime TIME," + "leafTemperature2Max FLOAT," + "leafTemperature2MaxTime TIME," + "leafTemperature2Avg FLOAT," + "leafTemperature3Min FLOAT," + "leafTemperature3MinTime TIME," + "leafTemperature3Max FLOAT," + "leafTemperature3MaxTime TIME," + "leafTemperature3Avg FLOAT," + "leafTemperature4Min FLOAT," + "leafTemperature4MinTime TIME," + "leafTemperature4Max FLOAT," + "leafTemperature4MaxTime TIME," + "leafTemperature4Avg FLOAT," + "leafTemperature5Min FLOAT," + "leafTemperature5MinTime TIME," + "leafTemperature5Max FLOAT," + "leafTemperature5MaxTime TIME," + "leafTemperature5Avg FLOAT," + "leafTemperature6Min FLOAT," + "leafTemperature6MinTime TIME," + "leafTemperature6Max FLOAT," + "leafTemperature6MaxTime TIME," + "leafTemperature6Avg FLOAT," + "leafTemperature7Min FLOAT," + "leafTemperature7MinTime TIME," + "leafTemperature7Max FLOAT," + "leafTemperature7MaxTime TIME," + "leafTemperature7Avg FLOAT," + "leafTemperature8Min FLOAT," + "leafTemperature8MinTime TIME," + "leafTemperature8Max FLOAT," + "leafTemperature8MaxTime TIME," + "leafTemperature8Avg FLOAT," + "leafWetness1Min FLOAT," + "leafWetness1MinTime TIME," + "leafWetness1Max FLOAT," + "leafWetness1MaxTime TIME," + "leafWetness1Avg FLOAT," + "leafWetness2Min FLOAT," + "leafWetness2MinTime TIME," + "leafWetness2Max FLOAT," + "leafWetness2MaxTime TIME," + "leafWetness2Avg FLOAT," + "leafWetness3Min FLOAT," + "leafWetness3MinTime TIME," + "leafWetness3Max FLOAT," + "leafWetness3MaxTime TIME," + "leafWetness3Avg FLOAT," + "leafWetness4Min FLOAT," + "leafWetness4MinTime TIME," + "leafWetness4Max FLOAT," + "leafWetness4MaxTime TIME," + "leafWetness4Avg FLOAT," + "leafWetness5Min FLOAT," + "leafWetness5MinTime TIME," + "leafWetness5Max FLOAT," + "leafWetness5MaxTime TIME," + "leafWetness5Avg FLOAT," + "leafWetness6Min FLOAT," + "leafWetness6MinTime TIME," + "leafWetness6Max FLOAT," + "leafWetness6MaxTime TIME," + "leafWetness6Avg FLOAT," + "leafWetness7Min FLOAT," + "leafWetness7MinTime TIME," + "leafWetness7Max FLOAT," + "leafWetness7MaxTime TIME," + "leafWetness7Avg FLOAT," + "leafWetness8Min FLOAT," + "leafWetness8MinTime TIME," + "leafWetness8Max FLOAT," + "leafWetness8MaxTime TIME," + "leafWetness8Avg FLOAT," + "summaryType ENUM" // 0 = daily, 1 = daytime, 2 = nighttime + ");"; + +const char *update1to2 = "alter table dailyObs ADD COLUMN heatIndex FLOAT;" + "alter table dailyObs ADD COLUMN humidex FLOAT;" + "alter table dailyObs ADD COLUMN windChill FLOAT;" + + "alter table hourlySummary ADD COLUMN heatIndexMin FLOAT;" + "alter table hourlySummary ADD COLUMN heatIndexMinTime FLOAT;" + "alter table hourlySummary ADD COLUMN heatIndexMax FLOAT;" + "alter table hourlySummary ADD COLUMN heatIndexMaxTime FLOAT;" + "alter table hourlySummary ADD COLUMN heatIndexAvg FLOAT;" + "alter table hourlySummary ADD COLUMN humidexMin FLOAT;" + "alter table hourlySummary ADD COLUMN humidexMinTime FLOAT;" + "alter table hourlySummary ADD COLUMN humidexMax FLOAT;" + "alter table hourlySummary ADD COLUMN humidexMaxTime FLOAT;" + "alter table hourlySummary ADD COLUMN humidexAvg FLOAT;" + "alter table hourlySummary ADD COLUMN windChillMin FLOAT;" + "alter table hourlySummary ADD COLUMN windChillMinTime FLOAT;" + "alter table hourlySummary ADD COLUMN windChillMax FLOAT;" + "alter table hourlySummary ADD COLUMN windChillMaxTime FLOAT;" + "alter table hourlySummary ADD COLUMN windChillAvg FLOAT;" + + "alter table dailySummary ADD COLUMN heatIndexMin FLOAT;" + "alter table dailySummary ADD COLUMN heatIndexMinTime FLOAT;" + "alter table dailySummary ADD COLUMN heatIndexMax FLOAT;" + "alter table dailySummary ADD COLUMN heatIndexMaxTime FLOAT;" + "alter table dailySummary ADD COLUMN heatIndexAvg FLOAT;" + "alter table dailySummary ADD COLUMN humidexMin FLOAT;" + "alter table dailySummary ADD COLUMN humidexMinTime FLOAT;" + "alter table dailySummary ADD COLUMN humidexMax FLOAT;" + "alter table dailySummary ADD COLUMN humidexMaxTime FLOAT;" + "alter table dailySummary ADD COLUMN humidexAvg FLOAT;" + "alter table dailySummary ADD COLUMN windChillMin FLOAT;" + "alter table dailySummary ADD COLUMN windChillMinTime FLOAT;" + "alter table dailySummary ADD COLUMN windChillMax FLOAT;" + "alter table dailySummary ADD COLUMN windChillMaxTime FLOAT;" + "alter table dailySummary ADD COLUMN windChillAvg FLOAT;" + + "update schema set version = 2;" + "VACUUM"; + + +const char *update2to3 = "alter table dailyObs ADD COLUMN apparentTemp FLOAT;" + "alter table dailyObs ADD COLUMN cloudHeight FLOAT;" + + "alter table hourlySummary ADD COLUMN apparentTempMin FLOAT;" + "alter table hourlySummary ADD COLUMN apparentTempMinTime FLOAT;" + "alter table hourlySummary ADD COLUMN apparentTempMax FLOAT;" + "alter table hourlySummary ADD COLUMN apparentTempMaxTime FLOAT;" + "alter table hourlySummary ADD COLUMN apparentTempAvg FLOAT;" + "alter table hourlySummary ADD COLUMN cloudHeightMin FLOAT;" + "alter table hourlySummary ADD COLUMN cloudHeightMinTime FLOAT;" + "alter table hourlySummary ADD COLUMN cloudHeightMax FLOAT;" + "alter table hourlySummary ADD COLUMN cloudHeightMaxTime FLOAT;" + "alter table hourlySummary ADD COLUMN cloudHeightAvg FLOAT;" + + "alter table dailySummary ADD COLUMN apparentTempMin FLOAT;" + "alter table dailySummary ADD COLUMN apparentTempMinTime FLOAT;" + "alter table dailySummary ADD COLUMN apparentTempMax FLOAT;" + "alter table dailySummary ADD COLUMN apparentTempMaxTime FLOAT;" + "alter table dailySummary ADD COLUMN apparentTempAvg FLOAT;" + "alter table dailySummary ADD COLUMN cloudHeightMin FLOAT;" + "alter table dailySummary ADD COLUMN cloudHeightMinTime FLOAT;" + "alter table dailySummary ADD COLUMN cloudHeightMax FLOAT;" + "alter table dailySummary ADD COLUMN cloudHeightMaxTime FLOAT;" + "alter table dailySummary ADD COLUMN cloudHeightAvg FLOAT;" + + "update schema set version = 3;" + "VACUUM"; + +const char *update3to4 = "alter table dailyObs ADD COLUMN windGustDir FLOAT;" + "alter table hourlySummary ADD COLUMN windGustDirAvg FLOAT;" + "alter table dailySummary ADD COLUMN windGustDirAvg FLOAT;" + "update schema set version = 4;" + "VACUUM"; + +const char *update4to5 = "create table windData ( time INTEGEER PRIMARY KEY," + "speed FLOAT," + "dirSin FLOAT," + "dirCos FLOAT);" + "update schema set version = 5;" + "VACUUM"; + +const char *update5to6 = "create table rainData ( time INTEGEER PRIMARY KEY," + "amount FLOAT);" + "update schema set version = 6;" + "VACUUM"; + +const char *update6to7 = + "alter table dailyObs ADD COLUMN rain24Hour FLOAT;" + + "alter table hourlySummary ADD COLUMN rain24HourMin FLOAT;" + "alter table hourlySummary ADD COLUMN rain24HourMinTime TIME;" + "alter table hourlySummary ADD COLUMN rain24HourMax FLOAT;" + "alter table hourlySummary ADD COLUMN rain24HourMaxTime TIME;" + "alter table hourlySummary ADD COLUMN rain24HourAvg FLOAT;" + + "alter table dailySummary ADD COLUMN rain24HourMin FLOAT;" + "alter table dailySummary ADD COLUMN rain24HourMinTime TIME;" + "alter table dailySummary ADD COLUMN rain24HourMax FLOAT;" + "alter table dailySummary ADD COLUMN rain24HourMaxTime TIME;" + "alter table dailySummary ADD COLUMN rain24HourAvg FLOAT;" + "update schema set version = 7;" + "VACUUM"; + +const char *update7to8 = + "alter table dailySummary ADD COLUMN summaryType ENUM;" + "update schema set version = 8;" + "update dailySummary set summaryType = 0;" + "VACUUM"; + +const char *update8to9 = + "alter table dailyObs ADD COLUMN instantRain FLOAT;" + + "alter table hourlySummary ADD COLUMN instantRainMax FLOAT;" + "alter table hourlySummary ADD COLUMN instantRainMaxTime TIME;" + "alter table dailySummary ADD COLUMN instantRainMax FLOAT;" + "alter table dailySummary ADD COLUMN instantRainMaxTime TIME;" + "update schema set version = 9;" + "VACUUM"; diff --git a/sql.h b/sql.h new file mode 100644 index 0000000..21f60dd --- /dev/null +++ b/sql.h @@ -0,0 +1,23 @@ +#ifndef __SQL_H__ +#define __SQL_H__ + +#define DB_VERSION "9" + +extern const char *SQL_schema; +extern const char *dailySummarySchema; +extern const char *hourlySummarySchema; +extern const char *dailyObsSchema; +extern const char *windSchema; +extern const char *rainSchema; +extern const char *nighttimeObsSchema; +extern const char *daytimeObsSchema; + +extern const char *update1to2; +extern const char *update2to3; +extern const char *update3to4; +extern const char *update4to5; +extern const char *update5to6; +extern const char *update6to7; +extern const char *update7to8; +extern const char *update8to9; +#endif // __SQL_H__ diff --git a/test.cpp b/test.cpp new file mode 100644 index 0000000..79d0776 --- /dev/null +++ b/test.cpp @@ -0,0 +1,72 @@ +#include +#include "utils.h" +#include +#include +#include +#include +#include "gethostbyname_r.h" +#include +#include "VantagePro.h" +#include +#include +#include +#include "XML.h" +#include +#include +#include +#include "Database.h" +#include "Now.h" +#include "Almanac.h" +#include "Config.h" +#include + + +int main() +{ + time_t t = time(NULL); + Config cfg("test.cfg"); + Debug dbg("main"); + Almanac today( cfg, t); + + //printf("%f\n", getJulianDay( 29, 1, 111, 0, 0, 0 )); + + char buf[64]; + struct tm sunriseTm; + localtime_r( &today.sunrise, &sunriseTm ); + strftime( buf, 64, "%a, %d %b %Y %T %Z", &sunriseTm ); + printf("sunrise: %s\n", buf ); + + struct tm sunsetTm; + localtime_r( &today.sunset, &sunsetTm ); + strftime( buf, 64, "%a, %d %b %Y %T %Z", &sunsetTm ); + printf("sunset: %s\n", buf ); + + printf("length of day: %02d:%02d:%02d\n", + today.lengthOfDay/3600, + (int)((today.lengthOfDay%3600)/60.0), + (int)((today.lengthOfDay%3600)%60)); + //dbg.printf(EMERG, "end\n"); + //alm.setNewTime( t -86400 ); + //alm.setNewTime( 224769600 ); + //alm.setNewTime( t + 86400 ); + + if ( tomorrow.lengthOfDay < today.lengthOfDay ) + { + printf( "Tomorrow will be %d seconds shorter than today\n", + today.lengthOfDay - tomorrow.lengthOfDay ); + } + else if ( tomorrow.lengthOfDay > today.lengthOfDay ) + { + printf( "Tomorrow will be %d seconds longer than today\n", + tomorrow.lengthOfDay - today.lengthOfDay ); + } + else + { + printf( "Tomorrow will be same length as today\n"); + } + return 0; + +} + + + diff --git a/testReader.cpp b/testReader.cpp new file mode 100644 index 0000000..9fe21ea --- /dev/null +++ b/testReader.cpp @@ -0,0 +1,47 @@ +#include "testReader.h" +#include "utils.h" +#include "units.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "WeatherSource.h" + +TestReader::TestReader( Config &cfg ): + dbg("TestReader") +{ + if ( cfg.getInteger("test_only") != 1 ) + { + printf("Cannot use testReader except in TEST_ONLY mode!\n"); + exit(0); + } +} + +bool TestReader::read( ) +{ + wd->setValue(WeatherData::SLP, 30.12 ); + wd->setValue(WeatherData::insideTemp, 68.0 ); + wd->setValue(WeatherData::insideHumidity, 35 ); + + wd->setValue(WeatherData::outsideTemp, 54.1 ); + wd->setValue(WeatherData::outsideHumidity, 88 ); + + wd->setValue(WeatherData::windSpeed, 12.1 ); + wd->setValue(WeatherData::windDirection, 173 ); + + wd->setValue(WeatherData::dailyRain, .01 ); + + wd->setValue(WeatherData::dailyET, .03 ); + + wd->setValue(WeatherData::solarRadiation, 1023 ); + wd->setValue(WeatherData::UV, 7.1 ); + + return true; +} + diff --git a/testReader.h b/testReader.h new file mode 100644 index 0000000..a439a1b --- /dev/null +++ b/testReader.h @@ -0,0 +1,27 @@ +#ifndef __TEST_READER_H__ +#define __TEST_READER_H__ + +#include "types.h" +#include "Debug.h" +#include "WeatherSource.h" + +#define CR 0x0D +#define LF 0x0A +#define ACK 0x06 +#define NAK 0x21 +#define CANCEL 0x18 + +// Forward declarations + +class TestReader:public WeatherSource +{ +public: + TestReader( Config &cfg ); + + bool read( ); +private: + Debug dbg; + +}; + +#endif // __TEST_READER_H__ diff --git a/testWriter.cpp b/testWriter.cpp new file mode 100644 index 0000000..df976d9 --- /dev/null +++ b/testWriter.cpp @@ -0,0 +1,226 @@ +#include "testWriter.h" +#include "time.h" +#include +#include +#include +#include +#include +#include +#include + + +/* Curses info here: + * http://www.cs.utk.edu/~vose/c-stuff/ncurses.html + */ + +int TestWriter::winch = 0; + +static void testWriterSigWinchHandler( int sig ) +{ + (void) sig; + TestWriter::winch = 1; +} + +void TestWriter::setupWindow() +{ + int h,w; + mainWin = initscr(); + getmaxyx(stdscr, h, w); + noecho(); + cbreak(); + nodelay( mainWin, TRUE ); + refresh(); + titleWin = newwin(3,w,0,0); + dataWin = newwin(h-3,w,3,0); + getmaxyx( titleWin, h, w ); + box( titleWin, ACS_VLINE, ACS_HLINE ); + box( dataWin, ACS_VLINE, ACS_HLINE ); + wrefresh(titleWin); + wrefresh(dataWin); + wrefresh( mainWin ); +} + +void TestWriter::teardownWindow() +{ + endwin(); +} + +TestWriter::TestWriter( Config cfg, WeatherData *_wd ): + WeatherSink( cfg, _wd, "testWriter" ) +{ + signal( SIGWINCH, testWriterSigWinchHandler ); + setupWindow(); + pthread_mutex_init( ¬ify, NULL ); + pthread_mutex_lock( ¬ify ); + data.cardinal = ""; + data.gustCardinal = ""; + data.instantCardinal = ""; + startMain(); +} + +TestWriter::~TestWriter() +{ + endMain(); + clear(); + refresh(); + delwin(mainWin); + delwin(titleWin); + delwin(dataWin); + teardownWindow(); + fflush(stdout); +} + +void TestWriter::newData() +{ + pthread_mutex_unlock( ¬ify ); +} + +void TestWriter::getData() +{ + const char *cardinals[] = { "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N" }; + data.now = wd->now->unixtime(); + data.outsideTemp = wd->getValue( WeatherData::outsideTemp, 0 ); + data.outsideHumidity = wd->getValue( WeatherData::outsideHumidity, 0 ); + data.outsideTempTrend = wd->getTrend( WeatherData::outsideTemp ); + data.outsideHumidityTrend = wd->getTrend( WeatherData::outsideHumidity ); + data.windSpeed = wd->getValue( WeatherData::averageWindSpeed, 0 ); + data.windGust = wd->getValue( WeatherData::windGust, 0 ); + data.windDirection = (int)wd->getValue( WeatherData::averageWindDirection, 0 ); + + data.windGustDir = (int)wd->getValue( WeatherData::windGustDir, 0 ); + + data.instantWindSpeed = wd->getValue( WeatherData::windSpeed, 0 ); + data.instantWindDirection = (int)wd->getValue( WeatherData::windDirection, 0 ); + data.UV = wd->getValue( WeatherData::UV, 0 ); + data.solarRadiation = wd->getValue( WeatherData::solarRadiation, 0 ); + data.solarRadiationPercent = (int)wd->getValue( WeatherData::solarPercent, 0 ); + data.barometer = wd->getValue( WeatherData::SLP, 0 ); + data.barometricTrend = wd->getTrend( WeatherData::altimeter ); + data.dailyRain = wd->getValue( WeatherData::dailyRain, 0 )/100.0; + data.rainRate = wd->getValue( WeatherData::rainRate, 0 )/100.0; + data.rain24Hour = wd->getValue( WeatherData::rain24Hour, 0 )/100.0; + data.instantRain = wd->getValue( WeatherData::instantRain, 0)/100.0; + data.dewPoint = wd->getValue( WeatherData::dewPoint, 0 ); + data.apparentTemp = wd->getValue( WeatherData::apparentTemp, 0 ); + data.cloudHeight = (int)wd->getValue( WeatherData::cloudHeight, 0 ); + int i = (int)((data.windDirection+11)/(360/16.0)); + data.cardinal = cardinals[ i ]; + + i = (int)((data.windGustDir+11)/(360/16.0)); + data.gustCardinal = cardinals[ i ]; + + i = (int)((data.instantWindDirection+11)/(360/16.0)); + data.instantCardinal = cardinals[ i ]; + + // Oh, how I hate floating point. .010 != .099999999999999999 + int trend = (int)(data.barometricTrend * 1000 ); + dbg.printf(DEBUG, "trend: %d\n", trend ); + if ( trend <= -20 ) + { + data.barometricTrendString = "Falling Rapidly"; + } + else if ( trend > -20 && trend <= -10 ) + { + data.barometricTrendString = "Falling Slowly"; + } + else if ( trend > -10 && trend < 10 ) + { + data.barometricTrendString = "Steady"; + } + else if ( trend >= 10 && trend < 20 ) + { + data.barometricTrendString = "Rising Slowly"; + } + else if ( trend >= 20 ) + { + data.barometricTrendString = "Rising Rapidly"; + } + else + { + data.barometricTrendString = "Unknown"; + } + + +} + +void TestWriter::main( ) +{ + while ( threadRunning ) + { + if ( pthread_mutex_trylock( ¬ify ) == 0 ) + { + getData(); + } + else + { + sleep(1); + } + + if ( !threadRunning ) + { + dbg.printf(NOTICE, "Exiting\n"); + return; + } + + time_t ct = time(NULL); + char ct_str[30]; + int h,w; + int row = 0; + strcpy( ct_str, ctime( &ct )); + ct_str[strlen(ct_str)-1] = 0; + if ( winch ) + { + winch = 0; + struct winsize size; + if ( ioctl( fileno( stdout ), TIOCGWINSZ, &size ) == 0 ) + { + resizeterm( size.ws_row, size.ws_col ); + teardownWindow(); + setupWindow(); + } + } + curs_set(0); + werase( dataWin ); + werase( titleWin ); + box( dataWin, ACS_VLINE, ACS_HLINE ); + box( titleWin, ACS_VLINE, ACS_HLINE ); + getmaxyx( titleWin, h, w ); + if ( cfg.getInteger("test_only") == 1 ) + { + mvwprintw( titleWin, 1,(w-strlen(ct_str))/2-7, "(TEST) %s (TEST)", ct_str ); + } + else + { + mvwprintw( titleWin, 1,(w-strlen(ct_str))/2, "%s", ct_str ); + } + + row = 1; + mvwprintw( dataWin, row++,2, " Outside Temperature: %.1fF (%+.1fF/hr)", data.outsideTemp, data.outsideTempTrend); + mvwprintw( dataWin, row++,2, " Outside Humidity: %.1f%% (%+.1f%%/hr)", data.outsideHumidity, data.outsideHumidityTrend); + mvwprintw( dataWin, row++,2, " Dew Point: %.1fF", data.dewPoint); + mvwprintw( dataWin, row++,2, " Apparent Temp: %.1fF", data.apparentTemp); + mvwprintw( dataWin, row++,2, " Barometric pressure: %.2f\"", data.barometer ); + mvwprintw( dataWin, row++,2, " Barometric trend: %+.03f\"/hr (%s)", data.barometricTrend, data.barometricTrendString.c_str() ); + mvwprintw( dataWin, row++,2, " Cloud height: %d ft", data.cloudHeight ); + + row++; + mvwprintw( dataWin, row,2, " Wind Speed (2m avg): %.1f mph", data.windSpeed); + mvwprintw( dataWin, row++,40, " Instant Wind Speed: %.1f mph", data.instantWindSpeed); + mvwprintw( dataWin, row,2, " Wind Direction: %d (%s)", data.windDirection, data.cardinal); + mvwprintw( dataWin, row++,40, " Instant Wind Dir: %d (%s)", data.instantWindDirection, data.instantCardinal ); + mvwprintw( dataWin, row++,2, " Wind Gust: %.1f mph", data.windGust); + mvwprintw( dataWin, row++,2, " Wind Gust Direction: %d (%s)", data.windGustDir, data.gustCardinal); + row++; + mvwprintw( dataWin, row++,2, " Daily Rain: %.2f\" 24-hour rain: %.2f\"", data.dailyRain, data.rain24Hour); + mvwprintw( dataWin, row++,2, " Rain Rate: %.2f\"/hr Instant Rain Rate: %.2f\"/hr", data.rainRate, data.instantRain ); + + row++; + mvwprintw( dataWin, row++,2, " Solar Radiation: %.1f W/m^2", data.solarRadiation); + mvwprintw( dataWin, row++,2, " Solar Percent: %d%%", data.solarRadiationPercent); + mvwprintw( dataWin, row++,2, " UV: %.1f", data.UV); + wrefresh(dataWin); + wrefresh(titleWin); + refresh(); + } + //printf("Outside Temp: %.1f\n", data.outsideTemp ); +} diff --git a/testWriter.h b/testWriter.h new file mode 100644 index 0000000..4dc10e4 --- /dev/null +++ b/testWriter.h @@ -0,0 +1,68 @@ +#ifndef __TEST_WRITER__ +#define __TEST_WRITER__ +#include "WeatherSink.h" +#include "ncurses.h" + +#define TITLE "CURRENT WEATHER" + +class TestWriter: public WeatherSink +{ +public: + TestWriter( Config cfg, WeatherData *wd ); + ~TestWriter(); + + void newData(); + void main( ); + +private: + struct + { + time_t now; + double outsideTemp; + double outsideTempTrend; + double outsideHumidity; + double outsideHumidityTrend; + double solarRadiation; + int solarRadiationPercent; + double UV; + double windSpeed; + double instantWindSpeed; + double windGust; + double barometer; + double barometricTrend; + double dailyRain; + double rainRate; + double rain24Hour; + double instantRain; + double dewPoint; + int cloudHeight; + double apparentTemp; + int windDirection; + int windGustDir; + int instantWindDirection; + + const char *cardinal; + const char *gustCardinal; + const char *instantCardinal; + std::string barometricTrendString; + } data; + + WINDOW *mainWin; + WINDOW *titleWin; + WINDOW *dataWin; + + pthread_mutex_t notify; + +private: + void setupWindow(); + void teardownWindow(); + void getData(); + +public: + static int winch; +}; + + + + +#endif //__TEST_WRITER__ diff --git a/types.h b/types.h new file mode 100644 index 0000000..41f2dd1 --- /dev/null +++ b/types.h @@ -0,0 +1,7 @@ +typedef signed char INT8; +typedef unsigned char UINT8; +typedef unsigned short UINT16; +typedef signed short INT16; +typedef unsigned long UINT32; +typedef unsigned long long UINT64; +typedef double F64; diff --git a/units.c b/units.c new file mode 100644 index 0000000..0b330a4 --- /dev/null +++ b/units.c @@ -0,0 +1,165 @@ +#include "units.h" +#include +#include + +// Wind Conversion + +// To convert between miles per hour (mph) and knots (kts) +double mph2kts( double mph ) { return mph * 0.8689762; } +double kts2mph( double kts ) { return kts * 1.1507794; } + +// To convert between miles per hour (mph) and meters per second (m/s) +double mph2mps( double mph ) { return mph * 0.44704; } +double mps2mph( double mps ) { return mps * 2.23694; } + +// To convert between miles per hour (mph) and feet per second (ft/s) +double fps2mph( double fps ) { return fps * 0.681818; } +double mph2fps( double mph ) { return mph * 1.46667; } + +// To convert between miles per hours (mph) and kilometers per hour (km/h): +double kph2mph( double kph ) { return kph * 0.621371; } +double mph2kph( double mph ) { return mph * 1.609344; } + +// To convert between knots (kts) and meters per second (m/s) +double kts2mps( double kts ) { return kts * 0.5144444; } +double mps2kts( double mps ) { return mps * 1.9438455; } + + +// To convert between knots (kts) and feet per second (ft/s) +double fps2kts( double fps ) { return fps * 0.5924838; } +double kts2fps( double kts ) { return kts * 1.6878099; } + +// To convert between knots (kts) and kilometers per hour (km/h) +double kph2kts( double kph ) { return kph * 0.5399568; } +double kts2kph( double kts ) { return kts * 1.852; } + +// To convert between meters per second (m/s) and feet per second (ft/s): +double mps2fps( double fps ) { return fps * 0.3048; } +double fps2mps( double mps ) { return mps * 3.28084; } + +// To convert between meters per second (m/s) and kilometers per hour (km/h): +double kph2mps( double kph ) { return kph * 0.277778; } +double mps2kph( double mps ) { return mps * 3.6; } + +// To convert between feet per second (ft/s) and kilometers per hours (km/h) +double fps2kph( double fps ) { return fps * 1.09728; } +double kph2fps( double kph ) { return kph * 0.911344; } + +// To convert between meters and feet +double m2ft( double m ) { return m * 3.28084; } +double ft2m( double ft ) { return ft * 0.3048; } + +// Temperature Conversions + +// To convert between degrees Fahrenheit and degrees Celsius +double F2C( double F ) { return (5/9.0)*(F-32); } +double C2F( double C ) { return ((9/5.0)*C)+32; } + +// To convert between degrees Fahrenheit and Kelvin +double K2F( double K ) { return (9/5.0)*(K-273.15)+32; } +double F2K( double F ) { return ((5/9.0)*(F-32))+273.15; } + +// To convert between degrees Fahrenheit to Rankine +double R2F( double R ) { return R - 459.69; } +double F2R( double F ) { return F + 259.69; } + +// To convert between degrees Celsius to Kelvin +double K2C( double K ) { return K - 273.15; } +double C2K( double C ) { return C + 273.15; } + +// To convert between degrees Celsius to Rankine +double R2C( double R ) { return (5/9.0) * ( R - 491.69 ); } +double C2R( double C ) { return ((9/5.0) * C ) + 491.69 ; } + +// To convert between Kevin and Rankine +double R2K( double R ) { return (5/9.0)*(R-764.84); } +double K2R( double K ) { return ((9/5.0)*K)+764.84; } + +// Rain rate conversions +double in2mm( double in ) { return in * 25.4; } +double mm2in( double mm ) { return mm * 0.0393700787; }; + +// Pressure +double inHg2hPa( double inHg ) { return 33.8638 * inHg; } +double hPa2inHg( double hPa ) { return 0.0295300 * hPa; } + +// Trig +float deg2rad( float deg ) { return 0.01745329 * deg; } +float rad2deg( float rad ) { return 57.29578 * rad; } + +// Time +int jan1local() +{ + return 0; + //return JAN12000 + (timezone - 3600 * daylight); +} + +time_t gmt2local( time_t gmt ) +{ + return gmt; + //return gmt - (timezone - 3600 * daylight); +} + +time_t julian2localtime( double dec ) +{ + dec += .5; // roll over at midnight instead of noon + + int ip = ((int)dec - 2451545)*60*60*24; + int secs = (int)((dec-trunc(dec))*(3600*24)); + time_t localtime = ip + jan1local() + secs; + return gmt2local(localtime); +} + +double frac( double num ) +{ + double a; + a = num - floor( num ); + if ( a < 0 ) a+=1; + return a; +} + +std::string DecimalLatitudeToLORAN( double decimalDegrees ) +{ + char buf[16]; + char cardinal; + + bool negative = false; + if ( decimalDegrees < 0 ) + negative = true; + decimalDegrees = fabs( decimalDegrees ); + + int degrees = (int)(decimalDegrees); + int minutes = (int)((((decimalDegrees - degrees) * 100)*60.0)/100.0); + int decimalMin = ((int)(((decimalDegrees - degrees) * 100)*60.0) - minutes * 100); + + if ( negative == true ) + cardinal = 'S'; + else + cardinal = 'N'; + + snprintf(buf, 15, "%02d%02d.%02d%c", degrees, minutes, decimalMin, cardinal ); + return buf; +} + +std::string DecimalLongitudeToLORAN( double decimalDegrees ) +{ + char buf[16]; + char cardinal; + + bool negative = false; + if ( decimalDegrees < 0 ) + negative = true; + decimalDegrees = fabs( decimalDegrees ); + + int degrees = (int)(decimalDegrees); + int minutes = (int)((((decimalDegrees - degrees) * 100)*60.0)/100.0); + int decimalMin = ((int)(((decimalDegrees - degrees) * 100)*60.0) - minutes * 100); + + if ( negative == true ) + cardinal = 'W'; + else + cardinal = 'E'; + + snprintf(buf, 15, "%03d%02d.%02d%c", degrees, minutes, decimalMin, cardinal ); + return buf; +} diff --git a/units.h b/units.h new file mode 100644 index 0000000..4798324 --- /dev/null +++ b/units.h @@ -0,0 +1,109 @@ +#ifndef __UNITS_H__ +#define __UNITS_H__ +#include +#include + +#define JAN12000 (946684800) + +// Wind Conversion + +// To convert between miles per hour (mph) and knots (kts) +double mph2kts( double mph ); +double kts2mph( double kts ); + +// To convert between miles per hour (mph) and meters per second (m/s) +double mph2mps( double mph ); +double mps2mph( double mps ); + +// To convert between miles per hour (mph) and feet per second (ft/s) +double fps2mph( double fps ); +double mph2fps( double mph ); + +// To convert between miles per hours (mph) and kilometers per hour (km/h): +double mph2kph( double kph ); +double kph2mph( double mph ); + +// To convert between knots (kts) and meters per second (m/s) +double kts2mps( double kts ); +double mps2kts( double mps ); + + +// To convert between knots (kts) and feet per second (ft/s) +double fps2kts( double fps ); +double kts2fps( double kts ); + +// To convert between knots (kts) and kilometers per hour (km/h) +double kph2kts( double kph ); +double kts2kph( double kts ); + +// To convert between meters per second (m/s) and feet per second (ft/s): +double mps2fps( double fps ); +double fps2mps( double mps ); + +// To convert between meters per second (m/s) and kilometers per hour (km/h): +double kph2mps( double kph ); +double mps2kph( double mps ); + +// To convert between feet per second (ft/s) and kilometers per hours (km/h) +double fps2kph( double fps ); +double kph2fps( double kph ); + +// Convert meter to feet +double ft2m( double ft ); +double m2ft( double m ); + +// Temperature Conversions + +// To convert between degrees Fahrenheit and degrees Celsius +double F2C( double F ); +double C2F( double C ); + +// To convert between degrees Fahrenheit and Kelvin +double K2F( double K ); +double F2K( double F ); + +// To convert between degrees Fahrenheit to Rankine +double R2F( double R ); +double F2R( double F ); + +// To convert between degrees Celsius to Kelvin +double K2C( double K ); +double C2K( double C ); + +// To convert between degrees Celsius to Rankine +double R2C( double R ); +double C2R( double C ); + +// To convert between Kevin and Rankine +double R2K( double R ); +double K2R( double K ); + +// Rain rate conversions +double in2mm( double in ); +double mm2in( double mm ); + +double inHg2hPa( double inHg ); +double hPa2inHg( double hPa ); + + +// Trig + +float deg2rad( float deg ); +float rad2deg( float rad ); + + +// Time +int jan1local(); +time_t gmt2local( time_t gmt ); +time_t julian2localtime( double dec ); + + +// Math +double frac( double num ); + +// Geography +std::string DecimalLatitudeToLORAN( double decimalDegrees ); +std::string DecimalLongitudeToLORAN( double decimalDegrees ); + +#endif // __UNITS_H__ + diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..7f4726f --- /dev/null +++ b/utils.c @@ -0,0 +1,141 @@ +#include "utils.h" +#include +#include +#include +#include +#include +#include +#include "Debug.h" + + +size_t readTimeout( int fd, void *buf, size_t count, time_t timeout ) +{ + Debug dbg("readTimeout"); + fd_set rfds; + struct timeval tv; + int rval; + unsigned char *ptr = (unsigned char*)buf; + size_t readCount = count; // How many bytes left to read + + if ( fd == -1 ) + { + errno = EBADR; + return (size_t)-1; + } + + FD_ZERO( &rfds ); + FD_SET( fd, &rfds ); + tv.tv_sec = timeout; + tv.tv_usec = 0; + + while ( readCount ) + { + rval = select( fd + 1, &rfds, NULL, NULL, &tv ); + + if ( tv.tv_sec == 0 && tv.tv_usec == 0 ) + { + errno = ETIMEDOUT; + return (size_t)-1; + } + + if ( rval == -1 ) + { + // perror("select()"); + } + else if ( rval && FD_ISSET( fd, &rfds )) + { + int r = read( fd, ptr, readCount ); + if ( r > 0 ) + { + readCount-=r; + ptr+=r; + } + if ( r == 0 ) + { + return count; + } + } + else + { + errno = ETIMEDOUT; + return (size_t)-1; + } + } + return count; +} + +int strnfcat( char *str, size_t size, const char *format, ... ) +{ + va_list args; + char buf[size]; + va_start( args, format ); + vsnprintf( buf, size, format, args ); + strncat( str, buf, size - strlen( str ) ); + va_end( args ); + + return strlen( str ); + +} + +double getJulianDay( int day, int month, int year, int hour, int minute, int second ) +{ +// printf("%s (%d, %d, %d, %d, %d, %d)\n", __func__, day, month, year, hour, + // minute, second ); + year += 1900; + float D = day + (hour/24.0) + (minute / (24.0 * 60 )) + ( second / ( 24.0 * 60 * 60 )); + if ( month <= 2 ) + { + year -= 1; + month += 12; + } + int A = int( year/100.0 ); + int B = 2 - A + int( A/4.0 ); + int a = int( 365.25 * ( year + 4716 )); + int b = int( 30.6001* ( month + 1 )); + // printf("A = %d\n",A ); + // printf("B = %d\n",B ); + // printf("a = %d\n",a ); + // printf("b = %d\n",b ); + return a + ( b + D + B - 1524.5 ); + +} + +double dms2deg( int hour, int min, float sec ) +{ + return ( hour + min/60.0 + sec/3600.0 ); + + //rval = rval / +} + +std::string deg2dms( double decimalDegrees ) +{ + char buf[32]; + bool negative = false; + if ( decimalDegrees < 0 ) + negative = true; + decimalDegrees = fabs( decimalDegrees ); + // decimalDegrees /= 15.0; + + int degrees = (int)(decimalDegrees); + double dmin = ((((decimalDegrees - degrees) * 100)*60.0)/100.0); + int minutes = (int)dmin; + + double seconds= ((((dmin - minutes) * 100)*60.0)/100.0); + + snprintf(buf, 31, "%c%3d %02d' %.3f\"", negative?'-':' ', degrees, minutes, seconds ); + return buf; +} + +double rangeDegrees( double deg ) +{ + while ( deg < 0 ) + { + deg+= 360; + } + while (deg > 360 ) + { + deg-=360; + } + return deg; +} + diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..961c60e --- /dev/null +++ b/utils.h @@ -0,0 +1,24 @@ +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include +#include +#include +#include +#include +#include + + +size_t readTimeout( int fd, void *buf, size_t count, time_t timeout ); + +int strnfcat( char *str, size_t size, const char *format, ... ); + +double getJulianDay( int day, int month, int year, int hour, int minute, int second ); + +double dms2deg( int hour, int min, float sec ); + +std::string deg2dms( double deg ); + +double rangeDegrees( double deg ); + +#endif // __UTILS_H__ diff --git a/weather.xsl b/weather.xsl new file mode 100644 index 0000000..936490f --- /dev/null +++ b/weather.xsl @@ -0,0 +1,73 @@ + + + + + + + + + + + +
+ + + + + + + + +
+

+ + , +
+ +

+
+ + + + + + + + + + + + + +
SensorValue
+ + + +
+
+ + + + + + + + + + + + +
TrendValue
+ + + +
+
+
+ + +
+ +
+ diff --git a/weatherbug.cpp b/weatherbug.cpp new file mode 100644 index 0000000..3bf44e4 --- /dev/null +++ b/weatherbug.cpp @@ -0,0 +1,161 @@ +#include "weatherbug.h" +#include "time.h" +#include +#include "utils.h" +#include + +size_t WeatherBug::curl_write( void *buffer, size_t size, size_t nmemb, void* userp ) +{ + return size*nmemb; +} + + +WeatherBug::WeatherBug( Config cfg, WeatherData *_wd ): + WeatherSink( cfg, _wd, "WeatherBug" ), + failureCount( 0 ), + updateFrequency( 60 ) +{ + pthread_mutex_init( ¬ify, NULL ); + uploadTime.updateTime(); + startMain(); +} + +WeatherBug::~WeatherBug() +{ + endMain(); + +} + + +void WeatherBug::newData() +{ + pthread_mutex_unlock( ¬ify ); +} + +void WeatherBug::generatePacket() +{ + struct tm gmt; + time_t t; + t = (time_t) wd->now->unixtime() ; + char st[32]; + + + gmtime_r( &t, &gmt ); + strftime( st, 31, "%Y-%m-%d+%H%%3A%M%%3A%S", &gmt ); + + snprintf(uploadString, WEATHERBUG_STRING_SIZE, + "%s?ID=%s&Key=%s&num=%s&dateutc=%s", + WEATHERBUG_RT_URL, + cfg.getString("weatherbug_id").c_str(), + cfg.getString("weatherbug_key").c_str(), + cfg.getString("weatherbug_num").c_str(), + st ); + + appendData( "&winddir=%.0f", WeatherData::windDirection ); + appendData( "&windspeedmph=%.2f",WeatherData::averageWindSpeed ); + appendData( "&windgustmph=%.2f", WeatherData::windGust ); + appendData( "&humidity=%.0f", WeatherData::outsideHumidity ); + appendData( "&tempf=%.2f", WeatherData::outsideTemp ); + appendData( "&rainin=%.2f", WeatherData::rainRate, 100.0 ); + appendData( "&dailyrainin=%.2f", WeatherData::dailyRain, 100.0 ); + appendData( "&baromin=%.2f", WeatherData::SLP ); + + strnfcat( uploadString, WEATHERBUG_STRING_SIZE, "&softwaretype=%s%s", + "WxPro%20", + "DR1"); + +} + +void WeatherBug::uploadPacket() +{ + if ( cfg.getInteger("test_only") != 1 ) + { + curl = curl_easy_init(); + curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, WeatherBug::curl_write); + curl_easy_setopt( curl, CURLOPT_TIMEOUT, 5 ); + curl_easy_setopt( curl, CURLOPT_ERRORBUFFER, curlErrorBuf ); + curl_easy_setopt( curl, CURLOPT_NOSIGNAL, 1 ); + curl_easy_setopt( curl, CURLOPT_URL, uploadString ); + dbg.printf( DEBUG, "CURL upload string: '%s'\n", uploadString ); + res=curl_easy_perform(curl); + if ( (int)res != 0 ) + { + failureCount++; + if ( failureCount < 10 ) + { + dbg.printf(DEBUG, "Could not write to WeatherBug. Error: %s (%d)\n", + curlErrorBuf, res ); + } + else + { + dbg.printf(CRIT, "Could not write to WeatherBug after 10 attempts. Error: %s (%d)\n", + curlErrorBuf, res ); + failureCount = 0; + } + } + else + { + failureCount = 0; + } + if ( curl ) + curl_easy_cleanup( curl ); + } + else + { + dbg.printf(NOTICE,"Pretending to Uploading to WeatherBug\n"); + dbg.printf(DEBUG, "uploadString: %s\n", uploadString ); + } + uploadTime.updateTime(); +} + +void WeatherBug::main() +{ + + while( threadRunning ) + { + pthread_mutex_lock( ¬ify ); + if ( !threadRunning ) + return; + + float timeSinceLast = uploadTime.timeSinceLast(); + dbg.printf(DEBUG, "time since last: %f\n", timeSinceLast); + if ( timeSinceLast <= 2 ) + { + dbg.printf(DEBUG, "too soon since last update.. not doing anything\n"); + continue; + } + if ( timeSinceLast > 2 && timeSinceLast < updateFrequency ) + { + int sleepTime = (int)((updateFrequency - timeSinceLast ) ); + dbg.printf(DEBUG, "too soon since last update.. sleeping for %d usec\n", sleepTime); + for ( int i = 0; i < sleepTime; i++) + { + sleep(1); + if ( !threadRunning ) + return; + } + } + + generatePacket(); + uploadPacket(); + } +} + + +bool WeatherBug::appendData( const char *format, WeatherData::PROPERTY property, float divider ) +{ + char buf[255]; + if ( wd->hasData( property ) ) + { + snprintf(buf, 255, format, wd->getValue( property, 0 ) / divider ); + if ( strlen(buf) + strlen( uploadString ) >= WEATHERBUG_STRING_SIZE ) + { + dbg.printf(CRIT, "Buffer size exceeded.\n"); + return false; + } + strcat( uploadString, buf ); + return true; + } + return false; + +} diff --git a/weatherbug.h b/weatherbug.h new file mode 100644 index 0000000..65dfeac --- /dev/null +++ b/weatherbug.h @@ -0,0 +1,74 @@ +#ifndef __WeatherBug_H__ +#define __WeatherBug_H__ +#include "WeatherSink.h" +#include + +#define WEATHERBUG_STRING_SIZE (512) +#define WEATHERBUG_RT_URL "http://data.backyard2.weatherbug.com/data/livedata.aspx" + +class WeatherBug: public WeatherSink +{ +public: + WeatherBug( Config cfg, WeatherData *wd ); + ~WeatherBug(); + + void newData(); + void main(); + + static size_t curl_write( void *buffer, size_t size, size_t nmemb, void* userp ); + +private: + bool appendData( const char * format, WeatherData::PROPERTY, float divider = 1 ); + void generatePacket( void ); + void uploadPacket( void ); + +private: + pthread_mutex_t notify; + CURL *curl; + CURLcode res; + int failureCount; + float updateFrequency; + Now uploadTime; + + char curlErrorBuf[CURL_ERROR_SIZE]; + char uploadString[WEATHERBUG_STRING_SIZE]; +}; + +#endif //__WeatherBug_H__ +#ifndef __WeatherBug_H__ +#define __WeatherBug_H__ +#include "WeatherSink.h" +#include + +#define WEATHERBUG_STRING_SIZE (512) +#define WEATHERBUG_RT_URL "http://data.backyard2.weatherbug.com/data/livedata.aspx" + +class WeatherBug: public WeatherSink +{ +public: + WeatherBug( Config cfg, WeatherData *wd ); + ~WeatherBug(); + + void newData(); + void main(); + + static size_t curl_write( void *buffer, size_t size, size_t nmemb, void* userp ); + +private: + bool appendData( const char * format, WeatherData::PROPERTY, float divider = 1 ); + void generatePacket( void ); + void uploadPacket( void ); + +private: + pthread_mutex_t notify; + CURL *curl; + CURLcode res; + int failureCount; + float updateFrequency; + Now uploadTime; + + char curlErrorBuf[CURL_ERROR_SIZE]; + char uploadString[WEATHERBUG_STRING_SIZE]; +}; + +#endif //__WeatherBug_H__ diff --git a/weatherlink.c b/weatherlink.c new file mode 100644 index 0000000..212a39c --- /dev/null +++ b/weatherlink.c @@ -0,0 +1,235 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ +/* Download a document and use libtidy to parse the HTML. + * Written by Jeff Pohlmeyer + * + * LibTidy => http://tidy.sourceforge.net + * + * gcc -Wall -I/usr/local/include tidycurl.c -lcurl -ltidy -o tidycurl + * + */ + +#include +#include +#include +#include +#include + +#define URL_BUF_SIZE (1024) + +typedef struct +{ + double outsideTemp; + int outsideHumidity; + double dewPoint; + double barometer; + double instantWindSpeed; + int instantWindDirection; + double avgWindSpeed_2min; + double windGust_10min; + double rainRate; + double dailyRain; + double lastHourRain; +} WeatherData; + +/* curl write callback, to fill tidy's input buffer... */ +uint write_cb(char *in, uint size, uint nmemb, TidyBuffer *out) +{ + uint r; + r = size * nmemb; + tidyBufAppend( out, in, r ); + return(r); +} + +/* Traverse the document tree */ +int dumpNode(TidyDoc doc, TidyNode tnod, int element, WeatherData *data ) +{ + TidyNode child; + for ( child = tidyGetChild(tnod); child; child = tidyGetNext(child) ) + { + element++; + ctmbstr name = tidyNodeGetName( child ); + if ( name ) + { + /* if it has a name, then it's an HTML tag ... */ + //TidyAttr attr; + //printf( "%*.*s%s ", indent, indent, "<", name); + /* walk the attribute list */ + //for ( attr=tidyAttrFirst(child); attr; attr=tidyAttrNext(attr) ) { + //printf(tidyAttrName(attr)); + //tidyAttrValue(attr)?printf("=\"%s\" ", + //tidyAttrValue(attr)):printf(" "); + //} + //printf( ">\n"); + } + else { + /* if it doesn't have a name, then it's probably text, cdata, etc... */ + TidyBuffer buf; + tidyBufInit(&buf); + tidyNodeGetText(doc, child, &buf); + //printf("[%d]%s\n", element, buf.bp?(char *)buf.bp:""); + switch (element) + { + case 133: + sscanf( (char*)buf.bp, "%lf", &(data->outsideTemp) ); + break; + case 159: + sscanf( (char*)buf.bp, "%d", &(data->outsideHumidity)); + break; + case 301: + sscanf( (char*)buf.bp, "%lf", &(data->dewPoint)); + break; + case 333: + sscanf( (char*)buf.bp, "%lf", &(data->barometer)); + break; + case 391: // wind speed + if ( sscanf( (char*)buf.bp, "%lf", &(data->instantWindSpeed)) == 0) + { + data->instantWindSpeed = 0; + } + break; + case 417: // wind direction + { + char b[100]; + int i,j=0; + for ( i=0; iinstantWindDirection)); + } + break; + case 503: + if ( sscanf( (char*)buf.bp, "%lf", &(data->avgWindSpeed_2min)) == 0 ) + { + data->avgWindSpeed_2min = 0; + } + break; + case 533: + if ( sscanf( (char*)buf.bp, "%lf", &(data->windGust_10min)) == 0 ) + { + data->windGust_10min = 0; + } + break; + case 599: + sscanf( (char*)buf.bp, "%lf", &(data->rainRate)); + break; + case 603: + sscanf( (char*)buf.bp, "%lf", &(data->dailyRain)); + break; + case 625: + sscanf( (char*)buf.bp, "%lf", &(data->lastHourRain)); + break; + } + tidyBufFree(&buf); + } + element++; + element = dumpNode( doc, child, element, data ); /* recursive */ + } + return element; +} + + +int main(int argc, char **argv ) +{ + CURL *curl; + char curl_errbuf[CURL_ERROR_SIZE]; + char url[URL_BUF_SIZE]; + char *username; + TidyDoc tdoc; + TidyBuffer docbuf = {0}; + TidyBuffer tidy_errbuf = {0}; + int err; + if ( argc == 2) + { + username = argv[1]; + } + else + { + username = "parkernathan"; + } + WeatherData data; + snprintf(url, URL_BUF_SIZE, "http://www.weatherlink.com/user/%s/index.php?view=summary&headers=0&type=2", username); + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb); + + tdoc = tidyCreate(); + tidyOptSetBool(tdoc, TidyForceOutput, yes); /* try harder */ + tidyOptSetInt(tdoc, TidyWrapLen, 4096); + tidySetErrorBuffer( tdoc, &tidy_errbuf ); + tidyBufInit(&docbuf); + + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &docbuf); + err=curl_easy_perform(curl); + if ( !err ) + { + err = tidyParseBuffer(tdoc, &docbuf); /* parse the input */ + if ( err >= 0 ) + { + err = tidyCleanAndRepair(tdoc); /* fix any problems */ + if ( err >= 0 ) + { + dumpNode( tdoc, tidyGetRoot(tdoc), 0, &data ); /* walk the tree */ + //err = tidyRunDiagnostics(tdoc); /* load tidy error buffer */ + //if ( err >= 0 ) + //{ + //dumpNode( tdoc, tidyGetRoot(tdoc), 0 ); /* walk the tree */ + // fprintf(stderr, ">> %s\n", tidy_errbuf.bp); /* show errors */ + //} + } + } + } + else + { + fprintf(stderr, "%s\n", curl_errbuf); + } + printf("Outside temp: %f\n", data.outsideTemp ); + printf("Outside humidity: %d\n", data.outsideHumidity ); + printf("Dew Point: %f\n", data.dewPoint ); + printf("Barometer: %f\n", data.barometer ); + printf("Wind speed: %f\n", data.instantWindSpeed ); + printf("Wind direction: %d\n", data.instantWindDirection ); + printf("Average Wind: %f\n", data.avgWindSpeed_2min ); + printf("Wind Gust: %f\n", data.windGust_10min); + printf("rainRate: %f\n", data.rainRate ); + printf("dailyRain: %f\n", data.dailyRain ); + printf("lastHourRain: %f\n", data.lastHourRain ); + + /* clean-up */ + curl_easy_cleanup(curl); + tidyBufFree(&docbuf); + //tidyBufFree(&tidy_errbuf); + tidyRelease(tdoc); + return(err); + + + return(0); +} diff --git a/wunderground.cpp b/wunderground.cpp new file mode 100644 index 0000000..b24b5cb --- /dev/null +++ b/wunderground.cpp @@ -0,0 +1,167 @@ +#include "wunderground.h" +#include "time.h" +#include +#include "utils.h" +#include + +size_t Wunderground::curl_write( void *buffer, size_t size, size_t nmemb, void* userp ) +{ + return size*nmemb; +} + + +Wunderground::Wunderground( Config cfg, WeatherData *_wd ): + WeatherSink( cfg, _wd, "Weather Underground" ), + failureCount( 0 ), + updateFrequency( 2.5 ) +{ + pthread_mutex_init( ¬ify, NULL ); + uploadTime.updateTime(); + startMain(); +} + +Wunderground::~Wunderground() +{ + endMain(); + +} + + +void Wunderground::newData() +{ + pthread_mutex_unlock( ¬ify ); +} + +void Wunderground::generatePacket() +{ + struct tm gmt; + time_t t; + t = (time_t) wd->now->unixtime() ; + char st[32]; + + + gmtime_r( &t, &gmt ); + strftime( st, 31, "%Y-%m-%d+%H%%3A%M%%3A%S", &gmt ); + + snprintf(uploadString, WUNDERGROUND_STRING_SIZE, + "%s?ID=%s&PASSWORD=%s&dateutc=%s", + WUNDERGROUND_RT_URL, + cfg.getString("wunderground_id").c_str(), + cfg.getString("wunderground_password").c_str(), + st ); + + appendData( "&winddir=%.0f", WeatherData::windDirection ); + appendData( "&windspeedmph=%.2f",WeatherData::windSpeed ); + //appendData( "&windgustdir=%.0f",WeatherData::windGustDir ); + //appendData( "&windgustmph=%.2f",WeatherData::windGust ); + appendData( "&windgustdir_10m=%.0f", WeatherData::windGustDir ); + appendData( "&windgustmph_10m=%.2f", WeatherData::windGust ); + appendData( "&winddir_avg2m=%.0f", WeatherData::averageWindDirection ); + appendData( "&windspdmph_avg2m=%.2f", WeatherData::averageWindSpeed ); + appendData( "&tempf=%.2f", WeatherData::outsideTemp ); + appendData( "&rainin=%.2f", WeatherData::rainRate, 100.0 ); + appendData( "&dailyrainin=%.2f", WeatherData::dailyRain, 100.0 ); + appendData( "&baromin=%.2f", WeatherData::SLP ); + appendData( "&dewptf=%.2f", WeatherData::dewPoint ); + appendData( "&humidity=%.0f", WeatherData::outsideHumidity ); + appendData( "&solarradiation=%.0f", WeatherData::solarRadiation ); + appendData( "&UV=%.1f", WeatherData::UV ); + appendData( "&indoortempf=%.2f", WeatherData::insideTemp ); + appendData( "&indoorhumidity=%.0f", WeatherData::insideHumidity ); + + strnfcat( uploadString, WUNDERGROUND_STRING_SIZE, "&softwaretype=%s%s", + "WxPro%20", + "DR1"); + + strnfcat( uploadString, WUNDERGROUND_STRING_SIZE, "&action=updateraw&realtime=1&rtfreq=%.2f", + updateFrequency); +} + +void Wunderground::uploadPacket() +{ + if ( cfg.getInteger("test_only") != 1 ) + { + curl = curl_easy_init(); + curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, Wunderground::curl_write); + curl_easy_setopt( curl, CURLOPT_TIMEOUT, 5 ); + curl_easy_setopt( curl, CURLOPT_ERRORBUFFER, curlErrorBuf ); + curl_easy_setopt( curl, CURLOPT_NOSIGNAL, 1 ); + curl_easy_setopt( curl, CURLOPT_URL, uploadString ); + dbg.printf( DEBUG, "CURL upload string: '%s'\n", uploadString ); + res=curl_easy_perform(curl); + if ( (int)res != 0 ) + { + failureCount++; + if ( failureCount < 10 ) + { + dbg.printf(DEBUG, "Could not write to wunderground. Error: %s (%d)\n", + curlErrorBuf, res ); + } + else + { + dbg.printf(CRIT, "Could not write to wunderground after 10 attempts. Error: %s (%d)\n", + curlErrorBuf, res ); + failureCount = 0; + } + } + else + { + failureCount = 0; + } + if ( curl ) + curl_easy_cleanup( curl ); + } + else + { + dbg.printf(NOTICE,"Pretending to Uploading to wunderground\n"); + dbg.printf(DEBUG, "uploadString: %s\n", uploadString ); + } + uploadTime.updateTime(); +} + +void Wunderground::main() +{ + + while( threadRunning ) + { + pthread_mutex_lock( ¬ify ); + if ( !threadRunning ) + return; + + float timeSinceLast = uploadTime.timeSinceLast(); + dbg.printf(DEBUG, "time since last: %f\n", timeSinceLast); + if ( timeSinceLast <= 2 ) + { + dbg.printf(DEBUG, "too soon since last update.. not doing anything\n"); + continue; + } + if ( timeSinceLast > 2 && timeSinceLast < updateFrequency ) + { + int sleepTime = (int)((updateFrequency - timeSinceLast ) * 1000000 ); + dbg.printf(DEBUG, "too soon since last update.. sleeping for %d usec\n", sleepTime); + usleep( sleepTime ); + } + + generatePacket(); + uploadPacket(); + } +} + + +bool Wunderground::appendData( const char *format, WeatherData::PROPERTY property, float divider ) +{ + char buf[255]; + if ( wd->hasData( property ) ) + { + snprintf(buf, 255, format, wd->getValue( property, 0 ) / divider ); + if ( strlen(buf) + strlen( uploadString ) >= WUNDERGROUND_STRING_SIZE ) + { + dbg.printf(CRIT, "Buffer size exceeded.\n"); + return false; + } + strcat( uploadString, buf ); + return true; + } + return false; + +} diff --git a/wunderground.h b/wunderground.h new file mode 100644 index 0000000..da69485 --- /dev/null +++ b/wunderground.h @@ -0,0 +1,40 @@ +#ifndef __WUNDERGROUND_H__ +#define __WUNDERGROUND_H__ +#include "WeatherSink.h" +#include + +#define WUNDERGROUND_STRING_SIZE (512) +#define WUNDERGROUND_RT_URL "http://rtupdate.wunderground.com/weatherstation/updateweatherstation.php" + +class Wunderground: public WeatherSink +{ +public: + Wunderground( Config cfg, WeatherData *wd ); + ~Wunderground(); + + void newData(); + void main(); + + static size_t curl_write( void *buffer, size_t size, size_t nmemb, void* userp ); + +private: + bool appendData( const char * format, WeatherData::PROPERTY, float divider = 1 ); + void generatePacket( void ); + void uploadPacket( void ); + +private: + pthread_mutex_t notify; + CURL *curl; + CURLcode res; + int failureCount; + float updateFrequency; + Now uploadTime; + + char curlErrorBuf[CURL_ERROR_SIZE]; + char uploadString[WUNDERGROUND_STRING_SIZE]; +}; + + + + +#endif //__WUNDERGROUND_H__ diff --git a/wxpro.cfg.dist b/wxpro.cfg.dist new file mode 100644 index 0000000..1981ff3 --- /dev/null +++ b/wxpro.cfg.dist @@ -0,0 +1,49 @@ +vantage path = /dev/ttyS0 + +# Decimal degrees, negative for south +latitude = + +# Degimal degrees, negative for West +longitude = + +# Timezone.. negative for west +timezone = 0 + +# ID for weather underground +wunderground_id = + +# Password for weatherundeground +wunderground_password = + +# Use call sign if you are a HAM +cwop_user = CW0001 + +# email me if you need a APRS passcode +cwop_password = -1 + +# http://www.wxqa.com/servers2use.html +cwop_server = rotate.aprs.net + +# Should be 14580 or 23 +cwop_port = 14580 + +# elevation, in feet +elevation = 0 + +# If you barometer needs some tweaking +barometer_offset = 0 # inches + +# last digit of your CWOP id (ASxxx) +cwop_ul_min = 0 + +postal_code= +city= +state= + + +http_port=6100 + +# if this is set, no data will go out on the internet +test_only = 1 + +sw_vers = wxpro-0.2 diff --git a/wxpro.py b/wxpro.py new file mode 100755 index 0000000..44d1fc7 --- /dev/null +++ b/wxpro.py @@ -0,0 +1,288 @@ +#!/usr/bin/python + +from lxml import etree +from lxml import objectify +import curses, sys, signal, time, datetime +import httplib, threading, socket + +class WxProCurses: + + class DataFetcher( threading.Thread ): + def __init__( self, scr ): + self.scr = scr + self.scr.foo = "" + threading.Thread.__init__(self) + + def run(self): + if ( len(sys.argv)>1 ): + host = sys.argv[1].replace("http://", "", 1 ) + else: + host = "localhost" + + if ( len(sys.argv)>2 ): + port = sys.argv[2] + else: + port = 6100 + + conn = httplib.HTTPConnection(host, port) + + while( 1 ): + try: + conn.request("GET", "/") + except socket.gaierror as ex: + # This error is because of an invalid configuration + self.scr.immediateError = "%s:%s: %s" % (host, port, ex) + break + except socket.error as ex: + # This can either be an invalid configuration (host or port) or + # if the server went away for a bit. This is why we don't break + # here + self.scr.xml = "" + self.scr.fetcherError = "%s:%s: %s" % (host, port, ex) + except httplib.CannotSendRequest: + # Retry the connection. We can get here if there was a socket + # error because the server went away for a bit + conn.close() + conn = httplib.HTTPConnection(host, port) + else: + rval = conn.getresponse() + + if ( rval.status == 200 ): + self.scr.xml = rval.read() + else: + self.scr.xml = "" + self.scr.fetcherError = "Server Error: %d: %s" % (rval.status, rval.reason) + time.sleep(1.5) + + + def __init__(self, stdscr): + + self.stdscr = stdscr + self.useCelcius = True + self.fetcherError = "" + self.immediateError = "" + self.xml = "" + self.noDataCount = 0 + try: + curses.curs_set(0) + except Exception, e: + ex = e + + fetcher = self.DataFetcher(self) + fetcher.setDaemon( True ) + fetcher.start() + + self._setupScreen() + + titleStr = "Connecting to server..." + self.titleWin.addstr(1,(self.x-len(titleStr))/2, titleStr) + self.titleWin.refresh() + + while( 1 ): + + self._updateScreen() + + ch = self.stdscr.getch() + if ( ch == 113 ): # 113 = 'q' + stdscr.clear() + stdscr.refresh() + curses.endwin() + return; + elif ( ch == curses.KEY_RESIZE ): + curses.endwin() + self.stdscr=curses.initscr() + self._setupScreen() + elif ( ch == 12 ): # ctrl-L + curses.endwin() + self.stdscr=curses.initscr() + self._setupScreen() + + def _setupScreen(self ): + (self.y, self.x) = self.stdscr.getmaxyx() + self.stdscr.timeout(1000) # make getch timeout after 1 sec so we can + # update the screen asynchronously + + # We anything less than 24x80 is pretty useless, so let's make that our + # minimum size + if ( self.y < 24 ): + self.y = 24 + if ( self.x < 80 ): + self.x = 80 + self.titleWin = curses.newwin(3,self.x,0,0) + self.dataWin = curses.newwin(self.y-3,self.x,3,0) + self.titleWin.border(0) + self.dataWin.border(0) + self.stdscr.refresh() + self.dataWin.refresh() + self.titleWin.refresh() + + def _updateTitleWin(self): + # Draw the time in the title window + currentTimeStr = time.ctime() + self.titleWin.addstr(1,(self.x-len(currentTimeStr))/2, time.ctime()) + self.titleWin.refresh() + + def _showTemp( self, form, val, label=None ): + temp = val + if self.useCelcius: + units = "C" + if label: + temp = temp * 5/9 + else: + temp = (val - 32) * 5/9 # For the case when it's F/hour + else: + units = "F" + + self.dataWin.addstr( form % temp ) + self.dataWin.addch( curses.ACS_DEGREE ) + self.dataWin.addstr( units ) + if label: + self.dataWin.addstr( label ) + + + def _showWindDir( self, direction ): + cardinals = ( "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N" ) + + self.dataWin.addstr( "%d" % (direction) ) + self.dataWin.addch( curses.ACS_DEGREE ) + if direction > len(cardinals): + direction = 0 + self.dataWin.addstr( " (%s)" % ( cardinals[(int)((direction+11)/(360.0/16))])) + + + def _altimeterTrendToStr( self, val ): + if ( val <= -.020 ): + return "Falling Rapidly" + elif ( val > -.020 and val <= -.010 ): + return "Falling Slowly" + elif ( val > -.010 and val < .010 ): + return "Steady" + elif ( val >= .010 and val < .020 ): + return "Rising Slowly" + elif ( val >= .020 ): + return "Rising Rapidly" + else: + return "<<< UNKNOWN: %d >>>" % ( val ); + + + def _updateScreen(self): + if ( len(self.immediateError) > 0 ): + self.dataWin.erase() + self.dataWin.border(0) + self.dataWin.addstr( 2,3,self.immediateError) + elif ( len(self.xml)==0 ): + self.noDataCount+=1 + if ( self.noDataCount>10 ): + # Throw up an error + self.dataWin.erase() + self.dataWin.border(0) + self.dataWin.addstr( 2,3,"Error No data seen for %d seconds" % ( + self.noDataCount)) + self.dataWin.addstr( 3,3,self.fetcherError) + else: # We have data! + self.noDataCount = 0 + wxdata = objectify.fromstring(self.xml) + sensor = {} + trend = {} + for el in wxdata.location.observation.iterchildren(tag='sensor'): + sensor[el.get("type")] = el + for el in wxdata.location.observation.iterchildren(tag='trend'): + trend[el.get("type")] = el + self.titleWin.erase() + self.titleWin.border(0) + self.dataWin.erase() + self.dataWin.border(0) + + # Title window + observationTime = time.strftime("%a, %d %b %Y %H:%M %Z", time.localtime(int(wxdata.location.observation.get("secs")))) + titleStr = "%s, %s - %s " % (wxdata.location.get("city"), wxdata.location.get("state"), observationTime) + self.titleWin.addstr(1,(self.x-len(titleStr))/2, titleStr) + self.titleWin.refresh() + + # Outside temperature + self.dataWin.addstr( 1, 3, "Outside Temperature: " ) + self._showTemp( "%.1f", sensor["outsideTemperature"] ) + self._showTemp( " (%+.1f", trend["outsideTemperature"], "/hr)" ) + + # Outside humidity + self.dataWin.addstr( 2, 3, " Outside Humidity: " ) + self.dataWin.addstr( "%.1f%% (%+.1f%%/hr)" % ( sensor["outsideHumidity"], trend["outsideHumidity"] )) + + # Dew Point + self.dataWin.addstr( 3, 3, " Dew Point: ") + self._showTemp("%.1f", sensor["dewPoint"] ) + + # Apparent Temperature + self.dataWin.addstr( 4, 3, " Apparent Temp: ") + self._showTemp("%.1f", sensor["apparentTemp"] ) + + # Barometric Pressure + self.dataWin.addstr( 5, 3, "Barometric Pressure: ") + self.dataWin.addstr( "%.2f\"" % ( sensor["SLP"] )) + + # Barometric trend + self.dataWin.addstr( 6, 3, " Barometric Trend: ") + self.dataWin.addstr( "%+.03f\"/hr (%s)" % ( trend["altimeter"], + self._altimeterTrendToStr(trend["altimeter"]))) + + # Cloud height + self.dataWin.addstr( 7, 3, " Cloud height: ") + self.dataWin.addstr("%d ft" % ( sensor["cloudHeight"])) + + ### Wind + # Average Wind Speed + self.dataWin.addstr( 9, 3, "Wind Speed (2m avg): ") + self.dataWin.addstr( "%.1f mph" % (sensor["averageWindSpeed"])) + + # Average Wind Direction + self.dataWin.addstr( 10, 3, " Wind Direction: ") + self._showWindDir(sensor["averageWindDirection"]) + + # Wind Gust + self.dataWin.addstr( 11, 3, " Wind Gust: ") + self.dataWin.addstr( "%.1f mph" % (sensor["windGustSpeed"])) + + # Wind Gust Direction + self.dataWin.addstr( 12, 3, "Wind Gust Direction: ") + self._showWindDir(sensor["windGustDirection"]) + + # Instant Wind Speed + self.dataWin.addstr( 9, 40, " Instant Wind Speed: ") + self.dataWin.addstr( "%.1f mph" % (sensor["instantWindSpeed"])) + + # Instant Wind Direction + self.dataWin.addstr( 10, 40, " Instant Wind Dir: ") + self._showWindDir(sensor["instantWindDirection"]) + + ### Rain + # Daily rain and 24-hour rain + self.dataWin.addstr( 14, 3, " Daily Rain: %.2f\" 24-hour rain: %.2f\"" % ( sensor["dailyRain"] , sensor["rainLast24Hours"])) + + # Rain Rate and Instant Rain Rate + self.dataWin.addstr( 15, 3, " Rain Rate: %.2f\" Instant Rain Rate: %.2f\"/hr" % ( sensor["rainRate"], sensor["instantRainRate"] )) + + ### Solar and UV + # Solar Radiation + self.dataWin.addstr( 17, 3, " Solar Radiation: ") + self.dataWin.addstr( "%.1f W/m^2" % (sensor["solarRadiation"])) + + # Solar Percent + self.dataWin.addstr( 18, 3, " Solar Percent: ") + self.dataWin.addstr( "%d%%" % ( sensor["solarRadiationPercent"] )) + + # UV + self.dataWin.addstr( 19, 3, " UV: ") + self.dataWin.addstr( "%.1f" % ( sensor["UV"])) + self.dataWin.refresh() + +def main( stdscr ): + try: + WxProCurses( stdscr ) + except KeyboardInterrupt: + stdscr.clear() + stdscr.refresh() + curses.endwin() + +curses.wrapper(main) + + diff --git a/xml2obj.py b/xml2obj.py new file mode 100644 index 0000000..931d7e7 --- /dev/null +++ b/xml2obj.py @@ -0,0 +1,87 @@ +#!/usr/bin/python + +import re +import xml.sax.handler + +def xml2obj(src): + """ + A simple function to converts XML data into native Python object. + """ + + non_id_char = re.compile('[^_0-9a-zA-Z]') + def _name_mangle(name): + return non_id_char.sub('_', name) + + class DataNode(object): + def __init__(self): + self._attrs = {} # XML attributes and child elements + self.data = None # child text data + def __len__(self): + # treat single element as a list of 1 + return 1 + def __getitem__(self, key): + if isinstance(key, basestring): + return self._attrs.get(key,None) + else: + return [self][key] + def __contains__(self, name): + return self._attrs.has_key(name) + def __nonzero__(self): + return bool(self._attrs or self.data) + def __getattr__(self, name): + if name.startswith('__'): + # need to do this for Python special methods??? + raise AttributeError(name) + return self._attrs.get(name,None) + def _add_xml_attr(self, name, value): + if name in self._attrs: + # multiple attribute of the same name are represented by a list + children = self._attrs[name] + if not isinstance(children, list): + children = [children] + self._attrs[name] = children + children.append(value) + else: + self._attrs[name] = value + def __str__(self): + return self.data or '' + def __repr__(self): + items = sorted(self._attrs.items()) + if self.data: + items.append(('data', self.data)) + return u'{%s}' % ', '.join([u'%s:%s' % (k,repr(v)) for k,v in items]) + + class TreeBuilder(xml.sax.handler.ContentHandler): + def __init__(self): + self.stack = [] + self.root = DataNode() + self.current = self.root + self.text_parts = [] + def startElement(self, name, attrs): + self.stack.append((self.current, self.text_parts)) + self.current = DataNode() + self.text_parts = [] + # xml attributes --> python attributes + for k, v in attrs.items(): + self.current._add_xml_attr(_name_mangle(k), v) + def endElement(self, name): + text = ''.join(self.text_parts).strip() + if text: + self.current.data = text + if self.current._attrs: + obj = self.current + else: + # a text only node is simply represented by the string + obj = text or '' + self.current, self.text_parts = self.stack.pop() + self.current._add_xml_attr(_name_mangle(name), obj) + def characters(self, content): + self.text_parts.append(content) + + builder = TreeBuilder() + if isinstance(src,basestring): + xml.sax.parseString(src, builder) + else: + xml.sax.parse(src, builder) + return builder.root._attrs.values()[0] +