Rewrite sun component calculations (#1661)

This commit is contained in:
Otto Winter
2021-04-07 12:16:36 +02:00
committed by GitHub
parent e0c5b45694
commit 7964b724ed
4 changed files with 391 additions and 216 deletions
+47 -3
View File
@@ -1,3 +1,5 @@
import re
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
@@ -22,7 +24,7 @@ CONF_ON_SUNSET = "on_sunset"
# Default sun elevation is a bit below horizon because sunset
# means time when the entire sun disk is below the horizon
DEFAULT_ELEVATION = -0.883
DEFAULT_ELEVATION = -0.83333
ELEVATION_MAP = {
"sunrise": 0.0,
@@ -45,12 +47,54 @@ def elevation(value):
return cv.float_range(min=-180, max=180)(value)
# Parses sexagesimal values like 22°57′7″S
LAT_LON_REGEX = re.compile(
r"([+\-])?\s*"
r"(?:([0-9]+)\s*°)?\s*"
r"(?:([0-9]+)\s*[′\'])?\s*"
r'(?:([0-9]+)\s*[″"])?\s*'
r"([NESW])?"
)
def parse_latlon(value):
if isinstance(value, str) and value.endswith("°"):
# strip trailing degree character
value = value[:-1]
try:
return cv.float_(value)
except cv.Invalid:
pass
value = cv.string_strict(value)
m = LAT_LON_REGEX.match(value)
if m is None:
raise cv.Invalid("Invalid format for latitude/longitude")
sign = m.group(1)
deg = m.group(2)
minute = m.group(3)
second = m.group(4)
d = m.group(5)
val = float(deg or 0) + float(minute or 0) / 60 + float(second or 0) / 3600
if sign == "-":
val *= -1
if d and d in "SW":
val *= -1
return val
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(Sun),
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
cv.Required(CONF_LATITUDE): cv.float_range(min=-90, max=90),
cv.Required(CONF_LONGITUDE): cv.float_range(min=-180, max=180),
cv.Required(CONF_LATITUDE): cv.All(
parse_latlon, cv.float_range(min=-90, max=90)
),
cv.Required(CONF_LONGITUDE): cv.All(
parse_latlon, cv.float_range(min=-180, max=180)
),
cv.Optional(CONF_ON_SUNRISE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SunTrigger),
File diff suppressed because it is too large Load Diff
+50 -63
View File
@@ -8,85 +8,72 @@
namespace esphome {
namespace sun {
namespace internal {
/* Usually, ESPHome uses single-precision floating point values
* because those tend to be accurate enough and are more efficient.
*
* However, some of the data in this class has to be quite accurate, so double is
* used everywhere.
*/
using num_t = double;
struct GeoLocation {
num_t latitude;
num_t longitude;
num_t latitude_rad() const;
num_t longitude_rad() const;
};
struct Moment {
time::ESPTime dt;
num_t jd() const;
num_t jde() const;
};
struct EquatorialCoordinate {
num_t right_ascension;
num_t declination;
num_t right_ascension_rad() const;
num_t declination_rad() const;
};
struct HorizontalCoordinate {
num_t elevation;
num_t azimuth;
num_t elevation_rad() const;
num_t azimuth_rad() const;
};
} // namespace internal
class Sun {
public:
void set_time(time::RealTimeClock *time) { time_ = time; }
time::RealTimeClock *get_time() const { return time_; }
void set_latitude(double latitude) { latitude_ = latitude; }
void set_longitude(double longitude) { longitude_ = longitude; }
void set_latitude(double latitude) { location_.latitude = latitude; }
void set_longitude(double longitude) { location_.longitude = longitude; }
optional<time::ESPTime> sunrise(double elevation = 0.0);
optional<time::ESPTime> sunset(double elevation = 0.0);
optional<time::ESPTime> sunrise(double elevation);
optional<time::ESPTime> sunset(double elevation);
double elevation();
double azimuth();
protected:
double current_sun_time_() { return this->calc_sun_time_(this->time_->utcnow()); }
/** Calculate the declination of the sun in rad.
*
* See https://en.wikipedia.org/wiki/Position_of_the_Sun#Declination_of_the_Sun_as_seen_from_Earth
*
* Accuracy: ±0.2°
*
* @param sun_time The day of the year, 1 means January 1st. See calc_sun_time_.
* @return Sun declination in degrees
*/
double sun_declination_(double sun_time);
double elevation_ratio_(double sun_time);
/** Calculate the hour angle based on the sun time of day in hours.
*
* Positive in morning, 0 at noon, negative in afternoon.
*
* @param sun_time Sun time, see calc_sun_time_.
* @return Hour angle in rad.
*/
double hour_angle_(double sun_time);
double elevation_(double sun_time);
double elevation_rad_(double sun_time);
double zenith_rad_(double sun_time);
double azimuth_rad_(double sun_time);
double azimuth_(double sun_time);
/** Return the sun time given by the time_ object.
*
* Sun time is defined as doubleing point day of year.
* Integer part encodes the day of the year (1=January 1st)
* Decimal part encodes time of day (1/24 = 1 hour)
*/
double calc_sun_time_(const time::ESPTime &time);
uint32_t calc_epoch_(time::ESPTime base, double sun_time);
/** Calculate the sun time of day
*
* @param day_of_year
* @param elevation
* @param rising
* @return
*/
double sun_time_for_elevation_(int32_t day_of_year, double elevation, bool rising);
double latitude_rad_();
internal::HorizontalCoordinate calc_coords_();
optional<time::ESPTime> calc_event_(bool rising, double zenith);
time::RealTimeClock *time_;
/// Latitude in degrees, range: -90 to 90.
double latitude_;
/// Longitude in degrees, range: -180 to 180.
double longitude_;
internal::GeoLocation location_;
};
class SunTrigger : public Trigger<>, public PollingComponent, public Parented<Sun> {
public:
SunTrigger() : PollingComponent(1000) {}
SunTrigger() : PollingComponent(60000) {}
void set_sunrise(bool sunrise) { sunrise_ = sunrise; }
void set_elevation(double elevation) { elevation_ = elevation; }
+1
View File
@@ -405,6 +405,7 @@ ARDUINO_FORBIDDEN_RE = r"[^\w\d](" + r"|".join(ARDUINO_FORBIDDEN) + r")\(.*"
include=cpp_include,
exclude=[
"esphome/components/mqtt/custom_mqtt_device.h",
"esphome/components/sun/sun.cpp",
"esphome/core/esphal.*",
],
)