Skip to content

Commit

Permalink
Support Daylight sensor with additional state.status
Browse files Browse the repository at this point in the history
The GPS positon configuration in `config.lat` and `config.long` is automatically
extracted from the discovery server response (based on IP).

TODO check if it can be already set via REST-API too.

`state.status` represents the current phase of the day/night and might be
used in rules for finer control as `state.delight` provides.

**Values**

    DL_NADIR          100
    DL_NIGHT_END      110
    DL_NAUTICAL_DAWN  120
    DL_DAWN           130
    DL_SUNRISE_START  140
    DL_SUNRISE_END    150
    DL_GOLDENHOUR1    160
    DL_SOLAR_NOON     170
    DL_GOLDENHOUR2    180
    DL_SUNSET_START   190
    DL_SUNSET_END     200
    DL_DUSK           210
    DL_NAUTICAL_DUSK  220
    DL_NIGHT_START    230

`state.status` and `state.daylight` do notify changed values via events.
  • Loading branch information
manup committed Mar 10, 2018
1 parent ac5bc87 commit 0fc795a
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 4 deletions.
17 changes: 16 additions & 1 deletion database.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 dresden elektronik ingenieurtechnik gmbh.
* Copyright (c) 2016-2018 dresden elektronik ingenieurtechnik gmbh.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
Expand Down Expand Up @@ -1871,6 +1871,11 @@ static int sqliteLoadAllSensorsCallback(void *user, int ncols, char **colval , c
quint8 endpoint = sensor.fingerPrint().endpoint;
DBG_Printf(DBG_INFO_L2, "DB found sensor %s %s\n", qPrintable(sensor.name()), qPrintable(sensor.id()));

if (!isClip && sensor.type() == QLatin1String("Daylight"))
{
isClip = true;
}

if (isClip)
{
ok = true;
Expand Down Expand Up @@ -2124,6 +2129,16 @@ static int sqliteLoadAllSensorsCallback(void *user, int ncols, char **colval , c
item->setValue(0);
}
}
else if (sensor.type() == QLatin1String("Daylight"))
{
d->daylightSensorId = sensor.id();
sensor.removeItem(RConfigReachable);
sensor.addItem(DataTypeBool, RConfigConfigured);
sensor.addItem(DataTypeString, RConfigLat);
sensor.addItem(DataTypeString, RConfigLong);
sensor.addItem(DataTypeBool, RStateDaylight);
sensor.addItem(DataTypeInt32, RStateStatus);
}

if (sensor.modelId().startsWith(QLatin1String("RWL02"))) // Hue dimmer switch
{
Expand Down
114 changes: 114 additions & 0 deletions daylight.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright (c)2018 dresden elektronik ingenieurtechnik gmbh.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
*/

#include <QtMath>
#include <QVariantMap>
#include "daylight.h"

// Qt/C++ port of:
// https://github.com/mourner/suncalc/blob/master/suncalc.js

static const qreal dayMs = 1000 * 60 * 60 * 24;
static const qreal J1970 = 2440588;
static const qreal J2000 = 2451545;
static const double rad = M_PI / 180;
static const double e = rad * 23.4397; // obliquity of the Earth

static double toJulian(double msecSinceEpoch) { return msecSinceEpoch / dayMs - 0.5 + J1970; }
static double fromJulian(double j) { return (j + 0.5 - J1970) * dayMs; }
static double toDays(double msecSinceEpoch) { return toJulian(msecSinceEpoch) - J2000; }
static double declination(double l, double b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }

// general sun calculations

static double solarMeanAnomaly(double d) { return rad * (357.5291 + 0.98560028 * d); }

static double eclipticLongitude(double M) {

double C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)); // equation of center
double P = rad * 102.9372; // perihelion of the Earth

return M + C + P + M_PI;
}

// calculations for sun times

static const double J0 = 0.0009;

static double julianCycle(double d, double lw) { return round(d - J0 - lw / (2 * M_PI)); }

