Instrument Neutral Distributed Interface INDI  2.0.2
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.
39 static 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 
95  return true;
96 }
97 
99 {
101 
102  if (isConnected())
103  {
104  defineProperty(&AverageReadingNP);
105  defineProperty(&UnitInfoNP);
106 
107  getReadings();
108 
109  }
110  else
111  {
112  deleteProperty(AverageReadingNP.name);
113  deleteProperty(UnitInfoNP.name);
114  }
115 
116  return true;
117 }
118 
119 bool SQM::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
120 {
121  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
122  {
123  // For polling periods > 2 seconds, the user must configure SQM via the web tool
124  if (!strcmp(name, "POLLING_PERIOD"))
125  {
126  uint32_t seconds = values[0] / 1000;
127  if (seconds > 2)
128  LOGF_WARN("Make sure SQM web timeout is configured for %ld seconds or more. Otherwise SQM will disconnect prematurely.",
129  seconds);
130  }
131  }
132 
133  return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
134 }
135 
136 bool SQM::getReadings()
137 {
138  char res[DRIVER_LEN] = {0};
139 
140  if (!sendCommand("rx", res))
141  return false;
142 
143  float mpsas, period_seconds, temperature;
144  int frequency, period_counts;
145  int rc = sscanf(res, "r,%fm,%dHz,%dc,%fs,%fC", &mpsas, &frequency, &period_counts, &period_seconds, &temperature);
146 
147  if (rc < 5)
148  {
149  rc = sscanf(res, "r,%fm,%dHz,%dc,%fs,%fC,%*d", &mpsas, &frequency, &period_counts, &period_seconds, &temperature);
150  if (rc < 5)
151  {
152  LOGF_ERROR("Failed to parse input %s", res);
153  return false;
154  }
155  }
156 
157  AverageReadingN[0].value = mpsas;
158  AverageReadingN[1].value = frequency;
159  AverageReadingN[2].value = period_counts;
160  AverageReadingN[3].value = period_seconds;
161  AverageReadingN[4].value = temperature;
162 
163  return true;
164 }
165 
166 const char *SQM::getDefaultName()
167 {
168  return "SQM";
169 }
170 
171 bool SQM::getDeviceInfo()
172 {
173  if (getActiveConnection() == serialConnection)
174  {
175  PortFD = serialConnection->getPortFD();
176  }
177  else if (getActiveConnection() == tcpConnection)
178  {
179  PortFD = tcpConnection->getPortFD();
180  }
181 
182  char res[DRIVER_LEN] = {0};
183  for (int i = 0; i < 3; i++)
184  {
185  if (!sendCommand("ix", res))
186  {
187  usleep(500000);
188  }
189  else
190  break;
191  }
192 
193  if (res[0] == 0)
194  {
195  LOGF_ERROR("Error getting device info while reading response: %s", strerror(errno));
196  return false;
197  }
198 
199  int protocol, model, feature, serial;
200  int rc = sscanf(res, "i,%d,%d,%d,%d", &protocol, &model, &feature, &serial);
201 
202  if (rc < 4)
203  {
204  LOGF_ERROR("Failed to parse input %s", res);
205  return false;
206  }
207 
208  UnitInfoN[0].value = protocol;
209  UnitInfoN[1].value = model;
210  UnitInfoN[2].value = feature;
211  UnitInfoN[3].value = serial;
212 
213  return true;
214 }
215 
217 {
218  if (!isConnected())
219  return;
220 
221  bool rc = getReadings();
222 
223  AverageReadingNP.s = rc ? IPS_OK : IPS_ALERT;
224  IDSetNumber(&AverageReadingNP, nullptr);
225 
227 }
228 
232 bool SQM::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
233 {
234  int nbytes_written = 0, nbytes_read = 0, rc = -1;
235 
236  tcflush(PortFD, TCIOFLUSH);
237 
238  if (cmd_len > 0)
239  {
240  char hex_cmd[DRIVER_LEN * 3] = {0};
241  hexDump(hex_cmd, cmd, cmd_len);
242  LOGF_DEBUG("CMD <%s>", hex_cmd);
243  rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
244  }
245  else
246  {
247  LOGF_DEBUG("CMD <%s>", cmd);
248  rc = tty_write_string(PortFD, cmd, &nbytes_written);
249  }
250 
251  if (rc != TTY_OK)
252  {
253  char errstr[MAXRBUF] = {0};
254  tty_error_msg(rc, errstr, MAXRBUF);
255  LOGF_ERROR("Serial write error: %s.", errstr);
256  return false;
257  }
258 
259  if (res == nullptr)
260  return true;
261 
262  if (res_len > 0)
263  rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
264  else
265  rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
266 
267  if (rc != TTY_OK)
268  {
269  char errstr[MAXRBUF] = {0};
270  tty_error_msg(rc, errstr, MAXRBUF);
271  LOGF_ERROR("Serial read error: %s.", errstr);
272  return false;
273  }
274 
275  if (res_len > 0)
276  {
277  char hex_res[DRIVER_LEN * 3] = {0};
278  hexDump(hex_res, res, res_len);
279  LOGF_DEBUG("RES <%s>", hex_res);
280  }
281  else
282  {
283  // Remove extra \r\n
284  res[nbytes_read - 2] = 0;
285  LOGF_DEBUG("RES <%s>", res);
286  }
287 
288  tcflush(PortFD, TCIOFLUSH);
289 
290  return true;
291 }
292 
296 void SQM::hexDump(char * buf, const char * data, int size)
297 {
298  for (int i = 0; i < size; i++)
299  sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
300 
301  if (size > 0)
302  buf[3 * size - 1] = '\0';
303 }
304 
308 std::vector<std::string> SQM::split(const std::string &input, const std::string &regex)
309 {
310  // passing -1 as the submatch index parameter performs splitting
311  std::regex re(regex);
312  std::sregex_token_iterator
313  first{input.begin(), input.end(), re, -1},
314  last;
315  return {first, last};
316 }
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the intial connection to the device...
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 successfull connectio...
Definition: connectiontcp.h:38
void setDefaultHost(const char *addressHost)
void setDefaultPort(uint32_t addressPort)
int getPortFD() const
Definition: connectiontcp.h:84
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
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 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()
Initilize 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
Initilize 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:119
const char * getDefaultName() override
Definition: sqm.cpp:166
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
Definition: sqm.cpp:216
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:98
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:186
@ IP_RO
Definition: indiapi.h:184
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition: indicom.c:424
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition: indicom.c:482
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition: indicom.c:474
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition: indicom.c:1167
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:666
Implementations for common driver routines.
@ TTY_OK
Definition: indicom.h:150
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:272
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:180
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
#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 MAXRBUF
Definition: indiserver.cpp:102
__u8 cmd[4]
Definition: pwc-ioctl.h:2
#define UNIT_TAB
Definition: sqm.cpp:41
char name[MAXINDINAME]
Definition: indiapi.h:323