Instrument Neutral Distributed Interface INDI 2.2.5
io/ipx800v4.cpp
Go to the documentation of this file.
1/*
2 IPX800 Controller
3 Copyright (C) 2025 Jasem Mutlaq
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#include "ipx800v4.h"
21#include "defaultdevice.h"
23#include "indipropertyswitch.h"
24#include <memory>
25#include <httplib.h>
26
27#ifdef _USE_SYSTEM_JSONLIB
28#include <nlohmann/json.hpp>
29#else
30#include <indijson.hpp>
31#endif
32
34
35static std::unique_ptr<IPX800> device(new IPX800());
36
38{
39 setVersion(1, 1);
40}
41
42void IPX800::ISGetProperties(const char *dev)
43{
45 defineProperty(APIKeyTP);
46}
47
49{
51
52 // Initialize Input Interface for 8 digital inputs and 4 analog inputs
53 InputInterface::initProperties("Main Control", DIGITAL_INPUTS, ANALOG_INPUTS, "Input", "Analog");
54
55 // Initialize Output Interface for 8 digital outputs
56 OutputInterface::initProperties("Main Control", DIGITAL_OUTPUTS, "Output");
57
59
61
62 // Set up TCP connection
63 tcpConnection = new Connection::TCP(this);
64 tcpConnection->setDefaultHost("192.168.1.100"); // Default IPX800 IP
65 tcpConnection->setDefaultPort(80); // HTTP port
66 tcpConnection->registerHandshake([&]()
67 {
68 return Handshake();
69 });
70
71 registerConnection(tcpConnection);
72
73 //Define the APIKeyTP property
74 APIKeyTP[0].fill("API_KEY", "API Key", "");
75 APIKeyTP.fill(getDeviceName(), "API_KEY", "API Settings", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
76 APIKeyTP.load();
77
78
79 // Model version
80 ModelVersionTP[0].fill("VERSION", "Version", "");
81 ModelVersionTP.fill(getDeviceName(), "MODEL", "Model", "Main Control", IP_RO, 60, IPS_IDLE);
82
84
85 return true;
86}
87
89{
91 InputInterface::updateProperties();
92 OutputInterface::updateProperties();
93
94
95 if (isConnected())
96 {
97 defineProperty(ModelVersionTP);
99 }
100 else
101 {
102 deleteProperty(ModelVersionTP);
103 }
104
105 return true;
106}
107
108const char *IPX800::getDefaultName()
109{
110 return "IPX800";
111}
112
114{
115 httplib::Client cli(tcpConnection->host(), tcpConnection->port());
116
117 // Get digital input status as a simple test
118 std::string endpoint = "/api/xdevices.json?";
119 std::string apiKey = std::string(APIKeyTP[0].getText());
120 if (!apiKey.empty()) {
121 endpoint += "key=" + apiKey + "&" ;
122 }
123 endpoint += "Get=D";
124 auto result = cli.Get(endpoint);
125
126 if (!result)
127 {
128 LOG_ERROR("Failed to connect to device");
129 return false;
130 }
131
132 try
133 {
134 auto j = json::parse(result->body);
135
136 if (j.contains("product") && j.contains("status") && j["status"] == "Success")
137 {
138 std::string product = j["product"].get<std::string>();
139 // Extract version (e.g. "IPX800_V3" -> "V3")
140 size_t pos = product.find("_");
141 if (pos != std::string::npos)
142 {
143 ModelVersionTP[0].setText(product.substr(pos + 1).c_str());
144 ModelVersionTP.setState(IPS_OK);
145 }
146 return true;
147 }
148 else {
149 LOG_ERROR("Failed to connect to device");
150 return false;
151 }
152
153 }
154 catch (json::parse_error &e)
155 {
156 LOGF_ERROR("JSON parse error: %s", e.what());
157 }
158
159 return false;
160}
161
162void IPX800::TimerHit()
163{
164 if (!isConnected())
165 return;
166
167 UpdateDigitalInputs();
168 UpdateAnalogInputs();
170
172}
173
174bool IPX800::UpdateDigitalInputs()
175{
176 httplib::Client cli(tcpConnection->host(), tcpConnection->port());
177
178 std::string endpoint = "/api/xdevices.json?";
179 std::string apiKey = std::string(APIKeyTP[0].getText());
180 if (!apiKey.empty()) {
181 endpoint += "key=" + apiKey + "&" ;
182 }
183 endpoint += "Get=D";
184 auto result = cli.Get(endpoint);
185 if (!result)
186 {
187 LOG_ERROR("Failed to get digital inputs");
188 return false;
189 }
190 try
191 {
192 auto j = json::parse(result->body);
193 //Parse digital inputs
194 for (size_t i = 0; i < DIGITAL_INPUTS ; i++)
195 {
196 // Construisez la clé dynamiquement
197 std::string key = "D" + std::to_string(i + 1);
198 int state = j[key].get<int>() ? ISS_ON : ISS_OFF;
199 if (DigitalInputsSP[i].findOnSwitchIndex() != state)
200 {
201 DigitalInputsSP[i].reset();
202 DigitalInputsSP[i][state].setState(ISS_ON);
203 DigitalInputsSP[i].setState(IPS_OK);
204 DigitalInputsSP[i].apply();
205 }
206 }
207 return true;
208 }
209 catch (json::parse_error &e)
210 {
211 LOGF_ERROR("JSON parse error: %s", e.what());
212 }
213
214 return false;
215}
216
217bool IPX800::UpdateAnalogInputs()
218{
219 httplib::Client cli(tcpConnection->host(), tcpConnection->port());
220 std::string endpoint = "/api/xdevices.json?";
221 std::string apiKey = std::string(APIKeyTP[0].getText());
222 if (!apiKey.empty()) {
223 endpoint += "key=" + apiKey + "&" ;
224 }
225 endpoint += "Get=A";
226 auto result = cli.Get(endpoint);
227 if (!result)
228 {
229 LOG_ERROR("Failed to get analog inputs");
230 return false;
231 }
232
233 try
234 {
235 auto j = json::parse(result->body);
236 // Parse analog inputs
237 for (size_t i = 0; i < ANALOG_INPUTS; i++)
238 {
239 std::string key = "A" + std::to_string(i + 1);
240 if (j.contains(key))
241 {
242 double value = j[key].get<double>();
243 if (AnalogInputsNP[i][0].getValue() != value)
244 {
245 AnalogInputsNP[i][0].setValue(value);
246 AnalogInputsNP[i].setState(IPS_OK);
247 AnalogInputsNP[i].apply();
248 }
249 }
250 }
251 return true;
252 }
253 catch (json::parse_error &e)
254 {
255 LOGF_ERROR("JSON parse error: %s", e.what());
256 }
257
258 return false;
259}
260
262{
263 httplib::Client cli(tcpConnection->host(), tcpConnection->port());
264
265 std::string endpoint = "/api/xdevices.json?";
266 std::string apiKey = std::string(APIKeyTP[0].getText());
267 if (!apiKey.empty()) {
268 endpoint += "key=" + apiKey + "&" ;
269 }
270 endpoint += "Get=R";
271 auto result = cli.Get(endpoint);
272 if (!result)
273 {
274 LOG_ERROR("Failed to get digital outputs");
275 return false;
276 }
277
278 try
279 {
280 auto j = json::parse(result->body);
281
282 // Parse digital outputs
283 for (size_t i = 0; i < DIGITAL_OUTPUTS; i++)
284 {
285 std::string key = "R" + std::to_string(i + 1);
286 int state = j[key].get<int>() ? ISS_ON : ISS_OFF;
287 if (DigitalOutputsSP[i].findOnSwitchIndex() != state)
288 {
289 DigitalOutputsSP[i].reset();
290 DigitalOutputsSP[i][state].setState(ISS_ON);
291 DigitalOutputsSP[i].setState(IPS_OK);
292 DigitalOutputsSP[i].apply();
293 }
294 }
295 return true;
296 }
297 catch (json::parse_error &e)
298 {
299 LOGF_ERROR("JSON parse error: %s", e.what());
300 }
301
302 return false;
303}
304
305bool IPX800::CommandOutput(uint32_t index, OutputState command)
306{
307 httplib::Client cli(tcpConnection->host(), tcpConnection->port());
308
309 // IPX800 uses 1-based indexing for outputs
310 std::string endpoint = "/api/xdevices.json?";
311 std::string apiKey = std::string(APIKeyTP[0].getText());
312 if (!apiKey.empty())
313 endpoint += "key=" + apiKey + "&" ;
314
315 endpoint += std::string(command == OutputState::On ? "SetR=" : "ClearR=") + std::to_string(index + 1);
316
317 auto result = cli.Get(endpoint);
318 if (!result)
319 {
320 LOGF_ERROR("Failed to set output %d", index + 1);
321 return false;
322 }
323
324 return result->status == 200;
325}
326
327bool IPX800::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
328{
329 if (dev && !strcmp(dev, getDeviceName()))
330 {
331 if (APIKeyTP.isNameMatch(name))
332 {
333 APIKeyTP.update(texts, names, n);
334 APIKeyTP.setState(IPS_OK);
335 APIKeyTP.apply();
336 saveConfig(APIKeyTP);
337 return true;
338 }
339 }
340
341 if (InputInterface::processText(dev, name, texts, names, n))
342 return true;
343 if (OutputInterface::processText(dev, name, texts, names, n))
344 return true;
345
346 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
347}
348
349bool IPX800::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
350{
351 if (OutputInterface::processSwitch(dev, name, states, names, n))
352 return true;
353
354 return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
355}
356
357bool IPX800::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
358{
359 if (OutputInterface::processNumber(dev, name, values, names, n))
360 return true;
361
362 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
363}
364
365bool IPX800::saveConfigItems(FILE *fp)
366{
368 InputInterface::saveConfigItems(fp);
369 OutputInterface::saveConfigItems(fp);
370 APIKeyTP.save(fp);
371 return true;
372}
hid_device * device
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the initial connection to the devic...
The TCP class manages connection with devices over the network via TCP/IP. Upon successful connection...
void setDefaultHost(const char *addressHost)
void setDefaultPort(uint32_t addressPort)
virtual const char * host() const
virtual uint32_t port() const
bool isConnected() const
const char * getDeviceName() const
INDI::PropertyText getText(const char *name) const
virtual bool updateProperties()
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
virtual void ISGetProperties(const char *dev)
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
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...
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.
std::vector< INDI::PropertySwitch > DigitalInputsSP
std::vector< INDI::PropertyNumber > AnalogInputsNP
std::vector< INDI::PropertySwitch > DigitalOutputsSP
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 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)
virtual bool CommandOutput(uint32_t index, OutputState command) override
Send command to output.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool UpdateDigitalOutputs() override
Update all digital outputs.
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 ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
const char * getDefaultName() override
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
Provides interface to implement Digital/Analog input functionality.
Provides interface to implement Digital Boolean Output (On/Off) functionality.
a class to store JSON values
const char * what() const noexcept override
returns the explanatory string
exception indicating a parse error
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
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_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
std::string to_string(Error error)
Definition httplib.h:9275
basic_json<> json
default specialization