Instrument Neutral Distributed Interface INDI 2.2.5
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.
40static std::unique_ptr<MBox> mbox(new MBox());
41
43{
44 setVersion(1, 1);
45}
46
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 ResetSP[0].fill("RESET", "Reset", ISS_OFF);
65 ResetSP.fill(getDeviceName(), "CALIBRATION_RESET", "Reset", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY,
66 0, IPS_IDLE);
67
68 // Calibration Properties
69 CalibrationNP[CAL_TEMPERATURE].fill("CAL_TEMPERATURE", "Temperature", "%.f", -50, 50, 1, 0);
70 CalibrationNP[CAL_PRESSURE].fill("CAL_PRESSURE", "Pressure", "%.f", -100, 100, 10, 0);
71 CalibrationNP[CAL_HUMIDITY].fill("CAL_HUMIDITY", "Humidity", "%.f", -50, 50, 1, 0);
72 CalibrationNP.fill(getDeviceName(), "CALIBRATION", "Calibration", MAIN_CONTROL_TAB, IP_RW,
73 0, IPS_IDLE);
74
75 // Firmware Information
76 FirmwareTP[0].fill("VERSION", "Version", "--");
77 FirmwareTP.fill(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);
100 deleteProperty(ResetSP);
101 deleteProperty(FirmwareTP);
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.setState(IPS_BUSY);
129 return true;
130 }
131
132 return false;
133}
134
136{
137 char response[MBOX_BUF];
138
139 if (CalibrationNP.getState() == IPS_BUSY)
140 {
141 if (getCalibration(true))
142 {
143 CalibrationNP.setState(IPS_OK);
144 CalibrationNP.apply();
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(), FirmwareTP[0].getText()))
192 {
193 FirmwareTP[0].setText(result[FIRMWARE].c_str());
194 FirmwareTP.setState(IPS_OK);
195 FirmwareTP.apply();
196 }
197
198 return IPS_OK;
199}
200
201MBox::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 received 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
249bool 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 (CalibrationNP.isNameMatch(name))
254 {
255 double prevPressure = CalibrationNP[CAL_PRESSURE].getValue();
256 double prevTemperature = CalibrationNP[CAL_TEMPERATURE].getValue();
257 double prevHumidaty = CalibrationNP[CAL_HUMIDITY].getValue();
258 CalibrationNP.update(values, names, n);
259 double targetPressure = CalibrationNP[CAL_PRESSURE].getValue();
260 double targetTemperature = CalibrationNP[CAL_TEMPERATURE].getValue();
261 double targetHumidity = CalibrationNP[CAL_HUMIDITY].getValue();
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.setState(rc ? IPS_OK : IPS_ALERT);
280 CalibrationNP.apply();
281 return true;
282 }
283 }
284
285 return INDI::Weather::ISNewNumber(dev, name, values, names, n);
286}
287
288bool 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 (ResetSP.isNameMatch(name))
293 {
294 if (resetCalibration())
295 {
296 ResetSP.setState(IPS_OK);
297 ResetSP.apply();
298 LOG_INFO("Calibration values are reset.");
299
300 CalibrationNP[CAL_PRESSURE].setValue(0);
301 CalibrationNP[CAL_TEMPERATURE].setValue(0);
302 CalibrationNP[CAL_HUMIDITY].setValue(0);
303 CalibrationNP.setState(IPS_IDLE);
304 CalibrationNP.apply();
305 }
306 else
307 {
308 ResetSP.setState(IPS_ALERT);
309 ResetSP.apply();
310 }
311
312
313 return true;
314 }
315 }
316
317 return INDI::Weather::ISNewSwitch(dev, name, states, names, n);
318}
319
320bool 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 CalibrationNP[CAL_PRESSURE].setValue(std::stod(result[SENSOR_PRESSURE]) / 10.0);
389 CalibrationNP[CAL_TEMPERATURE].setValue(std::stod(result[SENSOR_PRESSURE + 2]) / 10.0);
390 CalibrationNP[CAL_HUMIDITY].setValue(std::stod(result[SENSOR_PRESSURE + 4]) / 10.0);
391 return true;
392}
393
394bool 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>(CalibrationNP[CAL_PRESSURE].getValue() * 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>(CalibrationNP[CAL_TEMPERATURE].getValue() * 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>(CalibrationNP[CAL_HUMIDITY].getValue() * 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
463bool 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
485bool 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 snprintf(checksum_string, MBOX_BUF, "%s", response + 1);
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
516std::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
INDI::PropertyText getText(const char *name) const
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 setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool isNameMatch(const char *otherName) const
bool update(const double values[], 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)
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)
void addParameter(std::string name, std::string label, double numMinOk, double numMaxOk, double percWarning, bool flipWarning=false)
Add a physical weather measurable parameter to the weather driver.
bool setCriticalParameter(std::string name)
setCriticalParameter Set parameter that is considered critical to the operation of the observatory....
void setParameterValue(std::string name, double value)
setParameterValue Update weather parameter value
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
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
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
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
Initialize 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:148
@ ISS_OFF
Definition indiapi.h:149
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_1OFMANY
Definition indiapi.h:171
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 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 getDeviceName
#define MBOX_TIMEOUT
Definition mbox.cpp:36
#define MBOX_BUF
Definition mbox.cpp:37
uint16_t type
Definition pwc-ioctl.h:0