Instrument Neutral Distributed Interface INDI  2.0.2
mbox.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright(c) 2017 Jasem Mutlaq. All rights reserved.
3 
4  INDI MBox 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 "mbox.h"
26 
27 #include "indicom.h"
29 
30 #include <memory>
31 #include <regex>
32 #include <cstring>
33 #include <termios.h>
34 #include <unistd.h>
35 
36 #define MBOX_TIMEOUT 6
37 #define MBOX_BUF 64
38 
39 // We declare an auto pointer to MBox.
40 static std::unique_ptr<MBox> mbox(new MBox());
41 
43 {
44  setVersion(1, 1);
45 }
46 
47 const char *MBox::getDefaultName()
48 {
49  return "MBox";
50 }
51 
53 {
55 
56  addParameter("WEATHER_TEMPERATURE", "Temperature (C)", -10, 30, 15);
57  addParameter("WEATHER_BAROMETER", "Barometer (mbar)", 20, 32.5, 15);
58  addParameter("WEATHER_HUMIDITY", "Humidity %", 0, 100, 15);
59  addParameter("WEATHER_DEWPOINT", "Dew Point (C)", 0, 100, 15);
60 
61  setCriticalParameter("WEATHER_TEMPERATURE");
62 
63  // Reset Calibration
64  IUFillSwitch(&ResetS[0], "RESET", "Reset", ISS_OFF);
65  IUFillSwitchVector(&ResetSP, ResetS, 1, getDeviceName(), "CALIBRATION_RESET", "Reset", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY,
66  0, IPS_IDLE);
67 
68  // Calibration Properties
69  IUFillNumber(&CalibrationN[CAL_TEMPERATURE], "CAL_TEMPERATURE", "Temperature", "%.f", -50, 50, 1, 0);
70  IUFillNumber(&CalibrationN[CAL_PRESSURE], "CAL_PRESSURE", "Pressure", "%.f", -100, 100, 10, 0);
71  IUFillNumber(&CalibrationN[CAL_HUMIDITY], "CAL_HUMIDITY", "Humidity", "%.f", -50, 50, 1, 0);
72  IUFillNumberVector(&CalibrationNP, CalibrationN, 3, getDeviceName(), "CALIBRATION", "Calibration", MAIN_CONTROL_TAB, IP_RW,
73  0, IPS_IDLE);
74 
75  // Firmware Information
76  IUFillText(&FirmwareT[0], "VERSION", "Version", "--");
77  IUFillTextVector(&FirmwareTP, FirmwareT, 1, getDeviceName(), "DEVICE_FIRMWARE", "Firmware", MAIN_CONTROL_TAB, IP_RO, 0,
78  IPS_IDLE);
79 
81 
83 
84  return true;
85 }
86 
88 {
90 
91  if (isConnected())
92  {
93  defineProperty(&CalibrationNP);
94  defineProperty(&ResetSP);
95  defineProperty(&FirmwareTP);
96  }
97  else
98  {
99  deleteProperty(CalibrationNP.name);
100  deleteProperty(ResetSP.name);
101  deleteProperty(FirmwareTP.name);
102  }
103 
104  return true;
105 }
106 
108 {
109  //tty_set_debug(1);
110 
111  AckResponse rc = ACK_ERROR;
112 
113  for (int i = 0; i < 3; i++)
114  {
115  rc = ack();
116  if (rc != ACK_ERROR)
117  break;
118  }
119 
120  if (rc == ACK_OK_STARTUP)
121  {
122  getCalibration(false);
123  return true;
124  }
125  else if (rc == ACK_OK_INIT)
126  {
127  //getCalibration(true);
128  CalibrationNP.s = IPS_BUSY;
129  return true;
130  }
131 
132  return false;
133 }
134 
136 {
137  char response[MBOX_BUF];
138 
139  if (CalibrationNP.s == IPS_BUSY)
140  {
141  if (getCalibration(true))
142  {
143  CalibrationNP.s = IPS_OK;
144  IDSetNumber(&CalibrationNP, nullptr);
145  }
146  }
147 
148  int nbytes_read = 0;
149 
150  if (isSimulation())
151  {
152  strncpy(response, "$PXDR,P,96276.0,P,0,C,31.8,C,1,H,40.8,P,2,C,16.8,C,3,1.1*31\r\n", MBOX_BUF);
153  nbytes_read = strlen(response);
154  }
155  else
156  {
157  int rc = -1;
158  if ((rc = tty_read_section(PortFD, response, 0xA, MBOX_TIMEOUT, &nbytes_read)) != TTY_OK)
159  {
160  char errstr[MAXRBUF];
161  tty_error_msg(rc, errstr, MAXRBUF);
162  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
163  return IPS_ALERT;
164  }
165 
166  tcflush(PortFD, TCIOFLUSH);
167  }
168 
169  // Remove \r\n
170  response[nbytes_read - 2] = '\0';
171 
172  LOGF_DEBUG("RES <%s>", response);
173 
174  if (verifyCRC(response) == false)
175  {
176  LOG_ERROR("CRC check failed!");
177  return IPS_ALERT;
178  }
179 
180  // Remove * and checksum
181  char *end = strstr(response, "*");
182  *end = '\0';
183 
184  // PXDR
185  std::vector<std::string> result = split(response, ",");
186  // Convert Pascal to mbar
187  setParameterValue("WEATHER_BAROMETER", std::stod(result[SENSOR_PRESSURE]) / 100.0);
188  setParameterValue("WEATHER_TEMPERATURE", std::stod(result[SENSOR_TEMPERATURE]));
189  setParameterValue("WEATHER_HUMIDITY", std::stod(result[SENSOR_HUMIDITY]));
190  setParameterValue("WEATHER_DEWPOINT", std::stod(result[SENSOR_DEW]));
191  if (strcmp(result[FIRMWARE].c_str(), FirmwareT[0].text))
192  {
193  IUSaveText(&FirmwareT[0], result[FIRMWARE].c_str());
194  FirmwareTP.s = IPS_OK;
195  IDSetText(&FirmwareTP, nullptr);
196  }
197 
198  return IPS_OK;
199 }
200 
201 MBox::AckResponse MBox::ack()
202 {
203  char response[MBOX_BUF] = {0};
204  int nbytes_read = 0;
205 
206  if (isSimulation())
207  {
208  strncpy(response, "MBox by Astromi.ch\r\n", 64);
209  nbytes_read = strlen(response);
210  }
211  else
212  {
213  char errstr[MAXRBUF] = {0};
214  int rc = -1;
215 
216  if ((rc = tty_read_section(PortFD, response, 0xA, MBOX_TIMEOUT, &nbytes_read)) != TTY_OK)
217  {
218  tty_error_msg(rc, errstr, MAXRBUF);
219  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
220  return ACK_ERROR;
221  }
222 
223  // Read again if we only recieved a newline character
224  if (response[0] == '\n')
225  {
226  if ((rc = tty_read_section(PortFD, response, 0xA, MBOX_TIMEOUT, &nbytes_read)) != TTY_OK)
227  {
228  tty_error_msg(rc, errstr, MAXRBUF);
229  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
230  return ACK_ERROR;
231  }
232  }
233  }
234 
235  // Remove \r\n
236  response[nbytes_read - 2] = '\0';
237 
238  LOGF_DEBUG("RES <%s>", response);
239 
240  if (strstr(response, "MBox"))
241  return ACK_OK_STARTUP;
242  // Check if already initialized
243  else if (strstr(response, "PXDR"))
244  return ACK_OK_INIT;
245 
246  return ACK_ERROR;
247 }
248 
249 bool MBox::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
250 {
251  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
252  {
253  if (!strcmp(name, CalibrationNP.name))
254  {
255  double prevPressure = CalibrationN[CAL_PRESSURE].value;
256  double prevTemperature = CalibrationN[CAL_TEMPERATURE].value;
257  double prevHumidaty = CalibrationN[CAL_HUMIDITY].value;
258  IUUpdateNumber(&CalibrationNP, values, names, n);
259  double targetPressure = CalibrationN[CAL_PRESSURE].value;
260  double targetTemperature = CalibrationN[CAL_TEMPERATURE].value;
261  double targetHumidity = CalibrationN[CAL_HUMIDITY].value;
262 
263  bool rc = true;
264  if (targetPressure != prevPressure)
265  {
266  rc = setCalibration(CAL_PRESSURE);
267  usleep(200000);
268  }
269  if (targetTemperature != prevTemperature)
270  {
271  rc = setCalibration(CAL_TEMPERATURE);
272  usleep(200000);
273  }
274  if (targetHumidity != prevHumidaty)
275  {
276  rc = setCalibration(CAL_HUMIDITY);
277  }
278 
279  CalibrationNP.s = rc ? IPS_OK : IPS_ALERT;
280  IDSetNumber(&CalibrationNP, nullptr);
281  return true;
282  }
283  }
284 
285  return INDI::Weather::ISNewNumber(dev, name, values, names, n);
286 }
287 
288 bool MBox::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
289 {
290  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
291  {
292  if (!strcmp(name, ResetSP.name))
293  {
294  if (resetCalibration())
295  {
296  ResetSP.s = IPS_OK;
297  IDSetSwitch(&ResetSP, nullptr);
298  LOG_INFO("Calibration values are reset.");
299 
300  CalibrationN[CAL_PRESSURE].value = 0;
301  CalibrationN[CAL_TEMPERATURE].value = 0;
302  CalibrationN[CAL_HUMIDITY].value = 0;
303  CalibrationNP.s = IPS_IDLE;
304  IDSetNumber(&CalibrationNP, nullptr);
305  }
306  else
307  {
308  ResetSP.s = IPS_ALERT;
309  IDSetSwitch(&ResetSP, nullptr);
310  }
311 
312 
313  return true;
314  }
315  }
316 
317  return INDI::Weather::ISNewSwitch(dev, name, states, names, n);
318 }
319 
320 bool MBox::getCalibration(bool sendCommand)
321 {
322  int nbytes_written = 0, nbytes_read = 0;
323 
324  const char *command = ":calget*";
325  char response[MBOX_BUF];
326 
327  if (sendCommand)
328  LOGF_DEBUG("CMD <%s>", command);
329 
330  if (isSimulation())
331  {
332  strncpy(response, "$PCAL,P,20,T,50,H,-10*79\r\n", 64);
333  nbytes_read = strlen(response);
334  }
335  else
336  {
337  int rc = -1;
338  char errstr[MAXRBUF] = {0};
339 
340  if (sendCommand)
341  {
342  tcflush(PortFD, TCIOFLUSH);
343 
344  if ((rc = tty_write(PortFD, command, strlen(command), &nbytes_written)) != TTY_OK)
345  {
346  tty_error_msg(rc, errstr, MAXRBUF);
347  LOGF_ERROR("%s write error: %s.", __FUNCTION__, errstr);
348  return false;
349  }
350  }
351 
352  if ((rc = tty_read_section(PortFD, response, 0xA, MBOX_TIMEOUT, &nbytes_read)) != TTY_OK)
353  {
354  tty_error_msg(rc, errstr, MAXRBUF);
355  LOGF_ERROR("%s read error: %s.", __FUNCTION__, errstr);
356  return false;
357  }
358 
359  // If token is invalid, read again
360  if (strstr(response, "$PCAL") == nullptr)
361  {
362  if ((rc = tty_read_section(PortFD, response, 0xA, MBOX_TIMEOUT, &nbytes_read)) != TTY_OK)
363  {
364  tty_error_msg(rc, errstr, MAXRBUF);
365  LOGF_ERROR("%s read error: %s.", __FUNCTION__, errstr);
366  return false;
367  }
368  }
369  }
370 
371  // Remove \r\n
372  response[nbytes_read - 2] = '\0';
373 
374  LOGF_DEBUG("RES <%s>", response);
375 
376  if (verifyCRC(response) == false)
377  {
378  LOG_ERROR("CRC check failed!");
379  return false;
380  }
381 
382  // Remove * and checksum
383  char *end = strstr(response, "*");
384  *end = '\0';
385 
386  // PCAL
387  std::vector<std::string> result = split(response, ",");
388  CalibrationN[CAL_PRESSURE].value = std::stod(result[SENSOR_PRESSURE]) / 10.0;
389  CalibrationN[CAL_TEMPERATURE].value = std::stod(result[SENSOR_PRESSURE + 2]) / 10.0;
390  CalibrationN[CAL_HUMIDITY].value = std::stod(result[SENSOR_PRESSURE + 4]) / 10.0;
391  return true;
392 }
393 
394 bool MBox::setCalibration(CalibrationType type)
395 {
396  int nbytes_written = 0, rc = -1;
397  char errstr[MAXRBUF];
398  char command[16] = {0};
399 
400  if (type == CAL_PRESSURE)
401  {
402  // Pressure.
403  snprintf(command, 16, ":calp,%d*", static_cast<int32_t>(CalibrationN[CAL_PRESSURE].value * 10.0));
404 
405  LOGF_DEBUG("CMD <%s>", command);
406 
407  if (isSimulation() == false)
408  {
409  tcflush(PortFD, TCIOFLUSH);
410 
411  if ((rc = tty_write(PortFD, command, strlen(command), &nbytes_written)) != TTY_OK)
412  {
413  tty_error_msg(rc, errstr, MAXRBUF);
414  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
415  return false;
416  }
417 
418  }
419  }
420  else if (type == CAL_TEMPERATURE)
421  {
422  // Temperature
423  snprintf(command, 16, ":calt,%d*", static_cast<int32_t>(CalibrationN[CAL_TEMPERATURE].value * 10.0));
424 
425  LOGF_DEBUG("CMD <%s>", command);
426 
427  if (isSimulation() == false)
428  {
429  tcflush(PortFD, TCIOFLUSH);
430 
431  if ((rc = tty_write(PortFD, command, strlen(command), &nbytes_written)) != TTY_OK)
432  {
433  tty_error_msg(rc, errstr, MAXRBUF);
434  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
435  return false;
436  }
437  }
438  }
439  else
440  {
441  // Humidity
442  snprintf(command, 16, ":calh,%d*", static_cast<int32_t>(CalibrationN[CAL_HUMIDITY].value * 10.0));
443 
444  LOGF_DEBUG("CMD <%s>", command);
445 
446  if (isSimulation() == false)
447  {
448  tcflush(PortFD, TCIOFLUSH);
449 
450  if ((rc = tty_write(PortFD, command, strlen(command), &nbytes_written)) != TTY_OK)
451  {
452  tty_error_msg(rc, errstr, MAXRBUF);
453  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
454  return false;
455  }
456 
457  }
458  }
459 
460  return getCalibration(false);
461 }
462 
463 bool MBox::resetCalibration()
464 {
465  const char *command = ":calreset*";
466  LOGF_DEBUG("CMD <%s>", command);
467 
468  if (isSimulation() == false)
469  {
470  int nbytes_written = 0, rc = -1;
471  tcflush(PortFD, TCIOFLUSH);
472 
473  if ((rc = tty_write(PortFD, command, strlen(command), &nbytes_written)) != TTY_OK)
474  {
475  char errstr[MAXRBUF];
476  tty_error_msg(rc, errstr, MAXRBUF);
477  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
478  return false;
479  }
480  }
481 
482  return true;
483 }
484 
485 bool MBox::verifyCRC(const char *response)
486 {
487  // Start with $ and ends with * followed by checksum value of the response
488  uint8_t calculated_checksum = 0, response_checksum = 0;
489  // Skip starting $. Copy string
490  char checksum_string[MBOX_BUF] = {0};
491  strncpy(checksum_string, response + 1, MBOX_BUF);
492 
493  std::vector<std::string> result = split(checksum_string, R"(\*)");
494 
495  // Hex value
496  try
497  {
498  response_checksum = std::stoi(result[1], nullptr, 16);
499  }
500  catch (...)
501  {
502  return false;
503  }
504 
505 
506  // Calculate checksum of message XOR
507  for (auto oneByte : result[0])
508  calculated_checksum ^= oneByte;
509 
510  return (calculated_checksum == response_checksum);
511 }
512 
516 std::vector<std::string> MBox::split(const std::string &input, const std::string &regex)
517 {
518  // passing -1 as the submatch index parameter performs splitting
519  std::regex re(regex);
520  std::sregex_token_iterator
521  first{input.begin(), input.end(), re, -1},
522  last;
523  return {first, last};
524 }
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
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)
bool isSimulation() const
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
void setParameterValue(std::string name, double value)
setParameterValue Update weather parameter value
bool setCriticalParameter(std::string param)
setCriticalParameter Set parameter that is considered critical to the operation of the observatory....
void addParameter(std::string name, std::string label, double numMinOk, double numMaxOk, double percWarning)
addParameter Add a physical weather measurable parameter to the weather driver. The weather value has...
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition: indiweather.cpp:85
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.
Connection::Serial * serialConnection
Definition: indiweather.h:133
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: indiweather.cpp:41
Definition: mbox.h:30
virtual IPState updateWeather() override
updateWeather Update weather conditions from device or service. The function should not change the st...
Definition: mbox.cpp:135
MBox()
Definition: mbox.cpp:42
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition: mbox.cpp:87
virtual bool Handshake() override
perform handshake with device to check communication
Definition: mbox.cpp:107
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: mbox.cpp:52
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition: mbox.cpp:288
virtual const char * getDefaultName() override
Definition: mbox.cpp:47
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition: mbox.cpp:249
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
IPState
Property state.
Definition: indiapi.h:160
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
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:566
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition: indicom.c:424
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition: indicom.c:1167
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 IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:291
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indidevapi.c:36
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition: indidevapi.c:158
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
Definition: indidevapi.c:198
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 IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:235
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:1362
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
Definition: indidriver.c:1191
#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
#define MAXRBUF
Definition: indiserver.cpp:102
#define MBOX_TIMEOUT
Definition: mbox.cpp:36
#define MBOX_BUF
Definition: mbox.cpp:37
__le16 type
Definition: pwc-ioctl.h:0
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250