Instrument Neutral Distributed Interface INDI  2.0.2
mydcp4esp32.cpp
Go to the documentation of this file.
1 /*
2  myDCP4ESP32
3  Copyright (C) 2023 Stephen Hillier
4 
5  Based on MyFocuserPro2 Focuser
6  Copyright (C) 2019 Alan Townshend
7 
8  As well as USB_Dewpoint
9  Copyright (C) 2017-2023 Jarno Paananen
10 
11  And INDI Sky Quality Meter Driver
12  Copyright(c) 2016 Jasem Mutlaq. All rights reserved.
13 
14  This library is free software; you can redistribute it and/or
15  modify it under the terms of the GNU Lesser General Public
16  License as published by the Free Software Foundation; either
17  version 2.1 of the License, or (at your option) any later version.
18 
19  This library is distributed in the hope that it will be useful,
20  but WITHOUT ANY WARRANTY; without even the implied warranty of
21  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22  Lesser General Public License for more details.
23 
24  You should have received a copy of the GNU Lesser General Public
25  License along with this library; if not, write to the Free Software
26  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
27 
28 */
29 
30 #include "mydcp4esp32.h"
33 #include "indicom.h"
34 
35 #include <cstring>
36 #include <string>
37 #include <memory>
38 
39 #include <termios.h>
40 #include <unistd.h>
41 
42 #define USBDEWPOINT_TIMEOUT 3
43 
44 std::unique_ptr<MyDCP4ESP> mydcp4esp(new MyDCP4ESP());
45 
47 {
48 
50 
51 }
52 
54 {
55  DefaultDevice::initProperties();
56 
57  /* Channel duty cycles */
58  ChannelPowerNP[0].fill("CHANNEL1", "Channel 1", "%3.0f", 0., 100., 0., 0.);
59  ChannelPowerNP[1].fill("CHANNEL2", "Channel 2", "%3.0f", 0., 100., 0., 0.);
60  ChannelPowerNP[2].fill("CHANNEL3", "Channel 3", "%3.0f", 0., 100., 0., 0.);
61  ChannelPowerNP[3].fill("CHANNEL4", "Channel 4", "%3.0f", 0., 100., 0., 0.);
62  ChannelPowerNP.fill(getDeviceName(), "OUTPUT", "Power", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
63 
64  /* Temperature Probe Found status */
65  TempProbeFoundSP[0].fill("PROBE1", "Probe 1", ISS_OFF);
66  TempProbeFoundSP[1].fill("PROBE2", "Probe 2", ISS_OFF);
67  TempProbeFoundSP[2].fill("PROBE3", "Probe 3", ISS_OFF);
68  TempProbeFoundSP[3].fill("PROBE4", "Probe 4", ISS_OFF);
69  TempProbeFoundSP.fill(getDeviceName(), "SENSORS", "Sensors", MAIN_CONTROL_TAB, IP_RO, ISR_NOFMANY, 0, IPS_IDLE);
70 
71  /* Temperatures */
72  TemperatureNP[0].fill("CHANNEL1", "Channel 1", "%3.2f", -50., 120., 0., 0.);
73  TemperatureNP[1].fill("CHANNEL2", "Channel 2", "%3.2f", -50., 120., 0., 0.);
74  TemperatureNP[2].fill("CHANNEL3", "Channel 3", "%3.2f", -50., 120., 0., 0.);
75  TemperatureNP[3].fill("CHANNEL4", "Channel 4", "%3.2f", -50., 120., 0., 0.);
76  TemperatureNP.fill(getDeviceName(), "TEMPERATURE", "Temperature", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
77 
78  /* Ambient Temperature */
79  AmbientTemperatureNP[0].fill("AMBIENT", "Ambient", "%3.2f", 0., 100., 0., 0.);
80  AmbientTemperatureNP.fill(getDeviceName(), "AMBIENT", "Temperature", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
81 
82  /* Humidity */
83  HumidityNP[0].fill("HUMIDITY", "Humidity", "%3.2f", 0., 100., 0., 0.);
84  HumidityNP.fill(getDeviceName(), "HUMIDITY", "Humidity", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
85 
86  /* Dew point */
87  DewpointNP[0].fill("DEWPOINT", "Dew point", "%3.2f", -50., 120., 0., 0.);
88  DewpointNP.fill(getDeviceName(), "DEWPOINT", "Dew point", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
89 
90  /* Temperature calibration values */
91  ChannelOffsetNP[0].fill("CHANNEL1", "Channel 1", "%1.2f", -5., 5., 0.25, 0.);
92  ChannelOffsetNP[1].fill("CHANNEL2", "Channel 2", "%1.2f", -5., 5., 0.25, 0.);
93  ChannelOffsetNP[2].fill("CHANNEL3", "Channel 3", "%1.2f", -5., 5., 0.25, 0.);
94  ChannelOffsetNP[3].fill("CHANNEL4", "Channel 4", "%1.2f", -5., 5., 0.25, 0.);
95  ChannelOffsetNP.fill(getDeviceName(), "TEMPOFFSET", "T Offset", OPTIONS_TAB, IP_RW, 0, IPS_IDLE);
96 
97  /* Ambient Temperature Offset */
98  AmbientOffsetNP[0].fill("AMBIENT", "Ambient", "%1.2f", -4., 3., 0.25, 0.);
99  AmbientOffsetNP.fill(getDeviceName(), "AMBIENTOFFSET", "T Offset", OPTIONS_TAB, IP_RW, 0, IPS_IDLE);
100 
101  /* Tracking Offset */
102  TrackingOffsetNP[0].fill("TRACKING", "Tracking", "%1.0f", -4, 3, 1, 0);
103  TrackingOffsetNP.fill(getDeviceName(), "TRACKING", "T Offset", OPTIONS_TAB, IP_RW, 0, IPS_IDLE);
104 
105  /* Tracking mode */
106  TrackingModeSP[0].fill("AMBIENT", "Ambient", ISS_ON);
107  TrackingModeSP[1].fill("DEWPOINT", "Dewpoint", ISS_OFF);
108  TrackingModeSP[2].fill("MIDPOINT", "Midpoint", ISS_OFF);
109  TrackingModeSP.fill(getDeviceName(), "TRACKINGMODE", "Tracking", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
110 
111  /* Channel 3 operating Mode */
112  Ch3ModeSP[0].fill("DISABLED", "Disabled", ISS_OFF);
113  Ch3ModeSP[1].fill("CHANNEL1", "Channel 1", ISS_OFF);
114  Ch3ModeSP[2].fill("CHANNEL2", "Channel 2", ISS_OFF);
115  Ch3ModeSP[3].fill("MANUAL", "Manual", ISS_OFF);
116  Ch3ModeSP[4].fill("CHANNEL3", "Channel 3", ISS_ON);
117  Ch3ModeSP.fill(getDeviceName(), "CH3MODE", "Ch3 Mode", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
118 
119  /* Channel 3 Manual Power Setting */
120  Ch3ManualPowerNP[0].fill("CH3MANUAL", "Power", "%3.0f", 0, 100, 10, 0);
121  Ch3ManualPowerNP.fill(getDeviceName(), "CH3MANUAL", "Ch3 Manual", OPTIONS_TAB, IP_RW, 0, IPS_IDLE);
122 
123  /* Channel 100% Boost On/Off */
124  ChannelBoostSP[0].fill("CHANNEL1", "Channel 1", ISS_OFF);
125  ChannelBoostSP[1].fill("CHANNEL2", "Channel 2", ISS_OFF);
126  ChannelBoostSP[2].fill("CHANNEL3", "Channel 3", ISS_OFF);
127  ChannelBoostSP[3].fill("CHANNEL4", "Channel 4", ISS_OFF);
128  ChannelBoostSP[4].fill("RESETALL", "Reset All", ISS_OFF);
129  ChannelBoostSP.fill(getDeviceName(), "CHANNELBOOST", "100% Boost", OPTIONS_TAB, IP_RW, ISR_NOFMANY, 0, IPS_IDLE);
130 
131  /* Reset settings */
132  RebootSP[0].fill("REBOOT", "Reboot", ISS_OFF);
133  RebootSP.fill(getDeviceName(), "REBOOT", "Controller", CONNECTION_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
134 
135  /* Firmware version */
136  FWversionNP[0].fill("FIRMWARE", "Firmware Version", "%3.0f", 0, 999, 0, 0);
137  FWversionNP.fill(getDeviceName(), "FW_VERSION", "Firmware", CONNECTION_TAB, IP_RO, 0, IPS_IDLE);
138 
139  /* Controller check code */
140  CheckCodeTP[0].fill("CNTR_CODE", "Handshake Code", nullptr);
141  CheckCodeTP.fill(getDeviceName(), "CNTR_CODE", "Controller", CONNECTION_TAB, IP_RO, 0, IPS_IDLE);
142 
144 
145  addDebugControl();
149 
150  // No simulation control for now
151 
152  if (mdcpConnection & CONNECTION_SERIAL)
153  {
154  serialConnection = new Connection::Serial(this);
155  serialConnection->registerHandshake([&]()
156  {
157  return Handshake();
158  });
160  registerConnection(serialConnection);
161  }
162 
163  if (mdcpConnection & CONNECTION_TCP)
164  {
165  tcpConnection = new Connection::TCP(this);
166  tcpConnection->setDefaultHost("192.168.4.1");
167  tcpConnection->setDefaultPort(3131);
168  tcpConnection->registerHandshake([&]()
169  {
170  return Handshake();
171  });
172 
173  registerConnection(tcpConnection);
174  }
175 
176 
177  return true;
178 }
179 
181 {
182  DefaultDevice::updateProperties();
183 
184  if (isConnected())
185  {
186  defineProperty(TrackingModeSP);
187  if (myDCP4Firmware > 109) // Firmware 109 has a bug with setting Tracking offsets
188  defineProperty(TrackingOffsetNP);
189  defineProperty(AmbientTemperatureNP);
190  defineProperty(AmbientOffsetNP);
191  defineProperty(HumidityNP);
192  defineProperty(DewpointNP);
193  defineProperty(TempProbeFoundSP);
194  defineProperty(TemperatureNP);
195  defineProperty(ChannelPowerNP);
196  defineProperty(ChannelOffsetNP);
197  if (myDCP4Firmware > 109) // Firmware 109 has a bug with the 100% boost settings
198  defineProperty(ChannelBoostSP);
199  defineProperty(Ch3ModeSP);
200  defineProperty(RebootSP);
201  defineProperty(FWversionNP);
202  defineProperty(CheckCodeTP);
203  ch3ManualPower = false;
204  loadConfig(true);
205  readSettings();
206  LOG_INFO("myDCP4ESP32 parameters updated, device ready for use.");
207  timerIndex = SetTimer(getCurrentPollingPeriod());
208  }
209  else
210  {
211  deleteProperty(TrackingModeSP);
212  if (myDCP4Firmware > 109) // Firmware 109 has a bug with setting Tracking offsets
213  deleteProperty(TrackingOffsetNP);
214  deleteProperty(AmbientTemperatureNP);
215  deleteProperty(AmbientOffsetNP);
216  deleteProperty(HumidityNP);
217  deleteProperty(DewpointNP);
218  deleteProperty(TempProbeFoundSP);
219  deleteProperty(TemperatureNP);
220  deleteProperty(ChannelPowerNP);
221  deleteProperty(ChannelOffsetNP);
222  if (myDCP4Firmware > 109) // Firmware 109 has a bug with the 100% boost settings
223  deleteProperty(ChannelBoostSP);
224  deleteProperty(Ch3ModeSP);
225  deleteProperty(RebootSP);
226  deleteProperty(FWversionNP);
227  deleteProperty(CheckCodeTP);
228  if (ch3ManualPower == true)
229  {
230  deleteProperty(Ch3ManualPowerNP);
231  ch3ManualPower = false;
232  }
233  }
234 
235  return true;
236 }
237 
239 {
240  return "MyDCP4ESP32";
241 }
242 
243 // sleep for a number of milliseconds
244 int MyDCP4ESP::msleep( long duration)
245 {
246  struct timespec ts;
247  int res;
248 
249  if (duration < 0)
250  {
251  errno = EINVAL;
252  return -1;
253  }
254 
255  ts.tv_sec = duration / 1000;
256  ts.tv_nsec = (duration % 1000) * 1000000;
257 
258  do
259  {
260  res = nanosleep(&ts, &ts);
261  }
262  while (res && errno == EINTR);
263 
264  return res;
265 }
266 
267 bool MyDCP4ESP::sendCommand(const char *cmd, char *resp)
268 {
269  int nbytes_written = 0, nbytes_read = 0, rc = -1;
270  char errstr[MAXRBUF];
271 
272  LOGF_DEBUG("CMD <%s>", cmd);
273  tcflush(PortFD, TCIOFLUSH);
274  if ((rc = tty_write_string(PortFD, cmd, &nbytes_written)) != TTY_OK)
275  {
276  tty_error_msg(rc, errstr, MAXRBUF);
277  LOGF_ERROR("Error writing command %s: %s.", cmd, errstr);
278  return false;
279  }
280 
281  // Small delay to allow controller to process command
282  msleep(MDCP_SMALL_DELAY);
283 
284  if (resp)
285  {
286  if ((rc = tty_nread_section(PortFD, resp, MDCP_RESPONSE_LENGTH, '#', MDCP_READ_TIMEOUT, &nbytes_read)) != TTY_OK)
287  {
288  tty_error_msg(rc, errstr, MAXRBUF);
289  LOGF_ERROR("Error reading response for command <%s>: %s.", cmd, errstr);
290  return false;
291  }
292 
293  if (nbytes_read < 2)
294  {
295  LOGF_ERROR("Invalid response <%s> for command <%s>.", resp, cmd);
296  return false;
297  }
298 
299  LOGF_DEBUG("RESP <%s>", resp);
300  resp[nbytes_read - 1] = '\0'; // Strip "#" termination character
301  }
302 
303  return true;
304 }
305 
306 // Determine which of the 4 channels have temperature probes attached. Only those with probes can be active
307 // except for Channel 3 which can mirrior Channels 1 & 2 or be controlled manually.
308 // Check to see if each channel can be set to Overide if it doesn't currently have a power output
309 bool MyDCP4ESP::getActiveChannels()
310 {
311  char cmd[MDCP_CMD_LENGTH] = {};
312  char resp[MDCP_RESPONSE_LENGTH] = {};
313  int output[4] = {0, 0, 0, 0};
314  int ok, i;
315  unsigned int channel_boost = 0;
316  unsigned int currentCh3Mode = 0;
317 
318  // Default all channels to active in case channel testing fails
319  i = 0;
320  while (i <= 3)
321  {
322  channelActive[i] = 1;
323  TempProbeFoundSP[i].setState(ISS_ON);
324  i++;
325  }
326 
327  TempProbeFoundSP.setState(IPS_IDLE); // Keep state Idle unless positive exit
328  TempProbeFoundSP.apply();
329 
330  if (myDCP4Firmware > 109)
331  {
332  // TempProbeFoundSP.setState(IPS_BUSY);
333 
334  // Get current channel output to trim the test to those at zero
335  if (!sendCommand(MDCP_GET_ALL_CH_POWER_CMD, resp))
336  return false;
337 
338  ok = sscanf(resp, MDCP_GET_ALL_CH_POWER_RES, &output[0], &output[1], &output[2], &output[3] );
339 
340  if (ok == 4)
341  {
342 
343  i = 0;
344 
345  while (i <=3 ) // Go through each channel
346  {
347 
348  if ((output[i] == 0) || (i == 2)) // Only do test if Channel power is zero or is Channel 3 (i=2)
349  {
350  if (i == 2) // Channel 3 (i=2) is special and needs to be in use temp probe mode to test
351  {
352  memset(resp, '\0', MDCP_RESPONSE_LENGTH);
353 
354  if (!sendCommand(MDCP_GET_CH3_MODE_CMD, resp))
355  return false;
356 
357  ok = sscanf(resp, MDCP_GET_CH3_MODE_RES, &currentCh3Mode);
358 
359  if ((ok != 1) || (currentCh3Mode > 4))
360  return false;
361 
362  if (currentCh3Mode != CH3MODE_CH3TEMP)
363  if (!setCh3Mode( CH3MODE_CH3TEMP ))
364  return false;
365  }
366 
367  if (!setChannelBoost( i+1, 1))
368  return false;
369 
371 
372  memset(resp, '\0', MDCP_RESPONSE_LENGTH);
373 
374  if (!sendCommand(cmd, resp))
375  return false;
376 
377  ok = sscanf(resp, MDCP_GET_CH_OVERIDE_RES, &channel_boost);
378 
379  if ((ok == 1) && (channel_boost == 0))
380  {
381  TempProbeFoundSP[i].setState(ISS_OFF);
382  channelActive[i] = 0;
383  }
384 
385  if (!setChannelBoost( i+1, 0))
386  return false;
387 
388  if ((i == 2) && (currentCh3Mode != CH3MODE_CH3TEMP)) // Return Ch3 to previous mode
389  if (!setCh3Mode(currentCh3Mode))
390  return false;
391 
392  }
393 
394  i++;
395  }
396  }
397  else
398  return false;
399  }
400  else
401  return false;
402 
403  TempProbeFoundSP.setState(IPS_OK);
404  TempProbeFoundSP.apply();
405  return true;
406 }
407 
408 bool MyDCP4ESP::Handshake()
409 {
410  if (getActiveConnection() == serialConnection)
411  {
412  PortFD = serialConnection->getPortFD();
413  }
414  else if (getActiveConnection() == tcpConnection)
415  {
416  PortFD = tcpConnection->getPortFD();
417  }
418 
419  int tries = 2;
420  do
421  {
422  if (Ack())
423  {
424  LOG_INFO("myDCP4ESP32 is online. Getting device parameters...");
425  if (!getActiveChannels())
426  LOG_INFO("Could not determine active channels. Default to all active.");
427  return true;
428  }
429  LOG_INFO("Error retrieving data from myDCP4ESP32, retrying...");
430  }
431  while (--tries > 0 );
432 
433  LOG_INFO("Error retrieving data from myDCP4ESP32, please ensure controller "
434  "is powered and the port is correct.");
435 
436  return false;
437 }
438 
439 bool MyDCP4ESP::Ack()
440 {
441 
442  char resp[MDCP_RESPONSE_LENGTH] = {};
443  int firmware;
444  char code[10] = {};
445 
446 
447  if (!sendCommand(MDCP_GET_CONTROLLER_CODE_CMD, resp))
448  return false;
449 
450 
451  int ok = sscanf(resp, MDCP_GET_CONTROLLER_CODE_RES, code);
452 
453  if (ok != 1)
454  {
455  LOGF_ERROR("Get Handshake Code: Response <%s> for Command <%s> not recognized.", resp, MDCP_GET_CONTROLLER_CODE_CMD);
456  return false;
457  }
458 
459  CheckCodeTP[0].setText(code);
460  CheckCodeTP.setState(IPS_OK);
461  CheckCodeTP.apply();
462 
463  tcflush(PortFD, TCIOFLUSH);
464  memset(resp, '\0', MDCP_RESPONSE_LENGTH);
465 
466  if (!sendCommand(MDCP_GET_VERSION_CMD, resp))
467  return false;
468 
469  ok = sscanf(resp, MDCP_GET_VERSION_RES, &firmware);
470 
471  if (ok != 1)
472  {
473  LOGF_ERROR("Get Firmware Version: Response <%s> for Command <%s> not recognized.", resp, MDCP_GET_VERSION_CMD);
474  return false;
475  }
476 
477  myDCP4Firmware = firmware;
478  FWversionNP[0].setValue(firmware);
479  FWversionNP.setState(IPS_OK);
480  FWversionNP.apply();
481  return true;
482 }
483 
484 // Set the temperature offset for a channel
485 bool MyDCP4ESP::setChannelOffset(unsigned int channel, float value)
486 {
487  char cmd[MDCP_CMD_LENGTH] = {};
488 
489  switch (channel) {
490  case 1:
491  snprintf(cmd, MDCP_CMD_LENGTH, MDCP_SET_CH1_OFFSET_CMD, value);
492  break;
493 
494  case 2:
495  snprintf(cmd, MDCP_CMD_LENGTH, MDCP_SET_CH2_OFFSET_CMD, value);
496  break;
497 
498  case 3:
499  snprintf(cmd, MDCP_CMD_LENGTH, MDCP_SET_CH3_OFFSET_CMD, value);
500  break;
501 
502  case 4:
503  snprintf(cmd, MDCP_CMD_LENGTH, MDCP_SET_CH4_OFFSET_CMD, value);
504  break;
505 
506  default:
507  return false;
508  }
509 
510  return sendCommand(cmd, nullptr);
511 }
512 
513 // Set the offset for ambient temperature
514 bool MyDCP4ESP::setAmbientOffset(float value)
515 {
516  char cmd[MDCP_CMD_LENGTH] = {};
517 
519  return sendCommand(cmd, nullptr);
520 }
521 
522 // set or reset Channel overide. Channel = 5 resets all channels
523 bool MyDCP4ESP::setChannelBoost( unsigned int channel, unsigned int value)
524 {
525  char cmd[MDCP_CMD_LENGTH] = {};
526  if ((channel == 5) || (value == 0))
527  {
528  snprintf(cmd, MDCP_CMD_LENGTH, MDCP_RESET_CH_100_CMD, channel);
529  return sendCommand(cmd, nullptr);
530  }
531  else if (channel != 5)
532  {
533  snprintf(cmd, MDCP_CMD_LENGTH, MDCP_SET_CH_100_CMD, channel);
534  return sendCommand(cmd, nullptr);
535  }
536 
537  return false;
538 }
539 
540 // Set Tracking Mode (1=Ambient, 2=Dewpoint, 3=Midpoint)
541 bool MyDCP4ESP::setTrackingMode(unsigned int value)
542 {
543  char cmd[MDCP_CMD_LENGTH] = {};
544 
546  return sendCommand(cmd, nullptr);
547 }
548 
549 // Set the mode for Channel 3 control (0=disabled, 1=Channel 1, 2=Channel 2, 3=Manual, 4=use temp probe3)
550 bool MyDCP4ESP::setCh3Mode(unsigned int value)
551 {
552  char cmd[MDCP_CMD_LENGTH] = {};
553 
554  snprintf(cmd, MDCP_CMD_LENGTH, MDCP_SET_CH3_MODE_CMD, value);
555  return sendCommand(cmd, nullptr);
556 }
557 
558 // Set Channel 3 power output - Channel 3 must be in Manual mode.
559 bool MyDCP4ESP::setCh3Output(unsigned int value)
560 {
561  char cmd[MDCP_CMD_LENGTH] = {};
562 
564  return sendCommand(cmd, nullptr);
565 }
566 
567 // Set Tracking Offset
568 bool MyDCP4ESP::setTrackingOffset(int value)
569 {
570  char cmd[MDCP_CMD_LENGTH] = {};
571 
573  return sendCommand(cmd, nullptr);
574 }
575 
576 // Reboot the Dew Controller then wait to reconnect
577 bool MyDCP4ESP::rebootController()
578 {
579  LOG_INFO("Rebooting Controller and Disconnecting.");
580  sendCommand(MDCP_REBOOT_CMD, nullptr);
581 
582  if (!Disconnect())
583  LOG_INFO("Disconnect failed");
584  setConnected(false, IPS_IDLE);
586  LOG_INFO("Waiting 10 seconds before attempting to reconnect.");
587  RemoveTimer(timerIndex);
588 
589  int i = 1;
590  do
591  {
592  sleep(10);
593  if (!Connect())
594  {
595  i++;
596  if (i <= 5)
597  LOGF_INFO("Could not reconnect waiting 10 seconds before attempt %d of 5.", i);
598  else
599  {
600  LOGF_ERROR("Could not reconnect after %d attempts", i-1);
601  setConnected(false, IPS_OK);
602  }
603  }
604  else
605  {
606  i = 0;
607  setConnected(true, IPS_OK);
608  }
609  } while ((i != 0) && (i <= 5));
610 
611  return updateProperties();
612 }
613 
614 bool MyDCP4ESP::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
615 {
616 
617  if (!dev || strcmp(dev, getDeviceName()))
618  return false;
619 
620  if (ChannelBoostSP.isNameMatch(name))
621  {
622  if (states[4] == ISS_ON) // Reset all to ISS_OFF
623  {
624  setChannelBoost(5, 1);
625  }
626  else // Check current versus new state and only invoke for changes
627  {
628  for (int i = 0; i < 4; i++)
629  {
630  if (ChannelBoostSP[i].getState() != states[i] )
631  setChannelBoost(i+1, states[i]);
632  }
633 
634  }
635  ChannelBoostSP.update(states, names, n);
636  ChannelBoostSP.setState(IPS_BUSY);
637  ChannelBoostSP.apply();
638  readSettings();
639  return true;
640  }
641 
642  if (TrackingModeSP.isNameMatch(name))
643  {
644  TrackingModeSP.update(states, names, n);
645  TrackingModeSP.setState(IPS_BUSY);
646  TrackingModeSP.apply();
647  setTrackingMode(TrackingModeSP.findOnSwitchIndex() + 1);
648  readSettings();
649  return true;
650  }
651 
652  if (Ch3ModeSP.isNameMatch(name))
653  {
654  Ch3ModeSP.update(states, names, n);
655  Ch3ModeSP.setState(IPS_BUSY);
656  Ch3ModeSP.apply();
657  setCh3Mode(Ch3ModeSP.findOnSwitchIndex());
658  readSettings();
659  return true;
660  }
661 
662  if (RebootSP.isNameMatch(name))
663  {
664  RebootSP.reset();
665 
666  if (rebootController())
667  {
668  RebootSP.setState(IPS_OK);
669  }
670  else
671  {
672  RebootSP.setState(IPS_ALERT);
673  }
674 
675  RebootSP.apply();
676  return true;
677  }
678 
679  return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
680 }
681 
682 bool MyDCP4ESP::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
683 {
684 
685  if (!dev || strcmp(dev, getDeviceName()))
686  return false;
687 
688  if (ChannelOffsetNP.isNameMatch(name))
689  {
690  ChannelOffsetNP.update(values, names, n);
691  ChannelOffsetNP.setState(IPS_BUSY);
692  ChannelOffsetNP.apply();
693  setChannelOffset(1, ChannelOffsetNP[0].getValue());
694  setChannelOffset(2, ChannelOffsetNP[1].getValue());
695  setChannelOffset(3, ChannelOffsetNP[2].getValue());
696  setChannelOffset(4, ChannelOffsetNP[3].getValue());
697  readSettings();
698  return true;
699  }
700 
701  if (AmbientOffsetNP.isNameMatch(name))
702  {
703  AmbientOffsetNP.update(values, names, n);
704  AmbientOffsetNP.setState(IPS_BUSY);
705  AmbientOffsetNP.apply();
706  setAmbientOffset(AmbientOffsetNP[0].getValue());
707  readSettings();
708  return true;
709  }
710 
711  if (TrackingOffsetNP.isNameMatch(name))
712  {
713  TrackingOffsetNP.update(values, names, n);
714  TrackingOffsetNP.setState(IPS_BUSY);
715  TrackingOffsetNP.apply();
716  setTrackingOffset(TrackingOffsetNP[0].getValue());
717  readSettings();
718  return true;
719  }
720 
721  if (Ch3ManualPowerNP.isNameMatch(name))
722  {
723  Ch3ManualPowerNP.update(values, names, n);
724  Ch3ManualPowerNP.setState(IPS_BUSY);
725  Ch3ManualPowerNP.apply();
726  setCh3Output(Ch3ManualPowerNP[0].getValue());
727  readSettings();
728  return true;
729  }
730 
731  return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
732 }
733 
734 bool MyDCP4ESP::readSettings()
735 {
736  char resp[MDCP_RESPONSE_LENGTH] = {};
737  int ok = -1;
738  float temp1, temp2, temp3, temp4, temp_ambient, humidity, dewpoint;
739  unsigned int output1, output2, output3, output4;
740  float ambient_offset, offset1, offset2, offset3, offset4;
741  int tracking_offset;
742  unsigned int tracking_mode, ch3_mode, channel_boost;
743 
744 
745  // Get Ambient offset first
746  if (!sendCommand(MDCP_GET_AMBIENT_OFFSET_CMD, resp))
747  return false;
748 
749  ok = sscanf(resp, MDCP_GET_AMBIENT_OFFSET_RES, &ambient_offset );
750 
751  if (ok == 1)
752  {
753  AmbientOffsetNP[0].setValue(ambient_offset);
754  AmbientOffsetNP.setState(IPS_OK);
755  AmbientOffsetNP.apply();
756  }
757  else
758  LOGF_ERROR("Get Ambient Offset: Response <%s> for Command <%s> not recognized.", resp, MDCP_GET_AMBIENT_OFFSET_CMD);
759 
760  // Get the Ambient Temperature
761  memset(resp, '\0', MDCP_RESPONSE_LENGTH);
762 
763  if (!sendCommand(MDCP_GET_AMBIENT_TEMPERATURE_CMD, resp))
764  return false;
765 
766  ok = sscanf(resp, MDCP_GET_AMBIENT_TEMPERATURE_RES, &temp_ambient );
767 
768  // Set Ambient temperature adjusted by offset
769  if (ok == 1)
770  {
771  AmbientTemperatureNP[0].setValue(temp_ambient + ambient_offset);
772  AmbientTemperatureNP.setState(IPS_OK);
773  AmbientTemperatureNP.apply();
774  }
775  else
776  LOGF_ERROR("Get Ambient Temperature: Response <%s> for Command <%s> not recognized.", resp, MDCP_GET_AMBIENT_TEMPERATURE_CMD);
777 
778  // Get Humidity
779  memset(resp, '\0', MDCP_RESPONSE_LENGTH);
780 
781  if (!sendCommand(MDCP_GET_HUMIDITY_CMD, resp))
782  return false;
783 
784  ok = sscanf(resp, MDCP_GET_HUMIDITY_RES, &humidity );
785 
786  if (ok == 1)
787  {
788  HumidityNP[0].setValue(humidity);
789  HumidityNP.setState(IPS_OK);
790  HumidityNP.apply();
791  }
792  else
793  LOGF_ERROR("Get Humidity: Response <%s> for Command <%s> not recognized.", resp, MDCP_GET_HUMIDITY_CMD);
794 
795  // Get Dew Point
796  memset(resp, '\0', MDCP_RESPONSE_LENGTH);
797 
798  if (!sendCommand(MDCP_GET_DEWPOINT_CMD, resp))
799  return false;
800 
801  ok = sscanf(resp, MDCP_GET_DEWPOINT_RES, &dewpoint );
802 
803  if (ok == 1)
804  {
805  DewpointNP[0].setValue(dewpoint);
806  DewpointNP.setState(IPS_OK);
807  DewpointNP.apply();
808  }
809  else
810  LOGF_ERROR("Get Dew point: Response <%s> for Command <%s> not recognized.", resp, MDCP_GET_DEWPOINT_CMD);
811 
812  // Get Power output for all channels
813  memset(resp, '\0', MDCP_RESPONSE_LENGTH);
814 
815  if (!sendCommand(MDCP_GET_ALL_CH_POWER_CMD, resp))
816  return false;
817 
818  ok = sscanf(resp, MDCP_GET_ALL_CH_POWER_RES, &output1, &output2, &output3, &output4 );
819 
820  if (ok == 4)
821  {
822  ChannelPowerNP[0].setValue(output1);
823  ChannelPowerNP[1].setValue(output2);
824  ChannelPowerNP[2].setValue(output3);
825  ChannelPowerNP[3].setValue(output4);
826  ChannelPowerNP.setState(IPS_OK);
827  ChannelPowerNP.apply();
828  }
829  else
830  LOGF_ERROR("Get Power Outputs: Response <%s> for Command <%s> not recognized.", resp, MDCP_GET_ALL_CH_POWER_CMD);
831 
832  // Get Channel 3 Mode
833  memset(resp, '\0', MDCP_RESPONSE_LENGTH);
834 
835  if (!sendCommand(MDCP_GET_CH3_MODE_CMD, resp))
836  return false;
837 
838  ok = sscanf(resp, MDCP_GET_CH3_MODE_RES, &ch3_mode );
839 
840  if ((ok == 1) && (ch3_mode <= 4))
841  {
842  // Enabel/Disable Ch3 Manual Power setting if Ch3 Mode Manual enabled
843  if ((ch3_mode == CH3MODE_MANUAL) && (!ch3ManualPower))
844  {
845  defineProperty(Ch3ManualPowerNP);
846  ch3ManualPower = true;
847  }
848  else if ((ch3_mode != CH3MODE_MANUAL) && ch3ManualPower)
849  {
850  deleteProperty(Ch3ManualPowerNP);
851  ch3ManualPower = false;
852  }
853 
854  Ch3ModeSP.reset();
855  Ch3ModeSP[ch3_mode].setState(ISS_ON);
856  Ch3ModeSP.setState(IPS_OK);
857  Ch3ModeSP.apply();
858 
859  }
860  else
861  LOGF_ERROR("Get Channel 3 Mode: Response <%s> for Command <%s> not recognized.", resp, MDCP_GET_CH3_MODE_CMD);
862 
863  if (ch3ManualPower == true)
864  {
865  Ch3ManualPowerNP[0].setValue(ChannelPowerNP[2].getValue());
866  Ch3ManualPowerNP.setState(IPS_OK);
867  Ch3ManualPowerNP.apply();
868  }
869 
870  // Get Temperature offsets for all channels then use to set the channel temperatures
871  memset(resp, '\0', MDCP_RESPONSE_LENGTH);
872 
873  // Get offset values first
874  if (!sendCommand(MDCP_GET_ALL_CH_OFFSET_CMD, resp))
875  return false;
876 
877  ok = sscanf(resp, MDCP_GET_ALL_CH_OFFSET_RES, &offset1, &offset2, &offset3, &offset4 );
878 
879  if (ok == 4)
880  {
881  ChannelOffsetNP[0].setValue(offset1);
882  ChannelOffsetNP[1].setValue(offset2);
883  ChannelOffsetNP[2].setValue(offset3);
884  ChannelOffsetNP[3].setValue(offset4);
885  ChannelOffsetNP.setState(IPS_OK);
886  ChannelOffsetNP.apply();
887  }
888  else
889  LOGF_ERROR("Get Channel Offset: Response <%s> for Command <%s> not recognized.", resp, MDCP_GET_ALL_CH_OFFSET_CMD);
890 
891  memset(resp, '\0', MDCP_RESPONSE_LENGTH);
892 
893  if (!sendCommand(MDCP_GET_CHANNEL_TEMPS_CMD, resp))
894  return false;
895 
896  ok = sscanf(resp, MDCP_GET_CHANNEL_TEMPS_RES, &temp1, &temp2, &temp3, &temp4 );
897 
898  // Display adjusted temperature with offset but only for Channels with Probes
899  // the controller returns zero for temp if no probe but will continue to return offset
900  // channelActive will be zero for channels with no probe
901  if (ok == 4)
902  {
903  TemperatureNP[0].setValue(temp1 + (offset1 * channelActive[0]));
904  TemperatureNP[1].setValue(temp2 + (offset2 * channelActive[1]));
905  // Channel 3 is special and setting depends on CH3Mode only show in CH3TEMP mode
906  if (Ch3ModeSP.findOnSwitchIndex() == CH3MODE_CH3TEMP)
907  TemperatureNP[2].setValue(temp3 + (offset3 * channelActive[2]));
908  else
909  TemperatureNP[2].setValue( 0 );
910 
911  TemperatureNP[3].setValue(temp4 + (offset4 * channelActive[3]));
912  TemperatureNP.setState(IPS_OK);
913  TemperatureNP.apply();
914  }
915  else
916  LOGF_ERROR("Get Channel Temperatures: Response <%s> for Command <%s> not recognized.", resp, MDCP_GET_CHANNEL_TEMPS_CMD);
917 
918  // Get Tracking Mode
919  memset(resp, '\0', MDCP_RESPONSE_LENGTH);
920 
921  if (!sendCommand(MDCP_GET_TRACKING_MODE_CMD, resp))
922  return false;
923 
924  ok = sscanf(resp, MDCP_GET_TRACKING_MODE_RES, &tracking_mode );
925 
926  if ((ok == 1) && (tracking_mode > 0) && (tracking_mode <= 3))
927  {
928  TrackingModeSP.reset();
929  TrackingModeSP[tracking_mode-1].setState(ISS_ON);
930  TrackingModeSP.setState(IPS_OK);
931  TrackingModeSP.apply();
932  }
933  else
934  LOGF_ERROR("Get Tracking Mode: Response <%s> for Command <%s> not recognized.", resp, MDCP_GET_TRACKING_MODE_CMD);
935 
936  //Get Tracking Offset but Firmware 109 has a bug with setting offsets so only for >109
937  if (myDCP4Firmware > 109)
938  {
939  memset(resp, '\0', MDCP_RESPONSE_LENGTH);
940 
941  if (!sendCommand(MDCP_GET_TRACKING_OFFSET_CMD, resp))
942  return false;
943 
944  ok = sscanf(resp, MDCP_GET_TRACKING_OFFSET_RES, &tracking_offset );
945 
946  if (ok == 1)
947  {
948  TrackingOffsetNP[0].setValue(tracking_offset);
949  TrackingOffsetNP.setState(IPS_OK);
950  TrackingOffsetNP.apply();
951  }
952  else
953  LOGF_ERROR("Get Tracking Offset: Response <%s> for Command <%s> not recognized.", resp, MDCP_GET_TRACKING_OFFSET_CMD);
954 
955  }
956 
957  // Current Channel 100% boost state but only for firmware >109 due to firmware bug
958  if (myDCP4Firmware > 109)
959  {
960  // Always clear the Channel boost reset checkbox
961  ChannelBoostSP[4].setState(ISS_OFF);
962 
963  for (int i = 1; i <= 4; i++)
964  {
965  char cmd[MDCP_CMD_LENGTH] = {};
966 
968 
969  memset(resp, '\0', MDCP_RESPONSE_LENGTH);
970 
971  if (!sendCommand(cmd, resp))
972  return false;
973 
974  ok = sscanf(resp, MDCP_GET_CH_OVERIDE_RES, &channel_boost);
975 
976  if ((ok == 1) && (channel_boost <= 1))
977  {
978  ChannelBoostSP[i-1].setState( (ISState) channel_boost);
979  ChannelBoostSP.setState(IPS_OK);
980  ChannelBoostSP.apply();
981  }
982  else
983  LOGF_ERROR("Get Channel Overrides: Response <%s> for Command <%s> not recognized.", resp, cmd);
984  }
985  }
986 
987  return true;
988 }
989 
991 {
992  if (!isConnected())
993  {
994  return;
995  }
996 
997  // Get temperatures etc.
998  readSettings();
999  timerIndex = SetTimer(getCurrentPollingPeriod());
1000 }
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.
void addConfigurationControl()
Add Configuration control to the driver.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
virtual bool Disconnect()
Disconnect from device.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
virtual void setConnected(bool status, IPState state=IPS_OK, const char *msg=nullptr)
Set connection switch status in the client.
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool loadConfig(bool silent=false, const char *property=nullptr)
Load the last saved configuration file.
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 ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
void setDriverInterface(uint16_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
void RemoveTimer(int id)
Remove timer added with SetTimer.
Connection::Interface * getActiveConnection()
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
virtual bool Connect()
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
void addDebugControl()
Add Debug control 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)
bool update(const ISState states[], const char *const names[], int n)
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)
virtual const char * getDefaultName() override
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: mydcp4esp32.cpp:53
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
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.
@ CONNECTION_TCP
Definition: mydcp4esp32.h:156
@ CONNECTION_SERIAL
Definition: mydcp4esp32.h:155
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
int errno
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
@ 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
@ ISR_NOFMANY
Definition: indiapi.h:175
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
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
#define LOGF_ERROR(fmt,...)
Definition: indilogger.h:80
#define LOG_INFO(txt)
Definition: indilogger.h:74
#define MAXRBUF
Definition: indiserver.cpp:102
std::unique_ptr< MyDCP4ESP > mydcp4esp(new MyDCP4ESP())
#define MDCP_REBOOT_CMD
Definition: mydcp4esp32.h:52
#define MDCP_SET_TRACKING_OFFSET_CMD
Definition: mydcp4esp32.h:70
#define MDCP_SET_CH3_MANUAL_POWER_CMD
Definition: mydcp4esp32.h:88
#define MDCP_GET_AMBIENT_OFFSET_RES
Definition: mydcp4esp32.h:106
#define CDRIVER_VERSION_MAJOR
Definition: mydcp4esp32.h:37
#define MDCP_GET_HUMIDITY_RES
Definition: mydcp4esp32.h:108
#define MDCP_SET_TRACKING_MODE_CMD
Definition: mydcp4esp32.h:54
#define MDCP_GET_VERSION_RES
Definition: mydcp4esp32.h:94
#define MDCP_GET_TRACKING_OFFSET_CMD
Definition: mydcp4esp32.h:55
#define CDRIVER_VERSION_MINOR
Definition: mydcp4esp32.h:38
#define MDCP_GET_CHANNEL_TEMPS_RES
Definition: mydcp4esp32.h:116
#define MDCP_GET_ALL_CH_OFFSET_CMD
Definition: mydcp4esp32.h:79
#define MDCP_SET_CH2_OFFSET_CMD
Definition: mydcp4esp32.h:75
#define MDCP_GET_ALL_CH_POWER_RES
Definition: mydcp4esp32.h:128
#define MDCP_GET_AMBIENT_OFFSET_CMD
Definition: mydcp4esp32.h:63
#define MDCP_GET_CH_OVERIDE_CMD
Definition: mydcp4esp32.h:81
#define MDCP_SET_AMBIENT_OFFSET_CMD
Definition: mydcp4esp32.h:64
#define MDCP_SET_CH3_OFFSET_CMD
Definition: mydcp4esp32.h:76
#define MDCP_SET_CH_100_CMD
Definition: mydcp4esp32.h:80
#define MDCP_RESET_CH_100_CMD
Definition: mydcp4esp32.h:84
#define MDCP_GET_CH3_MODE_CMD
Definition: mydcp4esp32.h:87
#define MDCP_GET_CH_OVERIDE_RES
Definition: mydcp4esp32.h:124
#define MDCP_GET_TRACKING_OFFSET_RES
Definition: mydcp4esp32.h:98
#define MDCP_GET_VERSION_CMD
Definition: mydcp4esp32.h:51
#define MDCP_GET_ALL_CH_POWER_CMD
Definition: mydcp4esp32.h:85
#define MDCP_GET_CH3_MODE_RES
Definition: mydcp4esp32.h:130
#define MDCP_GET_CONTROLLER_CODE_RES
Definition: mydcp4esp32.h:93
#define MDCP_GET_HUMIDITY_CMD
Definition: mydcp4esp32.h:65
#define MDCP_GET_TRACKING_MODE_RES
Definition: mydcp4esp32.h:96
#define MDCP_GET_TRACKING_MODE_CMD
Definition: mydcp4esp32.h:53
#define MDCP_RESPONSE_LENGTH
Definition: mydcp4esp32.h:92
#define MDCP_GET_AMBIENT_TEMPERATURE_RES
Definition: mydcp4esp32.h:105
#define MDCP_GET_AMBIENT_TEMPERATURE_CMD
Definition: mydcp4esp32.h:62
#define MDCP_GET_CHANNEL_TEMPS_CMD
Definition: mydcp4esp32.h:73
#define MDCP_GET_DEWPOINT_RES
Definition: mydcp4esp32.h:109
#define MDCP_SET_CH1_OFFSET_CMD
Definition: mydcp4esp32.h:74
#define MDCP_GET_ALL_CH_OFFSET_RES
Definition: mydcp4esp32.h:122
#define MDCP_GET_DEWPOINT_CMD
Definition: mydcp4esp32.h:66
#define MDCP_CMD_LENGTH
Definition: mydcp4esp32.h:48
#define MDCP_GET_CONTROLLER_CODE_CMD
Definition: mydcp4esp32.h:50
#define MDCP_SET_CH4_OFFSET_CMD
Definition: mydcp4esp32.h:77
#define MDCP_SET_CH3_MODE_CMD
Definition: mydcp4esp32.h:86
const char * CONNECTION_TAB
__u8 cmd[4]
Definition: pwc-ioctl.h:2