Instrument Neutral Distributed Interface INDI 2.2.5
auxiliary/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, 0);
40}
41
43{
45
46 // Initialize Input Interface for 8 digital inputs and 4 analog inputs
47 InputInterface::initProperties("Main Control", DIGITAL_INPUTS, ANALOG_INPUTS, "Input", "Analog");
48
49 // Initialize Output Interface for 8 digital outputs
50 OutputInterface::initProperties("Main Control", DIGITAL_OUTPUTS, "Output");
51
53
55
56 // Set up TCP connection
57 tcpConnection = new Connection::TCP(this);
58 tcpConnection->setDefaultHost("192.168.1.100"); // Default IPX800 IP
59 tcpConnection->setDefaultPort(80); // HTTP port
60 tcpConnection->registerHandshake([&]()
61 {
62 return Handshake();
63 });
64
65 registerConnection(tcpConnection);
66
67 // API Key
68 APIKeyTP[0].fill("API_KEY", "API Key", "");
69 APIKeyTP.fill(getDeviceName(), "API_KEY", "API Settings", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
70 APIKeyTP.load();
71
72 // Model version
73 ModelVersionTP[0].fill("VERSION", "Version", "");
74 ModelVersionTP.fill(getDeviceName(), "MODEL", "Model", "Main Control", IP_RO, 60, IPS_IDLE);
75
77
78 return true;
79}
80
82{
84 InputInterface::updateProperties();
85 OutputInterface::updateProperties();
86
87 if (isConnected())
88 {
89 defineProperty(APIKeyTP);
90 defineProperty(ModelVersionTP);
92 }
93 else
94 {
95 deleteProperty(APIKeyTP);
96 deleteProperty(ModelVersionTP);
97 }
98
99 return true;
100}
101
103{
104 return "IPX800";
105}
106
108{
109 if (strlen(APIKeyTP[0].getText()) == 0)
110 {
111 LOG_ERROR("API Key is not set");
112 return false;
113 }
114
115 httplib::Client cli(tcpConnection->host(), tcpConnection->port());
116
117 // Get relay status as a simple test
118 std::string endpoint = "/api/xdevices.json";
119 endpoint += "?key=" + std::string(APIKeyTP[0].getText());
120 endpoint += "&Get=D";
121 auto result = cli.Get(endpoint);
122 if (!result)
123 {
124 LOG_ERROR("Failed to connect to device");
125 return false;
126 }
127
128 try
129 {
130 auto j = json::parse(result->body);
131 if (j.contains("product"))
132 {
133 std::string product = j["product"].get<std::string>();
134 // Extract version (e.g. "IPX800_V3" -> "V3")
135 size_t pos = product.find("_");
136 if (pos != std::string::npos)
137 {
138 ModelVersionTP[0].setText(product.substr(pos + 1).c_str());
139 ModelVersionTP.setState(IPS_OK);
140 }
141 }
142 return true;
143 }
144 catch (json::parse_error &e)
145 {
146 LOGF_ERROR("JSON parse error: %s", e.what());
147 }
148
149 return false;
150}
151
153{
154 if (!isConnected())
155 return;
156
157 UpdateDigitalInputs();
158 UpdateAnalogInputs();
160
162}
163
164bool IPX800::UpdateDigitalInputs()
165{
166 httplib::Client cli(tcpConnection->host(), tcpConnection->port());
167
168 std::string endpoint = "/api/xdevices.json";
169 endpoint += "?key=" + std::string(APIKeyTP[0].getText());
170 endpoint += "&Get=D";
171 auto result = cli.Get(endpoint);
172 if (!result)
173 {
174 LOG_ERROR("Failed to get digital inputs");
175 return false;
176 }
177
178 try
179 {
180 auto j = json::parse(result->body);
181
182 // Parse digital inputs
183 auto inputs = j.get<std::vector<int>>();
184 for (size_t i = 0; i < DIGITAL_INPUTS && i < inputs.size(); i++)
185 {
186 auto state = inputs[i] ? ISS_ON : ISS_OFF;
187 if (DigitalInputsSP[i].findOnSwitchIndex() != state)
188 {
189 DigitalInputsSP[i].reset();
190 DigitalInputsSP[i][state].setState(ISS_ON);
191 DigitalInputsSP[i].setState(IPS_OK);
192 DigitalInputsSP[i].apply();
193 }
194 }
195 return true;
196 }
197 catch (json::parse_error &e)
198 {
199 LOGF_ERROR("JSON parse error: %s", e.what());
200 }
201
202 return false;
203}
204
205bool IPX800::UpdateAnalogInputs()
206{
207 httplib::Client cli(tcpConnection->host(), tcpConnection->port());
208
209 std::string endpoint = "/api/xdevices.json";
210 endpoint += "?key=" + std::string(APIKeyTP[0].getText());
211 endpoint += "&Get=A";
212 auto result = cli.Get(endpoint);
213 if (!result)
214 {
215 LOG_ERROR("Failed to get analog inputs");
216 return false;
217 }
218
219 try
220 {
221 auto j = json::parse(result->body);
222
223 // Parse analog inputs
224 for (int i = 0; i < ANALOG_INPUTS; i++)
225 {
226 std::string key = "AN" + std::to_string(i + 1);
227 if (j.contains(key))
228 {
229 double value = j[key].get<double>();
230 if (AnalogInputsNP[i][0].getValue() != value)
231 {
232 AnalogInputsNP[i][0].setValue(value);
233 AnalogInputsNP[i].setState(IPS_OK);
234 AnalogInputsNP[i].apply();
235 }
236 }
237 }
238 return true;
239 }
240 catch (json::parse_error &e)
241 {
242 LOGF_ERROR("JSON parse error: %s", e.what());
243 }
244
245 return false;
246}
247
249{
250 httplib::Client cli(tcpConnection->host(), tcpConnection->port());
251
252 std::string endpoint = "/api/xdevices.json";
253 endpoint += "?key=" + std::string(APIKeyTP[0].getText());
254 endpoint += "&Get=R";
255 auto result = cli.Get(endpoint);
256 if (!result)
257 {
258 LOG_ERROR("Failed to get digital outputs");
259 return false;
260 }
261
262 try
263 {
264 auto j = json::parse(result->body);
265
266 // Parse digital outputs
267 auto outputs = j.get<std::vector<int>>();
268 for (size_t i = 0; i < DIGITAL_OUTPUTS && i < outputs.size(); i++)
269 {
270 auto state = outputs[i] ? ISS_ON : ISS_OFF;
271 if (DigitalOutputsSP[i].findOnSwitchIndex() != state)
272 {
273 DigitalOutputsSP[i].reset();
274 DigitalOutputsSP[i][state].setState(ISS_ON);
275 DigitalOutputsSP[i].setState(IPS_OK);
276 DigitalOutputsSP[i].apply();
277 }
278 }
279 return true;
280 }
281 catch (json::parse_error &e)
282 {
283 LOGF_ERROR("JSON parse error: %s", e.what());
284 }
285
286 return false;
287}
288
289bool IPX800::CommandOutput(uint32_t index, OutputState command)
290{
291 httplib::Client cli(tcpConnection->host(), tcpConnection->port());
292
293 // IPX800 uses 1-based indexing for outputs
294 std::string endpoint = "/api/xdevices.json";
295 endpoint += "?key=" + std::string(APIKeyTP[0].getText());
296 endpoint += "&" + std::string(command == OutputState::On ? "SetR=" : "ClearR=") + std::to_string(index + 1);
297
298 auto result = cli.Get(endpoint);
299 if (!result)
300 {
301 LOGF_ERROR("Failed to set output %d", index + 1);
302 return false;
303 }
304
305 return result->status == 200;
306}
307
308bool IPX800::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
309{
310 if (dev && !strcmp(dev, getDeviceName()))
311 {
312 if (APIKeyTP.isNameMatch(name))
313 {
314 APIKeyTP.update(texts, names, n);
315 APIKeyTP.setState(IPS_OK);
316 APIKeyTP.apply();
317 saveConfig(APIKeyTP);
318 return true;
319 }
320 }
321
322 if (InputInterface::processText(dev, name, texts, names, n))
323 return true;
324 if (OutputInterface::processText(dev, name, texts, names, n))
325 return true;
326
327 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
328}
329
330bool IPX800::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
331{
332 if (OutputInterface::processSwitch(dev, name, states, names, n))
333 return true;
334
335 return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
336}
337
338bool IPX800::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
339{
340 if (OutputInterface::processNumber(dev, name, values, names, n))
341 return true;
342
343 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
344}
345
347{
349 InputInterface::saveConfigItems(fp);
350 OutputInterface::saveConfigItems(fp);
351 APIKeyTP.save(fp);
352 return true;
353}
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.
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 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 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.
Result Get(const std::string &path, DownloadProgress progress=nullptr)
Definition httplib.h:16652
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
basic_json<> json
default specialization