Instrument Neutral Distributed Interface INDI 2.2.5
sqm.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2016 Jasem Mutlaq. All rights reserved.
3
4 INDI Sky Quality Meter Driver
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 "sqm.h"
26
29#include "indicom.h"
30
31#include <cerrno>
32#include <memory>
33#include <cstring>
34#include <unistd.h>
35#include <termios.h>
36#include <regex>
37
38// We declare an auto pointer to SQM.
39static std::unique_ptr<SQM> sqm(new SQM());
40
41#define UNIT_TAB "Unit"
42
44{
45 setVersion(1, 3);
46}
47
49{
51
52 // Average Readings
53 IUFillNumber(&AverageReadingN[SKY_BRIGHTNESS], "SKY_BRIGHTNESS", "Quality (mag/arcsec^2)", "%6.2f", -20, 30, 0, 0);
54 IUFillNumber(&AverageReadingN[SENSOR_FREQUENCY], "SENSOR_FREQUENCY", "Freq (Hz)", "%6.2f", 0, 1000000, 0, 0);
55 IUFillNumber(&AverageReadingN[SENSOR_COUNTS], "SENSOR_COUNTS", "Period (counts)", "%6.2f", 0, 1000000, 0, 0);
56 IUFillNumber(&AverageReadingN[SENSOR_PERIOD], "SENSOR_PERIOD", "Period (s)", "%6.2f", 0, 1000000, 0, 0);
57 IUFillNumber(&AverageReadingN[SKY_TEMPERATURE], "SKY_TEMPERATURE", "Temperature (C)", "%6.2f", -50, 80, 0, 0);
58 IUFillNumberVector(&AverageReadingNP, AverageReadingN, 5, getDeviceName(), "SKY_QUALITY", "Readings",
60
61 // Unit Info
62 IUFillNumber(&UnitInfoN[UNIT_PROTOCOL], "UNIT_PROTOCOL", "Protocol", "%.f", 0, 1000000, 0, 0);
63 IUFillNumber(&UnitInfoN[UNIT_MODEL], "UNIT_MODEL", "Model", "%.f", 0, 1000000, 0, 0);
64 IUFillNumber(&UnitInfoN[UNIT_FEATURE], "UNIT_FEATURE", "Feature", "%.f", 0, 1000000, 0, 0);
65 IUFillNumber(&UnitInfoN[UNIT_SERIAL], "UNIT_SERIAL", "Serial", "%.f", 0, 1000000, 0, 0);
66 IUFillNumberVector(&UnitInfoNP, UnitInfoN, 4, getDeviceName(), "Unit Info", "", UNIT_TAB, IP_RW, 0, IPS_IDLE);
67
68 if (sqmConnection & CONNECTION_SERIAL)
69 {
70 serialConnection = new Connection::Serial(this);
71 serialConnection->registerHandshake([&]()
72 {
73 return getDeviceInfo();
74 });
76 registerConnection(serialConnection);
77 }
78
79 if (sqmConnection & CONNECTION_TCP)
80 {
81 tcpConnection = new Connection::TCP(this);
82 tcpConnection->setDefaultHost("192.168.1.1");
83 tcpConnection->setDefaultPort(10001);
84 tcpConnection->registerHandshake([&]()
85 {
86 return getDeviceInfo();
87 });
88
89 registerConnection(tcpConnection);
90 }
91
94
96
97 return true;
98}
99
101{
103
104 if (isConnected())
105 {
106 defineProperty(&AverageReadingNP);
107 defineProperty(&UnitInfoNP);
108
109 getReadings();
110
111 }
112 else
113 {
114 deleteProperty(AverageReadingNP.name);
115 deleteProperty(UnitInfoNP.name);
116 }
117
118 return true;
119}
120
121bool SQM::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
122{
123 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
124 {
125 // For polling periods > 2 seconds, the user must configure SQM via the web tool
126 if (!strcmp(name, "POLLING_PERIOD"))
127 {
128 uint32_t seconds = values[0] / 1000;
129 if (seconds > 2)
130 LOGF_WARN("Make sure SQM web timeout is configured for %ld seconds or more. Otherwise SQM will disconnect prematurely.",
131 seconds);
132 }
133 }
134
135 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
136}
137
138bool SQM::getReadings()
139{
140 char res[DRIVER_LEN] = {0};
141
142 if (!sendCommand("rx", res))
143 return false;
144
145 float mpsas, period_seconds, temperature;
146 int frequency, period_counts;
147 int rc = sscanf(res, "r,%fm,%dHz,%dc,%fs,%fC", &mpsas, &frequency, &period_counts, &period_seconds, &temperature);
148
149 if (rc < 5)
150 {
151 rc = sscanf(res, "r,%fm,%dHz,%dc,%fs,%fC,%*d", &mpsas, &frequency, &period_counts, &period_seconds, &temperature);
152 if (rc < 5)
153 {
154 LOGF_ERROR("Failed to parse input %s", res);
155 return false;
156 }
157 }
158
159 AverageReadingN[0].value = mpsas;
160 AverageReadingN[1].value = frequency;
161 AverageReadingN[2].value = period_counts;
162 AverageReadingN[3].value = period_seconds;
163 AverageReadingN[4].value = temperature;
164
165 return true;
166}
167
169{
170 return "SQM";
171}
172
173bool SQM::getDeviceInfo()
174{
175 if (getActiveConnection() == serialConnection)
176 {
177 PortFD = serialConnection->getPortFD();
178 }
179 else if (getActiveConnection() == tcpConnection)
180 {
181 PortFD = tcpConnection->getPortFD();
182 }
183
184 char res[DRIVER_LEN] = {0};
185 for (int i = 0; i < 3; i++)
186 {
187 if (!sendCommand("ix", res))
188 {
189 usleep(500000);
190 }
191 else
192 break;
193 }
194
195 if (res[0] == 0)
196 {
197 LOGF_ERROR("Error getting device info while reading response: %s", strerror(errno));
198 return false;
199 }
200
201 int protocol, model, feature, serial;
202 int rc = sscanf(res, "i,%d,%d,%d,%d", &protocol, &model, &feature, &serial);
203
204 if (rc < 4)
205 {
206 LOGF_ERROR("Failed to parse input %s", res);
207 return false;
208 }
209
210 UnitInfoN[0].value = protocol;
211 UnitInfoN[1].value = model;
212 UnitInfoN[2].value = feature;
213 UnitInfoN[3].value = serial;
214
215 return true;
216}
217
219{
220 if (!isConnected())
221 return;
222
223 bool rc = getReadings();
224
225 AverageReadingNP.s = rc ? IPS_OK : IPS_ALERT;
226 IDSetNumber(&AverageReadingNP, nullptr);
227
229}
230
234bool SQM::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
235{
236 int nbytes_written = 0, nbytes_read = 0, rc = -1;
237
238 tcflush(PortFD, TCIOFLUSH);
239
240 if (cmd_len > 0)
241 {
242 char hex_cmd[DRIVER_LEN * 3] = {0};
243 hexDump(hex_cmd, cmd, cmd_len);
244 LOGF_DEBUG("CMD <%s>", hex_cmd);
245 rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
246 }
247 else
248 {
249 LOGF_DEBUG("CMD <%s>", cmd);
250 rc = tty_write_string(PortFD, cmd, &nbytes_written);
251 }
252
253 if (rc != TTY_OK)
254 {
255 char errstr[MAXRBUF] = {0};
256 tty_error_msg(rc, errstr, MAXRBUF);
257 LOGF_ERROR("Serial write error: %s.", errstr);
258 return false;
259 }
260
261 if (res == nullptr)
262 return true;
263
264 if (res_len > 0)
265 rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
266 else
267 rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
268
269 if (rc != TTY_OK)
270 {
271 char errstr[MAXRBUF] = {0};
272 tty_error_msg(rc, errstr, MAXRBUF);
273 LOGF_ERROR("Serial read error: %s.", errstr);
274 return false;
275 }
276
277 if (res_len > 0)
278 {
279 char hex_res[DRIVER_LEN * 3] = {0};
280 hexDump(hex_res, res, res_len);
281 LOGF_DEBUG("RES <%s>", hex_res);
282 }
283 else
284 {
285 // Remove extra \r\n
286 res[nbytes_read - 2] = 0;
287 LOGF_DEBUG("RES <%s>", res);
288 }
289
290 tcflush(PortFD, TCIOFLUSH);
291
292 return true;
293}
294
298void SQM::hexDump(char * buf, const char * data, int size)
299{
300 for (int i = 0; i < size; i++)
301 sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
302
303 if (size > 0)
304 buf[3 * size - 1] = '\0';
305}
306
310std::vector<std::string> SQM::split(const std::string &input, const std::string &regex)
311{
312 // passing -1 as the submatch index parameter performs splitting
313 std::regex re(regex);
314 std::sregex_token_iterator
315 first{input.begin(), input.end(), re, -1},
316 last;
317 return {first, last};
318}
double temperature
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 setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
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)
int getPortFD() const
bool isConnected() const
const char * getDeviceName() const
void addPollPeriodControl()
Add Polling period control to the driver.
virtual bool updateProperties()
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
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)
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.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
Connection::Interface * getActiveConnection()
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
void addDebugControl()
Add Debug control to the driver.
Definition sqm.h:30
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
Definition sqm.cpp:48
@ CONNECTION_SERIAL
Definition sqm.h:46
@ CONNECTION_TCP
Definition sqm.h:47
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition sqm.cpp:121
const char * getDefaultName() override
Definition sqm.cpp:168
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
Definition sqm.cpp:218
SQM()
Definition sqm.cpp:43
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition sqm.cpp:100
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
int errno
@ 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
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition indicom.c:473
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:759
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition indidevapi.c:490
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
Definition indidevapi.c:398
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
#define MAXRBUF
Definition indidriver.c:52
#define LOGF_WARN(fmt,...)
Definition indilogger.h:81
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define UNIT_TAB
Definition lpm.cpp:40
uint8_t cmd[4]
Definition pwc-ioctl.h:2
char name[MAXINDINAME]
Definition indiapi.h:321