mirror of
https://github.com/esphome/esphome.git
synced 2026-09-28 16:14:11 +08:00
Rewrite sun component calculations (#1661)
This commit is contained in:
@@ -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),
|
||||
|
||||
+293
-150
File diff suppressed because it is too large
Load Diff
@@ -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; }
|
||||
|
||||
@@ -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.*",
|
||||
],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user