44 m_lastRequestTime = std::chrono::system_clock::now();
67 if (wfAPIKeyTP[0].isEmpty())
69 LOG_ERROR(
"WeatherFlow API Key is not available. Please register your API key at "
70 "https://tempestwx.com/settings/tokens and save it under Options.");
75 auto now = std::chrono::system_clock::now();
76 auto timeSinceLastRequest = std::chrono::duration_cast<std::chrono::seconds>(now - m_lastRequestTime).count();
78 if (timeSinceLastRequest >= RATE_LIMIT_PERIOD)
81 m_lastRequestTime = now;
84 if (m_requestCount >= RATE_LIMIT_REQUESTS)
86 LOG_ERROR(
"API rate limit exceeded. Please wait before making more requests.");
91 if (wfStationIDTP[0].isEmpty())
93 if (!fetchStationInfo())
95 LOG_ERROR(
"Failed to fetch station information. Please provide a station ID manually.");
101 m_stationID = wfStationIDTP[0].
getText();
104 m_isConnected =
true;
105 LOG_INFO(
"WeatherFlow connection established successfully.");
111 m_isConnected =
false;
112 LOG_INFO(
"WeatherFlow disconnected.");
121 wfAPIKeyTP[0].
fill(
"API_KEY",
"API Key",
"");
126 wfStationIDTP[0].
fill(
"STATION_ID",
"Station ID",
"");
128 wfStationIDTP.
load();
131 wfSettingsNP[WF_UPDATE_INTERVAL].
fill(
"UPDATE_INTERVAL",
"Update Interval (s)",
"%.0f", 30, 3600, 30, 60);
132 wfSettingsNP[WF_CONNECTION_TIMEOUT].
fill(
"CONNECTION_TIMEOUT",
"Connection Timeout (s)",
"%.0f", 5, 120, 5, 30);
133 wfSettingsNP[WF_RETRY_ATTEMPTS].
fill(
"RETRY_ATTEMPTS",
"Retry Attempts",
"%.0f", 1, 10, 1, 3);
137 addParameter(
"WEATHER_TEMPERATURE",
"Temperature (°C)", -40, 50, 15);
138 addParameter(
"WEATHER_HUMIDITY",
"Humidity (%)", 0, 100, 15);
139 addParameter(
"WEATHER_PRESSURE",
"Pressure (hPa)", 800, 1200, 15);
140 addParameter(
"WEATHER_WIND_SPEED",
"Wind Speed (m/s)", 0, 30, 15);
141 addParameter(
"WEATHER_WIND_GUST",
"Wind Gust (m/s)", 0, 50, 15);
142 addParameter(
"WEATHER_WIND_DIRECTION",
"Wind Direction (degrees)", 0, 360, 15);
143 addParameter(
"WEATHER_RAIN_HOUR",
"Rain Accumulation (mm)", 0, 200, 15);
144 addParameter(
"WEATHER_RAIN_RATE",
"Rain Rate (mm/hr)", 0, 100, 15);
145 addParameter(
"WEATHER_SOLAR_RADIATION",
"Solar Radiation (W/m²)", 0, 1200, 15);
168 wfAPIKeyTP.
update(texts, names, n);
177 wfStationIDTP.
update(texts, names, n);
179 wfStationIDTP.
apply();
194 wfSettingsNP.
update(values, names, n);
196 wfSettingsNP.
apply();
211 LOGF_DEBUG(
"Location updated: Lat %.6f, Lon %.6f", latitude, longitude);
220 LOG_ERROR(
"WeatherFlow is not connected.");
225 auto now = std::chrono::system_clock::now();
226 auto timeSinceLastRequest = std::chrono::duration_cast<std::chrono::seconds>(now - m_lastRequestTime).count();
228 if (timeSinceLastRequest >= RATE_LIMIT_PERIOD)
231 m_lastRequestTime = now;
234 if (m_requestCount >= RATE_LIMIT_REQUESTS)
236 LOG_ERROR(
"API rate limit exceeded. Please wait before making more requests.");
241 if (!fetchCurrentObservations())
243 LOG_ERROR(
"Failed to fetch weather observations.");
260 LOGF_DEBUG(
"Weather data updated: Temp=%.1f°C, Humidity=%.1f%%, Wind=%.1fm/s",
261 m_lastData.air_temperature, m_lastData.relative_humidity, m_lastData.wind_avg);
271 wfStationIDTP.
save(fp);
272 wfSettingsNP.
save(fp);
277bool WeatherFlow::fetchStationInfo()
279 std::string response;
280 std::string endpoint = STATIONS_ENDPOINT +
"?token=" + wfAPIKeyTP[0].
getText();
282 if (!makeAPIRequest(endpoint, response))
284 LOG_ERROR(
"Failed to fetch station information from WeatherFlow API.");
288 return parseStationResponse(response);
291bool WeatherFlow::fetchCurrentObservations()
293 std::string response;
294 std::string endpoint;
298 endpoint = OBSERVATIONS_ENDPOINT + m_stationID +
"?token=" + wfAPIKeyTP[0].
getText();
300 if (!makeAPIRequest(endpoint, response))
302 LOG_ERROR(
"Failed to fetch current observations from WeatherFlow API.");
306 return parseObservationsResponse(response);
309bool WeatherFlow::parseStationResponse(
const std::string &response)
313 json stationData = json::parse(response);
315 if (stationData.
contains(
"status") && stationData[
"status"][
"status_code"] == 0)
317 if (stationData.
contains(
"stations") && !stationData[
"stations"].empty())
319 auto station = stationData[
"stations"][0];
320 m_stationID = std::to_string(station[
"station_id"].get<int>());
322 if (station.contains(
"devices") && !station[
"devices"].empty())
324 auto device = station[
"devices"][0];
325 m_deviceID = std::to_string(
device[
"device_id"].get<int>());
328 LOGF_INFO(
"Auto-detected station ID: %s, device ID: %s", m_stationID.c_str(), m_deviceID.c_str());
334 std::string errorMsg =
"Unknown error";
335 if (stationData.
contains(
"status") && stationData[
"status"].contains(
"status_message"))
337 errorMsg = stationData[
"status"][
"status_message"].
get<std::string>();
339 LOGF_ERROR(
"WeatherFlow API error: %s", errorMsg.c_str());
350bool WeatherFlow::parseObservationsResponse(
const std::string &response)
354 json obsData = json::parse(response);
356 if (obsData.
contains(
"status") && obsData[
"status"][
"status_code"] == 0)
358 if (obsData.
contains(
"obs") && !obsData[
"obs"].empty())
360 auto observation = obsData[
"obs"][0];
363 auto extractDouble = [&observation](
const std::string & key,
double defaultValue = 0.0) ->
double
365 if (observation.contains(key) && !observation[key].is_null())
367 return observation[key].
get<
double>();
372 m_lastData.air_temperature = extractDouble(
"air_temperature");
373 m_lastData.relative_humidity = extractDouble(
"relative_humidity");
374 m_lastData.barometric_pressure = extractDouble(
"barometric_pressure");
375 m_lastData.wind_avg = extractDouble(
"wind_avg");
376 m_lastData.wind_gust = extractDouble(
"wind_gust");
377 m_lastData.wind_direction = extractDouble(
"wind_direction");
378 m_lastData.precip_accum_local_day = extractDouble(
"precip_accum_local_day");
379 m_lastData.precip_rate = extractDouble(
"precip_rate");
380 m_lastData.solar_radiation = extractDouble(
"solar_radiation");
381 m_lastData.uv = extractDouble(
"uv");
382 m_lastData.timestamp = std::chrono::system_clock::now();
389 std::string errorMsg =
"Unknown error";
390 if (obsData.
contains(
"status") && obsData[
"status"].contains(
"status_message"))
392 errorMsg = obsData[
"status"][
"status_message"].
get<std::string>();
394 LOGF_ERROR(
"WeatherFlow API error: %s", errorMsg.c_str());
405bool WeatherFlow::makeAPIRequest(
const std::string &endpoint, std::string &response)
407 return retryRequest([
this, &endpoint, &response]() ->
bool
412 cli.set_connection_timeout(
static_cast<int>(wfSettingsNP[WF_CONNECTION_TIMEOUT].getValue()));
413 cli.set_read_timeout(
static_cast<int>(wfSettingsNP[WF_CONNECTION_TIMEOUT].getValue()));
417 headers.emplace(
"Authorization",
"Bearer " + std::string(wfAPIKeyTP[0].
getText()));
418 headers.emplace(
"Content-Type",
"application/json");
419 headers.emplace(
"User-Agent",
"INDI-WeatherFlow/1.0");
421 auto result = cli.Get(endpoint, headers);
430 if (result->status != 200)
436 response = result->body;
440 m_lastRequestTime = std::chrono::system_clock::now();
444 catch (
const std::exception &e)
452bool WeatherFlow::retryRequest(
const std::function <
bool() > &request)
454 int maxRetries =
static_cast < int > (wfSettingsNP[WF_RETRY_ATTEMPTS].getValue());
456 for (
int attempt = 0; attempt <= maxRetries; ++attempt)
463 if (attempt < maxRetries)
465 int delaySeconds = (1 << attempt);
466 LOGF_DEBUG(
"Request failed, retrying in %d seconds (attempt %d/%d)...",
467 delaySeconds, attempt + 1, maxRetries + 1);
468 std::this_thread::sleep_for(std::chrono::seconds(delaySeconds));
INDI::PropertyText getText(const char *name) const
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
void defineProperty(INumberVectorProperty *property)
void addDebugControl()
Add Debug control to the driver.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool load()
load Attempt to load property values from configuration file.
void save(FILE *f) const
save Save property to configuration file.
bool isNameMatch(const char *otherName) const
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool update(const char *const texts[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
INDI::PropertyViewText * getText() const
void addParameter(std::string name, std::string label, double numMinOk, double numMaxOk, double percWarning, bool flipWarning=false)
Add a physical weather measurable parameter to the weather driver.
bool setCriticalParameter(std::string name)
setCriticalParameter Set parameter that is considered critical to the operation of the observatory....
void setParameterValue(std::string name, double value)
setParameterValue Update weather parameter value
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
void setWeatherConnection(const uint8_t &value)
setWeatherConnection Set Weather connection mode. Child class should call this in the constructor bef...
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual IPState updateWeather() override
updateWeather Update weather conditions from device or service. The function should not change the st...
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Update weather station location.
const char * getDefaultName() override
bool Disconnect() override
Disconnect from device.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
a class to store JSON values
bool contains(const typename object_t::key_type &key) const
check the existence of an element in a JSON object
auto get() const noexcept(noexcept(std::declval< const basic_json_t & >().template get_impl< ValueType >(detail::priority_tag< 4 > {}))) -> decltype(std::declval< const basic_json_t & >().template get_impl< ValueType >(detail::priority_tag< 4 > {}))
get a (pointer) value (explicit)
general exception of the basic_json class
const char * what() const noexcept override
returns the explanatory string
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
#define LOGF_INFO(fmt,...)
#define LOGF_DEBUG(fmt,...)
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
#define LOGF_ERROR(fmt,...)
std::string to_string(Error error)
std::unordered_multimap< std::string, std::string, detail::case_ignore::hash, detail::case_ignore::equal_to > Headers
std::unique_ptr< WeatherFlow > weatherFlow(new WeatherFlow())