Instrument Neutral Distributed Interface INDI 2.2.5
pegasus_flatmaster_wifi.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2026 Jasem Mutlaq. All rights reserved.
3
4 Pegasus FlatMaster WiFi
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
26
27#include "indicom.h"
29
30#include <cerrno>
31#include <cstring>
32#include <memory>
33#include <termios.h>
34#include <unistd.h>
35#include <inttypes.h>
36#include <sys/ioctl.h>
37
38
39static std::unique_ptr<PegasusFlatMasterWifi> flatmasterwifi(new PegasusFlatMasterWifi());
40
41
46
48{
50
51 // Firmware version
52 FirmwareTP[0].fill("Version", "Version", nullptr);
53 FirmwareTP.fill(getDeviceName(), "Firmware", "Firmware", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
54
56
58
59 LightIntensityNP[0].setMin(0);
60 LightIntensityNP[0].setMax(100);
61 LightIntensityNP[0].setStep(1);
62
63 // Uptime (seconds, read-only)
64 UptimeNP[0].fill("UPTIME_SECONDS", "Uptime (s)", "%.0f", 0, 1e9, 1, 0);
65 UptimeNP.fill(getDeviceName(), "UPTIME", "Uptime", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
66
67 // Reboot button
68 RebootSP[0].fill("REBOOT", "Reboot", ISS_OFF);
69 RebootSP.fill(getDeviceName(), "DEVICE_REBOOT", "Device", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
70
72
73 serialConnection = new Connection::Serial(this);
75 serialConnection->setPortMatchPattern("FlatMaster_WiFi");
76 serialConnection->registerHandshake([&]()
77 {
78 return Ack();
79 });
80
81 registerConnection(serialConnection);
82 return true;
83}
84
90
92{
94
95 if (isConnected())
96 {
97 defineProperty(FirmwareTP);
98 defineProperty(UptimeNP);
99 defineProperty(RebootSP);
100 getStatusData();
101 }
102 else
103 {
104 deleteProperty(FirmwareTP);
105 deleteProperty(UptimeNP);
106 deleteProperty(RebootSP);
107 }
108
110 return true;
111}
112
114{
115 return "Pegasus FlatMaster WiFi";
116}
117
118void PegasusFlatMasterWifi::updateFirmwareVersion()
119{
120 char response[32] = {0};
121
122 if (sendCommand("FV", response))
123 {
124 // Response: FV:[MAJOR].[MINOR] — strip the "FV:" prefix
125 const char *ver = strstr(response, "FV:");
126 FirmwareTP[0].setText(ver ? ver + 3 : response);
127 FirmwareTP.setState(IPS_OK);
128 FirmwareTP.apply();
129 }
130 else
131 {
132 FirmwareTP.setState(IPS_ALERT);
133 FirmwareTP.apply();
134 LOG_ERROR("Error reading firmware version.");
135 }
136}
137
138bool PegasusFlatMasterWifi::Ack()
139{
140 PortFD = serialConnection->getPortFD();
141
142 char response[32] = {0};
143 if (sendCommand("F#", response))
144 {
145 if (strstr(response, "FMW") != nullptr)
146 {
147 updateFirmwareVersion();
148 return true;
149 }
150 }
151 LOGF_ERROR("Ack failed: unexpected response '%s'", response);
152 return false;
153}
154
156{
157 char cmd[16] = {0};
158 char response[32] = {0};
159
160 snprintf(cmd, sizeof(cmd), "FE:%d", enable ? 1 : 0);
161
162 if (!sendCommand(cmd, response))
163 {
164 LOGF_ERROR("EnableLightBox: send failed. %s", response);
165 return false;
166 }
167
168 if (strstr(response, "FE:") == nullptr)
169 {
170 LOGF_ERROR("EnableLightBox: unexpected response '%s'", response);
171 return false;
172 }
173
174 return true;
175}
176
178{
179 char cmd[16] = {0};
180 char response[32] = {0};
181
182 // Device accepts 0–100 directly
183 snprintf(cmd, sizeof(cmd), "FL:%d", value);
184
185 if (!sendCommand(cmd, response))
186 {
187 LOGF_ERROR("SetLightBoxBrightness: send failed. %s", response);
188 return false;
189 }
190
191 if (strstr(response, "FL:") == nullptr)
192 {
193 LOGF_ERROR("SetLightBoxBrightness: unexpected response '%s'", response);
194 return false;
195 }
196
197 return true;
198}
199
200bool PegasusFlatMasterWifi::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
201{
202 if (LI::processNumber(dev, name, values, names, n))
203 return true;
204
205 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
206}
207
208bool PegasusFlatMasterWifi::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
209{
210 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
211 {
212 if (LI::processText(dev, name, texts, names, n))
213 return true;
214 }
215
216 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
217}
218
219bool PegasusFlatMasterWifi::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
220{
221 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
222 {
223 if (LI::processSwitch(dev, name, states, names, n))
224 return true;
225
226 if (RebootSP.isNameMatch(name))
227 {
228 char response[32] = {0};
229 // FQ reboots the device — no response expected
230 sendCommand("FQ", response);
231 RebootSP.setState(IPS_OK);
232 RebootSP.apply();
233 LOG_INFO("Reboot command sent to device.");
234 return true;
235 }
236 }
237
238 return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
239}
240
246
252
253bool PegasusFlatMasterWifi::sendCommand(const char *command, char *res)
254{
255 int nbytes_written = 0, nbytes_read = 0, rc = -1;
256 char errstr[MAXRBUF];
257
258 char cmd[32] = {0};
259 snprintf(cmd, sizeof(cmd), "%s\n", command);
260
261 LOGF_DEBUG("CMD <%s>", command);
262
263 tcflush(PortFD, TCIOFLUSH);
264
265 if ((rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
266 {
267 tty_error_msg(rc, errstr, MAXRBUF);
268 LOGF_ERROR("command: %s error: %s.", cmd, errstr);
269 return false;
270 }
271
272 if ((rc = tty_read_section(PortFD, res, 0xA, 3, &nbytes_read)) != TTY_OK)
273 {
274 tty_error_msg(rc, errstr, MAXRBUF);
275 LOGF_ERROR("command: %s error: %s.", cmd, errstr);
276 return false;
277 }
278
279 // Strip trailing newline
280 res[nbytes_read - 1] = 0;
281 if (nbytes_read > 1 && res[nbytes_read - 2] == '\r')
282 res[nbytes_read - 2] = 0;
283
284 LOGF_DEBUG("RES <%s>", res);
285
286 tcflush(PortFD, TCIOFLUSH);
287
288 return true;
289}
290
291bool PegasusFlatMasterWifi::getStatusData()
292{
293 char response[32] = {0};
294
295 if (!sendCommand("FA", response))
296 {
297 LOG_ERROR("Error querying device status.");
298 return false;
299 }
300
301 // FA response: "FA:<brightness>:<power>"
302 // brightness: 0-100, power: 0 or 1
303 char *tok = strtok(response, ":"); // "FA"
304 if (tok == nullptr) return false;
305
306 char *brStr = strtok(nullptr, ":");
307 char *pwStr = strtok(nullptr, ":");
308
309 if (brStr == nullptr || pwStr == nullptr)
310 {
311 LOG_WARN("Unexpected FA response format.");
312 return false;
313 }
314
315 const double brightness = atof(brStr);
316 const bool lightOn = (atof(pwStr) != 0);
317
318 // Update LightBox brightness
319 if (LightIntensityNP[0].getValue() != brightness)
320 {
321 LightIntensityNP[0].setValue(brightness);
324 }
325
326 // Update LightBox on/off switch
327 if (LightSP[FLAT_LIGHT_ON].getState() != (lightOn ? ISS_ON : ISS_OFF))
328 {
329 LightSP.reset();
332 LightSP.apply();
333 }
334
335 return true;
336}
337
339{
340 if (!isConnected())
341 {
343 return;
344 }
345
346 getStatusData();
347
348 // Update uptime
349 char response[32] = {0};
350 if (sendCommand("FU", response))
351 {
352 // FU:<seconds>
353 const char *val = strstr(response, "FU:");
354 if (val)
355 {
356 UptimeNP[0].setValue(atof(val + 3));
357 UptimeNP.setState(IPS_OK);
358 UptimeNP.apply();
359 }
360 }
361
363}
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the initial connection to the devic...
The Serial class manages connection with serial devices including Bluetooth. Serial communication is ...
void setPortMatchPattern(const std::string &pattern)
setPortMatchPattern Set a regex pattern used to automatically select a serial port from the discovere...
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
bool isConnected() const
const char * getDeviceName() const
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.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
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 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.
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
bool isNameMatch(const char *otherName) const
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
Provides interface to implement controllable light box/switch device.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
const char * getDefaultName() override
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 SetLightBoxBrightness(uint16_t value) override
setBrightness Set light level. Must be implemented in the child class, if supported.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool EnableLightBox(bool enable) override
EnableLightBox Turn on/off on a light box. Must be implemented in the child class.
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 ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
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.
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_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_ATMOST1
Definition indiapi.h:172
int tty_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:653
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
#define MAXRBUF
Definition indidriver.c:52
#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 LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
uint8_t cmd[4]
Definition pwc-ioctl.h:2