static double approxTransit(double Ht, double lw, double n) { return J0 + (Ht + lw) / (2 * M_PI) + n; }
static double solarTransitJ(double ds, double M, double L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }

static double hourAngle(double h, double phi, double d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }

// returns set time for the given sun altitude
static double getSetJ(double h, double lw, double phi, double dec, double n, double M, double L) {

double w = hourAngle(h, phi, dec);
double a = approxTransit(w, lw, n);
return solarTransitJ(a, M, L);
}

struct TimePin {
double offset;
const char *first;
int firstWeight;
const char *second;
int secondWeight;
};

// calculates sun times for a given date and latitude/longitude

void getDaylightTimes(quint64 msecSinceEpoch, double lat, double lng, std::vector<DL_Result> &result)
{
std::vector<TimePin> times;
// sun times configuration (angle, morning name, evening name)
times.push_back({-0.833, "sunriseStart", DL_SUNRISE_START, "sunsetEnd", DL_SUNSET_END});
times.push_back({-0.3, "sunriseEnd", DL_SUNRISE_END, "sunsetStart", DL_SUNSET_START});
times.push_back({-6, "dawn", DL_DAWN, "dusk", DL_DUSK});
times.push_back({-12, "nauticalDawn", DL_NAUTICAL_DAWN, "nauticalDusk", DL_NAUTICAL_DUSK});
times.push_back({-18, "nightEnd", DL_NIGHT_END, "nightStart", DL_NIGHT_START});
times.push_back({6, "goldenHour1", DL_GOLDENHOUR1, "goldenHour2", DL_GOLDENHOUR2});

double lw = rad * -lng,
phi = rad * lat,

d = toDays(msecSinceEpoch),
n = julianCycle(d, lw),
ds = approxTransit(0, lw, n),

M = solarMeanAnomaly(ds),
L = eclipticLongitude(M),
dec = declination(L, 0),

Jnoon = solarTransitJ(ds, M, L),

Jset, Jrise;


result.push_back({"solarNoon", DL_SOLAR_NOON, (quint64)fromJulian(Jnoon)});
result.push_back({"nadir", DL_NADIR, (quint64)fromJulian(Jnoon - 0.5)});

for (const TimePin &time : times)
{

Jset = getSetJ(time.offset * rad, lw, phi, dec, n, M, L);
Jrise = Jnoon - (Jset - Jnoon);

result.push_back({time.first, time.firstWeight, (quint64)fromJulian(Jrise)});
result.push_back({time.second, time.secondWeight, (quint64)fromJulian(Jset)});
}

std::sort(result.begin(), result.end(),
[](const DL_Result &a, const DL_Result &b)
{ return a.msecsSinceEpoch < b.msecsSinceEpoch; });
}

37 changes: 37 additions & 0 deletions daylight.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c)2018 dresden elektronik ingenieurtechnik gmbh.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
*/

#ifndef DAYLIGHT_H
#define DAYLIGHT_H

#define DL_NADIR 100
#define DL_NIGHT_END 110
#define DL_NAUTICAL_DAWN 120
#define DL_DAWN 130
#define DL_SUNRISE_START 140
#define DL_SUNRISE_END 150
#define DL_GOLDENHOUR1 160
#define DL_SOLAR_NOON 170
#define DL_GOLDENHOUR2 180
#define DL_SUNSET_START 190
#define DL_SUNSET_END 200
#define DL_DUSK 210
#define DL_NAUTICAL_DUSK 220
#define DL_NIGHT_START 230

struct DL_Result {
const char *name;
int weight;
quint64 msecsSinceEpoch;
};

void getDaylightTimes(quint64 msecSinceEpoch, double lat, double lng, std::vector<DL_Result> &result);

