Instrument Neutral Distributed Interface INDI 2.2.5
dragonlight.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2024 Rick Bassham. All rights reserved.
3
4 Dark Dragons Astronomy DragonLIGHT
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2 of the License, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20
21 The full GNU General Public License is included in this distribution in the
22 file called LICENSE.
23*******************************************************************************/
24
25#include "dragonlight.h"
26
27#include <httplib.h>
28#include <string.h>
29
30#ifdef _USE_SYSTEM_JSONLIB
31#include <nlohmann/json.hpp>
32#else
33#include <indijson.hpp>
34#endif
35
36static std::unique_ptr<DragonLIGHT> dragonlight(new DragonLIGHT());
37
42
44{
46
47 FirmwareTP[0].fill("Version", "Version", nullptr);
48 FirmwareTP[1].fill("Serial", "Serial", nullptr);
49 FirmwareTP.fill(getDeviceName(), "FIRMWARE", "Firmware", INFO_TAB, IP_RO, 60, IPS_IDLE);
50
51 IPAddressTP[0].fill("IP Address", "IP Address", nullptr);
52 IPAddressTP.fill(getDeviceName(), "IP_ADDRESS", "IP Address", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
53
54 DiscoverSwitchSP[0].fill("DISCOVER", "Discover", ISS_OFF);
55 DiscoverSwitchSP.fill(getDeviceName(), "DISCOVER", "Discover", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
56
58
60
61 LightIntensityNP[0].setMin(0);
62 LightIntensityNP[0].setMax(255);
63 LightIntensityNP[0].setMax(1);
64
66
67 return true;
68}
69
70void DragonLIGHT::ISGetProperties(const char *dev)
71{
73
74 defineProperty(IPAddressTP);
75 defineProperty(DiscoverSwitchSP);
76
77 // Get Light box properties
79
80 loadConfig(IPAddressTP);
81}
82
84{
87
88 if (isConnected())
89 {
90 defineProperty(FirmwareTP);
91 }
92 else
93 {
94 deleteProperty(FirmwareTP);
95 }
96
97 return true;
98}
99
101{
102 return "DragonLIGHT";
103}
104
106{
107 LOGF_DEBUG("EnableLightBox: %d", enable);
108
109 httplib::Client cli(IPAddressTP[0].getText(), 80);
110
111 std::string endpoint = enable ? "/indi/turnon" : "/indi/turnoff";
112
113 auto result = cli.Post(endpoint);
114 if (!result)
115 {
116 LOG_ERROR("Unable to connect.");
117 return false;
118 }
119
120 return (result.value().status == 200);
121}
122
124{
125 if (LightSP[FLAT_LIGHT_ON].getState() != ISS_ON)
126 {
127 LOG_ERROR("You must set On the Flat Light first.");
128 return false;
129 }
130
131 LightIntensityNP[0].setValue(value);
133
134 httplib::Client cli(IPAddressTP[0].getText(), 80);
135
136 std::string endpoint = "/indi/brightness";
137
139 j["brightness"] = value;
140
141 auto result = cli.Post(endpoint, j.dump(), "application/json");
142 if (!result)
143 {
144 LOG_ERROR("Unable to connect.");
145 return false;
146 }
147
148 if (result.value().status == 200)
149 {
150 return true;
151 }
152 else
153 {
154 return false;
155 }
156}
157
158bool DragonLIGHT::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
159{
160 if (LI::processNumber(dev, name, values, names, n))
161 return true;
162
163 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
164}
165
166bool DragonLIGHT::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
167{
168 if (isDeviceNameMatch(dev) && IPAddressTP.isNameMatch(name))
169 {
170 IPAddressTP.update(texts, names, n);
171 IPAddressTP.setState(IPS_OK);
172 IPAddressTP.apply();
173
174 return true;
175 }
176
177 if (LI::processText(dev, name, texts, names, n))
178 return true;
179
180 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
181}
182
183bool DragonLIGHT::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
184{
185 if (isDeviceNameMatch(dev) && DiscoverSwitchSP.isNameMatch(name))
186 {
187 DiscoverSwitchSP.update(states, names, n);
188 auto isToggled = DiscoverSwitchSP[0].getState() == ISS_ON;
189 DiscoverSwitchSP.setState(isToggled ? IPS_BUSY : IPS_IDLE);
190
191 if (isToggled)
192 {
193 discoverDevices();
194 }
195
196 DiscoverSwitchSP.apply();
197 return true;
198 }
199
200 if (LI::processSwitch(dev, name, states, names, n))
201 return true;
202
203 return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
204}
205
207{
208 LI::snoop(root);
209
211}
212
214{
216
217 IPAddressTP.save(fp);
218
219 return LI::saveConfigItems(fp);
220}
221
223{
224 IPAddressTP.getText();
225
226 if (strlen(IPAddressTP[0].getText()) == 0)
227 {
228 LOG_ERROR("IP Address is not set.");
229 return false;
230 }
231
232 updateStatus();
233
235 return true;
236}
237
239{
240 return true;
241}
242
244{
245 if (!isConnected())
246 return;
247
249
250 updateStatus();
251
253}
254
255void DragonLIGHT::updateStatus()
256{
257 httplib::Client cli(IPAddressTP[0].getText(), 80);
258
259 auto result = cli.Get("/indi/status");
260 if (!result)
261 {
262 LOG_ERROR("Unable to connect.");
263 return;
264 }
265
266 if (result.value().status == 200)
267 {
268 nlohmann::json j = nlohmann::json::parse(result.value().body);
269
270 std::string version = j["version"];
271 std::string serial = j["serialNumber"];
272
273 FirmwareTP[0].setText(version.c_str());
274 FirmwareTP[1].setText(serial.c_str());
275 FirmwareTP.setState(IPS_OK);
276 FirmwareTP.apply();
277
278 uint32_t brightness = j["brightness"];
279
280 LightIntensityNP[0].setValue(brightness);
283
284 bool isOn = j["isOn"];
285
289 LightSP.apply();
290 }
291 else
292 {
293 LOG_ERROR("Error on updateStatus.");
294 }
295}
296
297#define DDA_DISCOVERY_PORT 0xdda
298#define DDA_DISCOVERY_TIMEOUT 2 // Seconds
299#define DDA_DISCOVERY_RECEIVE_BUFFER_SIZE 256
300
301void DragonLIGHT::discoverDevices()
302{
303 int s;
304
305 s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
306 if (s == -1)
307 {
308 LOG_ERROR("Error creating socket for discovery.");
309 return;
310 }
311
312 int broadcast = 1;
313 setsockopt(s, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
314
315 struct timeval tv;
316 tv.tv_sec = DDA_DISCOVERY_TIMEOUT;
317 tv.tv_usec = 0;
318 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
319
320 struct sockaddr_in Recv_addr;
321
322 socklen_t len = sizeof(struct sockaddr_in);
323
324 Recv_addr.sin_family = AF_INET;
325 Recv_addr.sin_port = htons(DDA_DISCOVERY_PORT);
326 Recv_addr.sin_addr.s_addr = INADDR_BROADCAST;
327
328 const char* sendMSG = "darkdragons";
329
330 sendto(s, sendMSG, strlen(sendMSG), 0, (sockaddr *)&Recv_addr, sizeof(Recv_addr));
331
333 while (true)
334 {
335 memset(recvbuff, 0, DDA_DISCOVERY_RECEIVE_BUFFER_SIZE);
336
337 int n = recvfrom(s, recvbuff, DDA_DISCOVERY_RECEIVE_BUFFER_SIZE, 0, (sockaddr *)&Recv_addr, &len);
338 if (n < 0)
339 break;
340
341 nlohmann::json doc = nlohmann::json::parse(recvbuff, nullptr, false, true);
342
343 if (!doc.is_discarded() && doc.contains("deviceType"))
344 {
345 char deviceIP[INET_ADDRSTRLEN];
346 inet_ntop(AF_INET, &(Recv_addr.sin_addr), deviceIP, INET_ADDRSTRLEN);
347
348 std::string deviceType = doc["deviceType"];
349 std::string serialNumber = doc["SerialNumber"].is_string() ? doc["SerialNumber"].get<std::string>() : "";
350
351 LOGF_INFO("Found %s %s at %s", deviceType.c_str(), serialNumber.c_str(), deviceIP);
352 }
353 }
354
355 close(s);
356
357 DiscoverSwitchSP.reset();
358 DiscoverSwitchSP[0].setState(ISS_OFF);
359 DiscoverSwitchSP.setState(IPS_OK);
360 DiscoverSwitchSP.apply();
361}
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool EnableLightBox(bool enable) override
EnableLightBox Turn on/off on a light box. Must be implemented in the child class.
virtual bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
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 void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool SetLightBoxBrightness(uint16_t value) override
setBrightness Set light level. Must be implemented in the child class, if supported.
const char * getDefaultName() override
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool Disconnect() override
Disconnect from device.
bool isConnected() const
const char * getDeviceName() const
INDI::PropertyText getText(const char *name) const
bool isDeviceNameMatch(const char *otherName) const
Check that the device name matches the argument.
virtual bool updateProperties()
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
virtual void ISGetProperties(const char *dev)
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool ISSnoopDevice(XMLEle *root)
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool loadConfig(bool silent=false, const char *property=nullptr)
Load the last saved configuration file.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual void TimerHit()
Callback function to be called once SetTimer duration elapses.
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
void initProperties(const char *group, uint32_t capabilities)
Initialize light box properties. It is recommended to call this function within initProperties() of y...
void ISGetProperties(const char *deviceName)
isGetLightBoxProperties Get light box properties
INDI::PropertyNumber LightIntensityNP
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process light box switch properties.
bool processText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process light box text properties.
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process light box number properties.
void setState(IPState state)
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
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)
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
Provides interface to implement controllable light box/switch device.
Result Post(const std::string &path)
Definition httplib.h:16722
a class to store JSON values
string_t dump(const int indent=-1, const char indent_char=' ', const bool ensure_ascii=false, const error_handler_t error_handler=error_handler_t::strict) const
serialization
bool contains(const typename object_t::key_type &key) const
check the existence of an element in a JSON object
constexpr bool is_string() const noexcept
return whether value is a string
constexpr bool is_discarded() const noexcept
return whether value is discarded
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)
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * INFO_TAB
INFO_TAB Where all the properties for general information are located.
#define DDA_DISCOVERY_TIMEOUT
#define DDA_DISCOVERY_RECEIVE_BUFFER_SIZE
#define DDA_DISCOVERY_PORT
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
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_ATMOST1
Definition indiapi.h:172
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#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