Instrument Neutral Distributed Interface INDI 2.2.5
connectionserial.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2017 Jasem Mutlaq. All rights reserved.
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License version 2 as published by the Free Software Foundation.
7
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Library General Public License for more details.
12
13 You should have received a copy of the GNU Library General Public License
14 along with this library; see the file COPYING.LIB. If not, write to
15 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 Boston, MA 02110-1301, USA.
17*******************************************************************************/
18
19#include "connectionserial.h"
21#include "indicom.h"
22#include "indilogger.h"
23
24#include <dirent.h>
25#include <unistd.h>
26#include <cerrno>
27#include <cstring>
28#include <algorithm>
29#include <thread>
30#include <chrono>
31#include <regex>
32#include <random>
33
34namespace Connection
35{
36extern const char *CONNECTION_TAB;
37
38Serial::Serial(INDI::DefaultDevice *dev, IPerm permission) : Interface(dev, CONNECTION_SERIAL), m_Permission(permission)
39{
40 char configPort[256] = {0};
41 // Try to load the port from the config file. If that fails, use default port.
42 if (IUGetConfigText(dev->getDeviceName(), INDI::SP::DEVICE_PORT, "PORT", configPort, 256) == 0)
43 {
44 m_ConfigPort = configPort;
45 PortTP[0].fill("PORT", "Port", configPort);
46 }
47 else
48 {
49#ifdef __APPLE__
50 PortTP[0].fill("PORT", "Port", "/dev/cu.usbserial");
51#else
52 PortTP[0].fill("PORT", "Port", "/dev/ttyUSB0");
53#endif
54 }
56
57 int autoSearchIndex = 0;
58 // Try to load the port from the config file. If that fails, use default port.
60 AutoSearchSP[INDI::DefaultDevice::INDI_ENABLED].fill("INDI_ENABLED", "Enabled", autoSearchIndex == 0 ? ISS_ON : ISS_OFF);
61 AutoSearchSP[INDI::DefaultDevice::INDI_DISABLED].fill("INDI_DISABLED", "Disabled", autoSearchIndex == 0 ? ISS_OFF : ISS_ON);
63 60, IPS_IDLE);
64
65 RefreshSP[0].fill("Scan Ports", "Scan Ports", ISS_OFF);
66 RefreshSP.fill(dev->getDeviceName(), "DEVICE_PORT_SCAN", "Refresh", CONNECTION_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
67
68 BaudRateSP[B_1200].fill("1200", "", ISS_OFF);
69 BaudRateSP[B_2400].fill("2400", "", ISS_OFF);
70 BaudRateSP[B_4800].fill("4800", "", ISS_OFF);
71 BaudRateSP[B_9600].fill("9600", "", ISS_ON);
72 BaudRateSP[B_19200].fill("19200", "", ISS_OFF);
73 BaudRateSP[B_38400].fill("38400", "", ISS_OFF);
74 BaudRateSP[B_57600].fill("57600", "", ISS_OFF);
75 BaudRateSP[B_115200].fill("115200", "", ISS_OFF);
76 BaudRateSP[B_230400].fill("230400", "", ISS_OFF);
78 60, IPS_IDLE);
79
80 // Try to load the baud rate from the config file. Look it up by name and not by index since
81 // configuration files written before slower baud rates were added have a different switch order.
82 char configBaudRate[MAXINDINAME] = {0};
85 // If we have a valid config entry, set it.
86 if (m_ConfigBaudRate >= 0)
87 {
90 }
91}
92
93Serial::~Serial() = default;
94
95bool Serial::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
96{
97 if (!strcmp(dev, m_Device->getDeviceName()))
98 {
99 // Serial Port
101 {
102 PortTP.update(texts, names, n);
104 PortTP.apply();
105
106 auto pos = std::find(m_SystemPorts.begin(), m_SystemPorts.end(), PortTP[0].getText());
107 // Auto search only makes sense when the user is not using a custom (non-system) port.
108 // Only act (and report it) when auto search is currently enabled, so that no redundant
109 // message is sent and no property update is pushed when it is already disabled.
111 {
112 LOGF_DEBUG("Auto search is disabled because %s is not a system port.", PortTP[0].getText());
116 }
117 }
118 return true;
119 }
120
121 return false;
122}
123
124bool Serial::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
125{
126 if (!strcmp(dev, m_Device->getDeviceName()))
127 {
128 // Change Baud Rate
130 {
131 BaudRateSP.update(states, names, n);
134 return true;
135 }
136
137 // Auto Search Devices on connection failure
139 {
141
142 AutoSearchSP.update(states, names, n);
144
145 // Only display message if there is an actual change
146 if (wasEnabled == false && AutoSearchSP[INDI::DefaultDevice::INDI_ENABLED].getState() == ISS_ON)
147 LOG_INFO("Auto search is enabled. When connecting, the driver shall attempt to "
148 "communicate with all available system ports until a connection is "
149 "established.");
150 else if (wasEnabled && AutoSearchSP[INDI::DefaultDevice::INDI_DISABLED].getState() == ISS_ON)
151 LOG_INFO("Auto search is disabled.");
153
154 return true;
155 }
156
157 // Refresh Serial Devices
159 {
162 return true;
163 }
164
165 // Check if a system port is selected.
167 {
168 SystemPortSP.update(states, names, n);
169
170 int index = SystemPortSP.findOnSwitchIndex();
171 if (index >= 0)
172 {
173 PortTP[0].setText(m_SystemPorts[index]);
174 PortTP.apply();
175 }
176
179 return true;
180 }
181 }
182
183 return false;
184}
185
187{
188 uint32_t baud = this->baud();
189 if (Connect(PortTP[0].getText(), baud) && processHandshake())
190 return true;
191
192 // Important, disconnect from port immediately
193 // to release the lock, otherwise another driver will find it busy.
195
196 // Collect the alternative system ports to try, excluding the port we just attempted.
197 std::vector<std::string> systemPorts;
198 for (const auto &onePort : m_SystemPorts)
199 {
200 // Only try the same port last again.
201 if (onePort == PortTP[0].getText())
202 continue;
203
204 systemPorts.push_back(onePort);
205 }
206
207 // Start auto-search if option was selected and IF we have system ports to try connecting to
208 if (AutoSearchSP[INDI::DefaultDevice::INDI_ENABLED].getState() == ISS_ON && !SystemPortSP.isEmpty() && !systemPorts.empty())
209 {
210 LOGF_WARN("Communication with %s @ %d failed. Starting Auto Search...", PortTP[0].getText(),
211 baud);
212
213 std::this_thread::sleep_for(std::chrono::milliseconds(500 + (rand() % 1000)));
214
215 // Try to connect "randomly" so that competing devices don't all try to connect to the same
216 // ports at the same time.
217 std::random_device rd;
218 std::minstd_rand g(rd());
219 std::shuffle(systemPorts.begin(), systemPorts.end(), g);
220
221 std::vector<std::string> doubleSearch = systemPorts;
222
223 // Try the current port as LAST port again
224 systemPorts.push_back(PortTP[0].getText());
225
226 // Double search just in case some items were BUSY in the first pass
227 systemPorts.insert(systemPorts.end(), doubleSearch.begin(), doubleSearch.end());
228
229 for (const auto &port : systemPorts)
230 {
231 LOGF_INFO("Trying connecting to %s @ %d ...", port.c_str(), baud);
232 if (Connect(port.c_str(), baud) && processHandshake())
233 {
234 PortTP[0].setText(port);
235 PortTP.apply();
236
237#ifdef __linux__
238 // Disable auto-search on Linux if not disabled already
240 {
245 }
246
247 // Save device port if different from configuration value and read-write.
248 if (m_Permission != IP_RO && m_ConfigPort != PortTP[0].getText())
250#else
251 // Do not overwrite custom ports because it can be actually cause
252 // temporary failure. For users who use mapped named ports (e.g. /dev/mount), it's not good to override their choice.
253 // So only write to config if the port was a system port.
254 if (m_Permission != IP_RO && std::find(m_SystemPorts.begin(), m_SystemPorts.end(), PortTP[0].getText()) != m_SystemPorts.end())
256#endif
257
258
259 // If baud rate is different from config file, save it.
262
263 return true;
264 }
265
267 // sleep randomly anytime between 0.5s and ~1.5s
268 // This enables different competing devices to connect
269 std::this_thread::sleep_for(std::chrono::milliseconds(500 + (rand() % 1000)));
270 }
271 }
272
273 return false;
274}
275
277{
278 LOG_DEBUG("Connection successful, attempting handshake...");
279 bool rc = Handshake();
280 if (rc)
281 {
282 LOGF_INFO("%s is online.", getDeviceName());
283 // If permission is read-write and either the config port or baud rate are different from
284 // configuration values, then save them.
285 if (m_Permission != IP_RO && PortTP[0].getText() != m_ConfigPort)
287
290 }
291 else
292 LOG_DEBUG("Handshake failed.");
293
294 return rc;
295}
296
297bool Serial::Connect(const char *port, uint32_t baud)
298{
299 if (m_Device->isSimulation())
300 return true;
301
302 int connectrc = 0;
303 char errorMsg[MAXRBUF];
304
305 LOGF_DEBUG("Connecting to %s @ %d", port, baud);
306
307 if ((connectrc = tty_connect(port, baud, wordSize, parity, stopBits, &PortFD)) != TTY_OK)
308 {
309 if (connectrc == TTY_PORT_BUSY)
310 {
311 LOGF_WARN("Port %s is already used by another driver or process.", port);
312 return false;
313 }
314
315 tty_error_msg(connectrc, errorMsg, MAXRBUF);
316 LOGF_ERROR("Failed to connect to port (%s). Error: %s", port, errorMsg);
317 return false;
318 }
319
320 LOGF_DEBUG("Port FD %d", PortFD);
321
322 return true;
323}
324
326{
327 if (PortFD > 0)
328 {
330 PortFD = -1;
331 }
332 return true;
333}
334
347
363
365{
366 if (m_Permission != IP_RO)
367 {
368 PortTP.save(fp);
369 BaudRateSP.save(fp);
370 AutoSearchSP.save(fp);
371 }
372
373 return true;
374}
375
376void Serial::setDefaultPort(const char *port)
377{
378 // JM 2021.09.19: Only set default port if configuration port was not loaded already.
379 if (m_ConfigPort.empty())
380 PortTP[0].setText(port);
382 PortTP.apply();
383}
384
386{
387 // JM 2021.09.19: Only set default baud rate if configuration baud rate was not loaded already.
388 if (m_ConfigBaudRate == -1)
389 {
391 BaudRateSP[newRate].setState(ISS_ON);
392 }
393
396}
397
398void Serial::setPortMatchPattern(const std::string &pattern)
399{
400 m_PortMatchPattern = pattern;
401}
402
403uint32_t Serial::baud()
404{
405 auto onSwitch = BaudRateSP.findOnSwitch();
406 return onSwitch ? atoi(onSwitch->getName()) : 9600;
407}
408
409int serial_dev_file_select(const dirent *entry)
410{
411#if defined(__APPLE__)
412 static const char *filter_names[] = { "cu.", nullptr };
413#else
414 static const char *filter_names[] = { "ttyUSB", "ttyACM", nullptr };
415#endif
416 const char **filter;
417
418 for (filter = filter_names; *filter; ++filter)
419 {
420 if (strstr(entry->d_name, *filter) != nullptr)
421 {
422 return (true);
423 }
424 }
425 return (false);
426}
427
428int usb_dev_file_select(const dirent *entry)
429{
430 static const char *filter_names[] = { "usb-", nullptr };
431 const char **filter;
432
433 for (filter = filter_names; *filter; ++filter)
434 {
435 if (strstr(entry->d_name, *filter) != nullptr)
436 {
437 return (true);
438 }
439 }
440 return (false);
441}
442
443int bluetooth_dev_file_select(const dirent *entry)
444{
445 static const char *filter_names[] = {"rfcomm", nullptr };
446 const char **filter;
447
448 for (filter = filter_names; *filter; ++filter)
449 {
450 if (strstr(entry->d_name, *filter) != nullptr)
451 {
452 return (true);
453 }
454 }
455 return (false);
456}
457
458bool Serial::Refresh(bool silent)
459{
460 std::vector<std::string> m_Ports;
461
462 // 0 Serial Only, 1 By USB-ID, 2 Bluetooth
463 auto searchPath = [&](std::string prefix, uint8_t searchType)
464 {
465 struct dirent **namelist;
466 std::vector<std::string> detectedDevices;
467 int devCount = 0;
468 if (searchType == SERIAL_DEV)
469 devCount = scandir(prefix.c_str(), &namelist, serial_dev_file_select, alphasort);
470 else if (searchType == USB_ID_DEV)
471 devCount = scandir(prefix.c_str(), &namelist, usb_dev_file_select, alphasort);
472 else
473 devCount = scandir(prefix.c_str(), &namelist, bluetooth_dev_file_select, alphasort);
474 if (devCount > 0)
475 {
476 while (devCount--)
477 {
478 if (detectedDevices.size() < 10)
479 {
480 std::string s(namelist[devCount]->d_name);
481 s.erase(s.find_last_not_of(" \n\r\t") + 1);
482 detectedDevices.push_back(prefix + s);
483 }
484 else
485 {
486 LOGF_DEBUG("Ignoring devices over %d : %s", detectedDevices.size(),
487 namelist[devCount]->d_name);
488 }
489 free(namelist[devCount]);
490 }
491 free(namelist);
492 }
493
494 return detectedDevices;
495 };
496
497#ifdef __linux__
498 // Search for serial, usb, and bluetooth devices.
499 const std::vector<std::string> serialDevices = searchPath("/dev/", SERIAL_DEV);
500 const std::vector<std::string> usbIDDevices = searchPath("/dev/serial/by-id/", USB_ID_DEV);
501 const std::vector<std::string> btDevices = searchPath("/dev/", BLUETOOTH_DEV);
502 m_Ports.insert(m_Ports.end(), btDevices.begin(), btDevices.end());
503 // Linux Kernel does not add identical VID:PID adapter to serial/by-id
504 // Therefore, we check if there is a 1:1 correspondence between serial/by-id and /dev/ttyUSBX nodes
505 // In case we have more by-id devices then /dev/ttyUSBX, we use them since these symlinks are more reusable in subsequence
506 // sessions
507 if (usbIDDevices.size() >= serialDevices.size())
508 m_Ports.insert(m_Ports.end(), usbIDDevices.begin(), usbIDDevices.end());
509 else
510 m_Ports.insert(m_Ports.end(), serialDevices.begin(), serialDevices.end());
511#else
512 const std::vector<std::string> serialDevices = searchPath("/dev/", SERIAL_DEV);
513 m_Ports.insert(m_Ports.end(), serialDevices.begin(), serialDevices.end());
514#endif
515
516 const int pCount = m_Ports.size();
517
518 if (pCount == 0)
519 {
520 if (!silent)
521 LOG_WARN("No candidate ports found on the system.");
522 return false;
523 }
524 else
525 {
526 if (!silent)
527 LOGF_INFO("Scan complete. Found %d port(s).", pCount);
528 }
529
530 // Check if anything changed and the property is already defined then we return.
531 if (m_Ports == m_SystemPorts && !SystemPortSP.isEmpty())
532 {
534 return true;
535 }
536
537 m_SystemPorts = m_Ports;
538
539 if (!SystemPortSP.isEmpty())
541
543
544 for (const auto &onePort : m_Ports)
545 {
546 // Simplify label by removing directory prefix
547 std::string name = onePort.substr(onePort.find_last_of("/\\") + 1);
548 std::string label = name;
549
550 // Remove Linux extra stuff to simplify string
551#ifdef __linux__
552 std::regex re("usb-(.[^-]+)");
553 std::smatch match;
554 if (std::regex_search(onePort, match, re))
555 {
556 name = label = match.str(1);
557 // Simplify further by removing non-unique strings
558 std::regex target("FTDI_|UART_|USB_|Bridge_Controller_|to_");
559 label = std::regex_replace(label, target, "");
560 // Protect against too-short of a label
561 if (label.length() <= 2)
562 label = match.str(1);
563 }
564#endif
566 node.fill(name, label, ISS_OFF);
567 SystemPortSP.push(std::move(node));
568 }
569
570 SystemPortSP.fill(m_Device->getDeviceName(), "SYSTEM_PORTS", "System Ports", CONNECTION_TAB, IP_RW, ISR_ATMOST1, 60,
571 IPS_IDLE);
572
574
575 // Check whether the saved config port is still usable: it is either among the detected system
576 // ports, or it still exists on disk (e.g. a custom or mapped port like /dev/mount).
577 const bool configPortAvailable =
578 !m_ConfigPort.empty() &&
579 (std::find(m_SystemPorts.begin(), m_SystemPorts.end(), m_ConfigPort) != m_SystemPorts.end() ||
580 access(m_ConfigPort.c_str(), F_OK) == 0);
581
582 // If we have one physical port, set the current device port to this physical port
583 // in case the config port is either not defined or no longer available.
584 if (pCount == 1 && !configPortAvailable)
585 {
586 if (!m_ConfigPort.empty())
587 LOGF_INFO("Saved port %s is no longer available. Using the only detected port %s instead.",
588 m_ConfigPort.c_str(), m_Ports[0].c_str());
589
590 PortTP[0].setText(m_Ports[0]);
592 PortTP.apply();
593 }
594
595 // If a port match pattern is registered, try to auto-select the best matching port.
596 // This is only done when no previously-saved config port is present among the discovered
597 // ports (e.g. first run, or device plugged into a different USB slot).
598 // Devices like Pegasus Astro embed their product name in the /dev/serial/by-id/ symlink,
599 // which makes it possible to pick the right port without blind handshake attempts.
600 if (!m_PortMatchPattern.empty())
601 {
602 if (!configPortAvailable)
603 {
604 try
605 {
606 const std::regex portRe(m_PortMatchPattern, std::regex::icase);
607 for (const auto &onePort : m_SystemPorts)
608 {
609 if (std::regex_search(onePort, portRe))
610 {
611 LOGF_INFO("Auto-selected port %s based on device name pattern \"%s\".",
612 onePort.c_str(), m_PortMatchPattern.c_str());
613 PortTP[0].setText(onePort);
614 PortTP.apply();
615 break;
616 }
617 }
618 }
619 catch (const std::regex_error &e)
620 {
621 LOGF_WARN("Port match pattern \"%s\" is not a valid regex: %s",
622 m_PortMatchPattern.c_str(), e.what());
623 }
624 }
625 }
626
627 return true;
628}
629}
The Interface class is the base class for all INDI connection plugins.
std::function< bool()> Handshake
INDI::DefaultDevice * m_Device
INDI::PropertySwitch RefreshSP
std::string m_PortMatchPattern
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
virtual const char * port()
INDI::PropertySwitch AutoSearchSP
virtual std::string name() override
BaudRate
Supported baud rates.
INDI::PropertySwitch SystemPortSP
std::vector< std::string > m_SystemPorts
INDI::PropertySwitch BaudRateSP
virtual void Activated() override
Activated Function called by the framework when the plugin is activated (i.e. selected by the user)....
void setPortMatchPattern(const std::string &pattern)
setPortMatchPattern Set a regex pattern used to automatically select a serial port from the discovere...
bool Refresh(bool silent=false)
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
virtual void Deactivated() override
Deactivated Function called by the framework when the plugin is deactivated. It is usually used to de...
virtual bool Disconnect() override
Disconnect Disconnect from device.
virtual bool saveConfigItems(FILE *fp) override
void setDefaultPort(const char *port)
setDefaultPort Set default port. Call this function in initProperties() of your driver if you want to...
virtual bool Connect() override
Connect Connect to device via the implemented communication medium. Do not perform any handshakes.
virtual uint32_t baud()
Serial(INDI::DefaultDevice *dev, IPerm permission=IP_RW)
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
virtual bool processHandshake()
virtual std::string label() override
INDI::PropertyText PortTP
const char * getDeviceName() const
Class to provide extended functionality for devices in addition to the functionality provided by INDI...
bool isInitializationComplete() const
isInitializationComplete Check if driver initialization is complete.
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a 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)
bool isSimulation() const
void push(WidgetView< T > &&item)
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
save Save property to configuration file.
void resize(size_t size)
const char * getName() const
bool isNameMatch(const char *otherName) const
int findWidgetIndexByName(const char *name) const
bool update(const ISState states[], const char *const names[], int n)
INDI::WidgetViewSwitch * findOnSwitch() const
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
bool update(const char *const texts[], 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)
INDI::PropertyViewText * getText() const
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
IPerm
Permission hint, with respect to client.
Definition indiapi.h:181
@ 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
@ ISR_1OFMANY
Definition indiapi.h:171
@ ISR_ATMOST1
Definition indiapi.h:172
#define MAXINDINAME
Definition indiapi.h:189
int tty_connect(const char *device, int bit_rate, int word_size, int parity, int stop_bits, int *fd)
Establishes a tty connection to a terminal device.
Definition indicom.c:1022
int tty_disconnect(int fd)
Closes a tty connection and flushes the bus.
Definition indicom.c:1224
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
@ TTY_PORT_BUSY
Definition indicom.h:63
#define MAXRBUF
Definition indidriver.c:52
int IUGetConfigOnSwitchName(const char *dev, const char *property, char *name, size_t size)
IUGetConfigOnSwitchLabel Opens configuration file and returns the name of the ON switch property,...
Definition indidriver.c:783
int IUGetConfigText(const char *dev, const char *property, const char *member, char *value, int len)
IUGetConfigText Opens configuration file and reads single text property.
Definition indidriver.c:908
int IUGetConfigOnSwitchIndex(const char *dev, const char *property, int *index)
IUGetConfigOnSwitchIndex Opens configuration file and reads single switch property to find ON switch ...
Definition indidriver.c:719
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#define LOGF_WARN(fmt,...)
Definition indilogger.h:81
#define LOG_WARN(txt)
Definition indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
Combines all INDI Connection Plugins. Each INDI connection plugin is responsible of managing communic...
Definition arduino_st4.h:34
const char * CONNECTION_TAB
int usb_dev_file_select(const dirent *entry)
int bluetooth_dev_file_select(const dirent *entry)
int serial_dev_file_select(const dirent *entry)
const char * DEVICE_AUTO_SEARCH
Toggle device auto search. If enabled and on connection failure with the default port,...
const char * DEVICE_PORT
Device serial (or bluetooth) connection port. The default value on Linux is /dev/ttyUSB0 while on Mac...
const char * DEVICE_BAUD_RATE
Set device baud rate.
void fill(const char *name, const char *label, ISState state=ISS_OFF)