Instrument Neutral Distributed Interface INDI 2.2.5
indiweatherinterface.cpp
Go to the documentation of this file.
1/*
2 Weather Interface
3 Copyright (C) 2018 Jasem Mutlaq ([email protected])
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19*/
20
22
23#include "indilogger.h"
24
25#include <cstring>
26
27namespace INDI
28{
29
30#define getDeviceName m_defaultDevice->getDeviceName
31
32WeatherInterface::WeatherInterface(DefaultDevice *defaultDevice) : m_defaultDevice(defaultDevice)
33{
34 m_UpdateTimer.callOnTimeout(std::bind(&WeatherInterface::checkWeatherUpdate, this));
35 m_UpdateTimer.setSingleShot(true);
36 m_UpdateTimer.setInterval(60000);
37}
38
42
43void WeatherInterface::initProperties(const char *statusGroup, const char *paramsGroup)
44{
45 m_ParametersGroup = paramsGroup;
46
47 // Update Period
48 // @INDI_STANDARD_PROPERTY@
49 UpdatePeriodNP[0].fill("PERIOD", "Period (s)", "%.f", 0, 3600, 60, 60);
50 UpdatePeriodNP.fill(getDeviceName(), "WEATHER_UPDATE", "Update", statusGroup,
51 IP_RW, 60, IPS_IDLE);
52
53 // Refresh
54 // @INDI_STANDARD_PROPERTY@
55 RefreshSP[0].fill("REFRESH", "Refresh", ISS_OFF);
56 RefreshSP.fill(getDeviceName(), "WEATHER_REFRESH", "Weather", statusGroup, IP_RW, ISR_ATMOST1, 0,
57 IPS_IDLE);
58
59 // Override
60 // @INDI_STANDARD_PROPERTY@
61 OverrideSP[0].fill("OVERRIDE", "Override Status", ISS_OFF);
62 OverrideSP.fill(getDeviceName(), "WEATHER_OVERRIDE", "Safety", statusGroup, IP_RW,
64
65 // Parameters
66 // @INDI_STANDARD_PROPERTY@
67 ParametersNP.fill(getDeviceName(), "WEATHER_PARAMETERS", "Parameters", paramsGroup, IP_RO, 60, IPS_OK);
68
69 // Weather Status
70 // @INDI_STANDARD_PROPERTY@
71 critialParametersLP.fill(getDeviceName(), "WEATHER_STATUS", "Status", statusGroup, IPS_IDLE);
72
73 // Safety Status (standard property for safety monitoring)
74 // @INDI_STANDARD_PROPERTY@
75 SafetyStatusLP[0].fill("SAFETY", "Safety", IPS_IDLE);
76 SafetyStatusLP.fill(getDeviceName(), "SAFETY_STATUS", "Safety", statusGroup, IPS_IDLE);
77}
78
80{
81 if (m_defaultDevice->isConnected())
82 {
83 m_defaultDevice->defineProperty(UpdatePeriodNP);
84 m_defaultDevice->defineProperty(RefreshSP);
85 m_defaultDevice->defineProperty(OverrideSP);
86
87 if (critialParametersLP.count() > 0)
88 m_defaultDevice->defineProperty(critialParametersLP);
89
90 m_defaultDevice->defineProperty(SafetyStatusLP);
91
92 if (ParametersNP.count() > 0)
93 m_defaultDevice->defineProperty(ParametersNP);
94
95 for (auto &oneProperty : ParametersRangeNP)
96 m_defaultDevice->defineProperty(oneProperty);
97
99 }
100 else
101 {
102 m_defaultDevice->deleteProperty(UpdatePeriodNP);
103 m_defaultDevice->deleteProperty(RefreshSP);
104 m_defaultDevice->deleteProperty(OverrideSP);
105
106 if (critialParametersLP.count() > 0)
107 m_defaultDevice->deleteProperty(critialParametersLP);
108
109 m_defaultDevice->deleteProperty(SafetyStatusLP);
110
111 if (ParametersNP.count() > 0)
112 m_defaultDevice->deleteProperty(ParametersNP);
113
114 if (ParametersRangeNP.size() > 0)
115 {
116 for (auto &oneProperty : ParametersRangeNP)
117 m_defaultDevice->deleteProperty(oneProperty);
118 }
119 }
120
121 return true;
122}
123
125{
126 if (!m_defaultDevice->isConnected())
127 return;
128
129 IPState state = updateWeather();
130
131 switch (state)
132 {
133 // Ok
134 case IPS_OK:
136
137 // Override weather state if required - checked unconditionally so it is
138 // always enforced even when critical parameter states have not changed.
139 if (OverrideSP[0].getState() == ISS_ON)
140 {
142
143 // Update SafetyStatusLP to match override status (only if different)
145 {
148 }
149 }
150
151 // If no critical parameters are defined, syncCriticalParameters() returns
152 // early without ever touching SafetyStatusLP. Ensure it still reflects
153 // that the weather reading itself is OK so safety monitors see green.
155 {
158 }
159
160 // Always push the light property so clients stay in sync (e.g. after a
161 // reconnect where preStates matched and syncCriticalParameters returned false).
162 if (critialParametersLP.count() > 0)
164
165 ParametersNP.setState(state);
167
168 // If update period is set, then set up the timer
169 if (UpdatePeriodNP[0].getValue() > 0)
170 m_UpdateTimer.start(UpdatePeriodNP[0].getValue() * 1000);
171
172 return;
173
174 // Alert
175 // We retry every 5000 ms until we get OK
176 case IPS_ALERT:
177 ParametersNP.setState(state);
179 break;
180
181 // Weather update is in progress - only transition the state once to IPS_BUSY
182 // so clients know an update is underway, without spamming them on every 5s retry.
183 default:
185 {
188 }
189 break;
190 }
191
192 m_UpdateTimer.start(5000);
193}
194
195bool WeatherInterface::processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
196{
197 INDI_UNUSED(dev);
198
199 // Refresh
200 if (RefreshSP.isNameMatch(name))
201 {
205
207 return true;
208 }
209
210 // Override
211 if (OverrideSP.isNameMatch(name))
212 {
213 OverrideSP.update(states, names, n);
214 if (OverrideSP[0].getState() == ISS_ON)
215 {
216 LOG_WARN("Weather override is enabled. Observatory is not safe. Turn off override as soon as possible.");
220
221 // Update SafetyStatusLP to match override status (only if different)
223 {
226 }
227 }
228 else
229 {
230 LOG_INFO("Weather override is disabled");
232
235 }
236
238 return true;
239 }
240
241 return false;
242}
243
244bool WeatherInterface::processNumber(const char *dev, const char *name, double values[], char *names[], int n)
245{
246 INDI_UNUSED(dev);
247
248 // Update period
249 if (UpdatePeriodNP.isNameMatch(name))
250 {
251 m_defaultDevice->updateProperty(UpdatePeriodNP, values, names, n, [this, values]()
252 {
253 if (values[0] == 0)
254 LOG_INFO("Periodic updates are disabled.");
255 else
256 {
257 m_UpdateTimer.setInterval(values[0] * 1000);
258 m_UpdateTimer.start();
259 }
260 return true;
261 }, true);
262 return true;
263 }
264 else
265 {
266 // Parameter ranges
267 for (auto &oneRange : ParametersRangeNP)
268 {
269 if (oneRange.isNameMatch(name))
270 {
271 m_defaultDevice->updateProperty(oneRange, values, names, n, [this, &oneRange, values, names, n]()
272 {
273 oneRange.update(values, names, n);
276 return true;
277 }, true);
278 return true;
279 }
280 }
281 }
282
283 return false;
284}
285
287{
288 LOG_ERROR("updateWeather() must be implemented in Weather device child class to update weather properties.");
289 return IPS_ALERT;
290}
291
313void WeatherInterface::addParameter(std::string name, std::string label, double numMinOk, double numMaxOk,
314 double percWarning, bool flipWarning)
315{
316 LOGF_DEBUG("Parameter %s is added. Ok (%.2f,%.2f,%.2f,%s) ", name.c_str(), numMinOk, numMaxOk, percWarning,
317 (flipWarning ? "true" : "false"));
318
319 INDI::WidgetNumber oneParameter;
320 oneParameter.fill(name.c_str(), label.c_str(), "%.2f", numMinOk, numMaxOk, 0, 0);
321 ParametersNP.push(std::move(oneParameter));
322
323 if (numMinOk != numMaxOk)
324 createParameterRange(name, label, numMinOk, numMaxOk, percWarning, flipWarning);
325}
326
332void WeatherInterface::setParameterValue(std::string name, double value)
333{
334 auto oneParameter = ParametersNP.findWidgetByName(name.c_str());
335 if (oneParameter)
336 oneParameter->setValue(value);
337}
338
349{
350 auto oneParameter = ParametersNP.findWidgetByName(name.c_str());
351 if (!oneParameter)
352 {
353 LOGF_WARN("Unable to find parameter %s in list of existing parameters!", name.c_str());
354 return false;
355 }
356
357 INDI::WidgetLight oneLight;
358 oneLight.fill(name.c_str(), oneParameter->getLabel(), IPS_IDLE);
359 critialParametersLP.push(std::move(oneLight));
360 return true;
361}
362
404IPState WeatherInterface::checkParameterState(const std::string &name) const
405{
406 auto oneRange = std::find_if(ParametersRangeNP.begin(), ParametersRangeNP.end(), [name](auto & oneElement)
407 {
408 return oneElement.isNameMatch(name);
409 });
410
411 auto oneParameter = ParametersNP.findWidgetByName(name.c_str());
412 if (!oneParameter)
413 return IPS_IDLE;
414
415 // If parameter is found but not in range, then it is a critical parameter
416 if (oneRange == ParametersRangeNP.end())
417 {
418 if (oneParameter->getMin() == 0 && oneParameter->getMax() == 0 && oneParameter->getValue() != 0)
419 return IPS_ALERT;
420 else
421 return IPS_IDLE;
422 }
423
424 auto minLimit = (*oneRange)[MIN_OK].getValue();
425 auto maxLimit = (*oneRange)[MAX_OK].getValue();
426 auto percentageWarning = (*oneRange)[PERCENT_WARNING].getValue();
427 auto rangeWarn = (maxLimit - minLimit) * (percentageWarning / 100);
428 auto flipRangeTest = (*oneRange)[FLIP_RANGE_TEST].getValue();
429
430 auto value = oneParameter->getValue();
431
432 if (flipRangeTest)
433 {
434 // flipped original range test, namely: outside limits = OK; outside percentage bounds = warning; inside percentage bounds = danger
435 if (value < minLimit || value > maxLimit)
436 return IPS_OK;
437 else if (((value < (minLimit + rangeWarn)) && minLimit != 0) || ((value > (maxLimit - rangeWarn)) && maxLimit != 0))
438 return IPS_BUSY;
439 else
440 return IPS_ALERT;
441 }
442 else
443 {
444 // originally there was only this range test, namely: outside limits = danger; outside percentage bounds = warning; inside percentage bounds = OK
445 if (value < minLimit || value > maxLimit)
446 return IPS_ALERT;
447 else if (((value < (minLimit + rangeWarn)) && minLimit != 0) || ((value > (maxLimit - rangeWarn)) && maxLimit != 0))
448 return IPS_BUSY;
449 else
450 return IPS_OK;
451 }
452
453 return IPS_IDLE;
454}
455
466{
467 if (critialParametersLP.count() == 0)
468 return false;
469
470 std::vector<IPState> preStates(critialParametersLP.count());
471 for (size_t i = 0; i < critialParametersLP.count(); i++)
472 preStates[i] = critialParametersLP[i].getState();
473
475
476 for (auto &oneCriticalParam : critialParametersLP)
477 {
478 auto oneParameter = ParametersNP.findWidgetByName(oneCriticalParam.getName());
479 if (!oneParameter)
480 continue;
481
482 auto state = checkParameterState(oneCriticalParam.getName());
483
484 switch (state)
485 {
486 case IPS_BUSY:
487 oneCriticalParam.setState(IPS_BUSY);
488 LOGF_WARN("Warning: Parameter %s value (%.2f) is in the warning zone!", oneParameter->getLabel(), oneParameter->getValue());
489 break;
490
491 case IPS_ALERT:
492 oneCriticalParam.setState(IPS_ALERT);
493 LOGF_WARN("Caution: Parameter %s value (%.2f) is in the danger zone!", oneParameter->getLabel(), oneParameter->getValue());
494 break;
495
496 case IPS_IDLE:
497 case IPS_OK:
498 oneCriticalParam.setState(IPS_OK);
499 }
500
501 // The overall state is the worst individual state.
502 if (oneCriticalParam.getState() > critialParametersLP.getState())
503 critialParametersLP.setState(oneCriticalParam.getState());
504 }
505
506 // Update the SafetyStatusLP to mirror the overall critical parameters state (only if different)
508 {
511 }
512
513 // if Any state changed, return true.
514 for (size_t i = 0; i < critialParametersLP.count(); i++)
515 {
516 if (preStates[i] != critialParametersLP[i].getState())
517 return true;
518 }
519
520 return false;
521}
522
539void WeatherInterface::createParameterRange(std::string name, std::string label, double numMinOk, double numMaxOk,
540 double percWarning, bool flipWarning)
541{
542 INDI::WidgetNumber minWidget, maxWidget, warnWidget, typeWidget;
543 minWidget.fill("MIN_OK", "OK range min", "%.2f", -1e6, 1e6, 0, numMinOk);
544 maxWidget.fill("MAX_OK", "OK range max", "%.2f", -1e6, 1e6, 0, numMaxOk);
545 warnWidget.fill("PERC_WARN", "% for Warning", "%.f", 0, 100, 5, percWarning);
546 typeWidget.fill("ALERT_TYPE", "Flip alerting to in-bounds", "%.f", 0, 1, 1, (flipWarning ? 1 : 0));
547
548 INDI::PropertyNumber oneRange {0};
549 oneRange.push(std::move(minWidget));
550 oneRange.push(std::move(maxWidget));
551 oneRange.push(std::move(warnWidget));
552 oneRange.push(std::move(typeWidget));
553 oneRange.fill(getDeviceName(), name.c_str(), label.c_str(), m_ParametersGroup.c_str(), IP_RW, 60, IPS_IDLE);
554
555 ParametersRangeNP.push_back(std::move(oneRange));
556}
557
567{
569 for (auto &oneRange : ParametersRangeNP)
570 oneRange.save(fp);
571 return true;
572}
573
574
575}
bool isConnected() const
Class to provide extended functionality for devices in addition to the functionality provided by INDI...
bool updateProperty(PropertyType &property, ValueType *values, char *names[], int n, std::function< bool()> updater, bool saveConfig=false)
Generic helper function to update an INDI property based on an external operation.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
void push(WidgetView< T > &&item)
void setState(IPState state)
WidgetView< T > * findWidgetByName(const char *name) const
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
save Save property to configuration file.
bool isNameMatch(const char *otherName) const
void fill(const char *device, const char *name, const char *label, const char *group, IPState state)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool update(const ISState states[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
void setSingleShot(bool singleShot)
Set whether the timer is a single-shot timer.
void callOnTimeout(const std::function< void()> &callback)
Definition inditimer.cpp:76
void start()
Starts or restarts the timer with the timeout specified in interval.
Definition inditimer.cpp:82
void setInterval(int msec)
Set the timeout interval in milliseconds.
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 processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process weather switch properties.
bool setCriticalParameter(std::string name)
setCriticalParameter Set parameter that is considered critical to the operation of the observatory....
bool syncCriticalParameters()
Synchronize and update the state of all critical parameters.
virtual IPState updateWeather()
updateWeather Update weather conditions from device or service. The function should not change the st...
void setParameterValue(std::string name, double value)
setParameterValue Update weather parameter value
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process weather number properties.
WeatherInterface(DefaultDevice *defaultDevice)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save parameters ranges in the config file.
INDI::PropertyNumber ParametersNP
std::vector< INDI::PropertyNumber > ParametersRangeNP
INDI::PropertySwitch OverrideSP
IPState checkParameterState(const std::string &param) const
Checks the given parameter against the defined bounds.
void checkWeatherUpdate()
checkWeatherUpdate Calls updateWeather and update critical parameters accordingly.
INDI::PropertyLight SafetyStatusLP
void initProperties(const char *statusGroup, const char *paramsGroup)
Initialize focuser properties. It is recommended to call this function within initProperties() of you...
INDI::PropertySwitch RefreshSP
bool updateProperties()
updateProperties Define or Delete Rotator properties based on the connection status of the base devic...
INDI::PropertyLight critialParametersLP
INDI::PropertyNumber UpdatePeriodNP
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_NOFMANY
Definition indiapi.h:173
@ ISR_ATMOST1
Definition indiapi.h:172
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define LOGF_WARN(fmt,...)
Definition indilogger.h:81
#define LOG_WARN(txt)
Definition indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOG_INFO(txt)
Definition indilogger.h:74
#define getDeviceName
Namespace to encapsulate INDI client, drivers, and mediator classes.
void fill(const char *name, const char *label, IPState state=IPS_OK)
void fill(const char *name, const char *label, const char *format, double min, double max, double step, double value)