#endif // DAYLIGHT_H
2 changes: 2 additions & 0 deletions de_web.pro
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ QMAKE_CXXFLAGS += -Wno-attributes
HEADERS = bindings.h \
connectivity.h \
colorspace.h \
daylight.h \
de_web_plugin.h \
de_web_plugin_private.h \
de_web_widget.h \
Expand Down Expand Up @@ -106,6 +107,7 @@ SOURCES = authentification.cpp \
connectivity.cpp \
colorspace.cpp \
database.cpp \
daylight.cpp \
discovery.cpp \
de_web_plugin.cpp \
de_web_widget.cpp \
Expand Down
3 changes: 3 additions & 0 deletions de_web_plugin_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,7 @@ public Q_SLOTS:
void internetDiscoveryTimerFired();
void internetDiscoveryFinishedRequest(QNetworkReply *reply);
void internetDiscoveryExtractVersionInfo(QNetworkReply *reply);
void internetDiscoveryExtractGeo(QNetworkReply *reply);
void inetProxyHostLookupDone(const QHostInfo &host);
void inetProxyCheckHttpVia(const QString &via);
void scheduleTimerFired();
Expand All @@ -857,6 +858,7 @@ public Q_SLOTS:
void verifyRuleBindingsTimerFired();
void indexRulesTriggers();
void fastRuleCheckTimerFired();
void daylightTimerFired();
void handleRuleEvent(const Event &e);
void queueBindingTask(const BindingTask &bindingTask);
void restartAppTimerFired();
Expand Down Expand Up @@ -1436,6 +1438,7 @@ public Q_SLOTS:
std::vector<Group> groups;
std::vector<LightNode> nodes;
std::vector<Rule> rules;
QString daylightSensorId;
std::vector<Sensor> sensors;
std::list<TaskItem> tasks;
std::list<TaskItem> runningTasks;
Expand Down
49 changes: 49 additions & 0 deletions discovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ void DeRestPluginPrivate::internetDiscoveryFinishedRequest(QNetworkReply *reply)
}
gwAnnounceVital++;
DBG_Printf(DBG_INFO, "Announced to internet\n");
internetDiscoveryExtractGeo(reply);
#ifdef ARCH_ARM
// currently this is only supported for the RaspBee Gateway
internetDiscoveryExtractVersionInfo(reply);
Expand Down Expand Up @@ -363,6 +364,54 @@ void DeRestPluginPrivate::internetDiscoveryExtractVersionInfo(QNetworkReply *rep
}
}

/*! Extracts the geo information.
\param reply from discovery server
*/
void DeRestPluginPrivate::internetDiscoveryExtractGeo(QNetworkReply *reply)
{
// for (const QByteArray &hdr : reply->rawHeaderList())
// {
// DBG_Printf(DBG_INFO, "hdr: %s: %s\n", qPrintable(hdr), qPrintable(reply->rawHeader(hdr)));
// }

if (reply->hasRawHeader("X-AppEngine-CityLatLong"))
{
QList<QByteArray> ll = reply->rawHeader("X-AppEngine-CityLatLong").split(',');
if (ll.size() != 2)
{
// no geo information available
return;
}

Sensor *sensor = getSensorNodeForId(daylightSensorId);
DBG_Assert(sensor != 0);
if (!sensor)
{
return;
}

ResourceItem *configured = sensor->item(RConfigConfigured);
ResourceItem *lat = sensor->item(RConfigLat);
ResourceItem *lon = sensor->item(RConfigLong);

DBG_Assert(configured && lat && lon);
if (!configured || !lat || !lon)
{
return;
}

if (!configured->toBool() || !configured->lastSet().isValid())
{
configured->setValue(true);
lat->setValue(QString(ll[0]));
lon->setValue(QString(ll[1]));
sensor->setNeedSaveDatabase(true);
queSaveDb(DB_SENSORS, DB_SHORT_SAVE_DELAY);
}
}
}

/*! Finished Lookup of http proxy IP address.
\param host holds the proxy host info
Expand Down
Loading

0 comments on commit 0fc795a

Please sign in to comment.