diff --git a/MyConfig.h b/MyConfig.h index 785beda..dff0d6e 100644 --- a/MyConfig.h +++ b/MyConfig.h @@ -719,21 +719,43 @@ /** * @def MY_LINUX_SERIAL_PORT - * @brief Set the name of predictable tty + * @brief Serial device port */ #ifndef MY_LINUX_SERIAL_PORT -#define MY_LINUX_SERIAL_PORT "/dev/ttyUSB0" +#define MY_LINUX_SERIAL_PORT "/dev/ttyACM0" #endif +/** + * @def MY_IS_SERIAL_PTY + * @brief Set serial as a pseudo terminal. + * + * Enable this if you need to connect to a controller running on the same device. + */ +//#define MY_IS_SERIAL_PTY + +/** + * @def MY_LINUX_SERIAL_PTY + * @brief Symlink name for the PTY device. + */ +#ifndef MY_LINUX_SERIAL_PTY +#define MY_LINUX_SERIAL_PTY "/dev/ttyMySensorsGateway" +#endif + +/** + * @def MY_LINUX_SERIAL_GROUPNAME + * @brief Grant access to the specified system group for the serial device. + */ +//#define MY_LINUX_SERIAL_GROUPNAME "tty" + /** * @def MY_LINUX_CONFIG_FILE * @brief Set the filepath for the gateway config file * * For now the configuration file is only used to store the emulated eeprom state */ - #ifndef MY_LINUX_CONFIG_FILE - #define MY_LINUX_CONFIG_FILE "/etc/mysensors.dat" - #endif +#ifndef MY_LINUX_CONFIG_FILE +#define MY_LINUX_CONFIG_FILE "/etc/mysensors.dat" +#endif // Doxygen specific constructs, not included when built normally // This is used to enable disabled macros/definitions to be included in the documentation as well. @@ -751,4 +773,5 @@ #define MY_RX_MESSAGE_BUFFER_FEATURE #define MY_RX_MESSAGE_BUFFER_SIZE #define MY_NODE_LOCK_FEATURE +#define MY_LINUX_SERIAL_GROUPNAME #endif diff --git a/MySensors.h b/MySensors.h index 77e36ac..2b7e820 100644 --- a/MySensors.h +++ b/MySensors.h @@ -245,7 +245,7 @@ // GATEWAY - ESP8266 #include "core/MyGatewayTransportEthernet.cpp" #elif defined(MY_GATEWAY_LINUX) - // GATEWAY - Generic Linux (RaspberryPi, BBB, ...) + // GATEWAY - Generic Linux #include "drivers/Linux/EthernetClient.h" #include "drivers/Linux/EthernetServer.h" #include "drivers/Linux/IPAddress.h" diff --git a/configure b/configure index 242b236..86b73c3 100755 --- a/configure +++ b/configure @@ -11,12 +11,7 @@ Options: Help: -h, --help print this message -Driver options: - --driver=[SPIDEV|MRAA|RPi|LittleWire] - Driver for RF24 library. [configure autodetected] - Building options: - --os=[LINUX|DARWIN] Operating system. [configure autodetected] --soc=[BCM2835|BCM2836|AM33XX|A10|A13|A20|H3] SoC type to be used. [configure autodetected] --cpu-flags= CPU defining/optimizing flags to be used. [configure autodetected] @@ -33,12 +28,10 @@ Installation options: MySensors options: --my-debug=[enable|disable] - Enables or disables debugging. - Default to enable. + Enables or disables debugging. [enable] --my-config-file= Config file path. [/etc/mysensors.dat] --my-gateway=[none|ethernet|serial|mqtt] - Gateway type, set to none to disable gateway feature. - Default to ethernet. + Gateway type, set to none to disable gateway feature. [ethernet] --my-controller-url-address= Controller or MQTT broker url. --my-controller-ip-address= @@ -48,21 +41,25 @@ MySensors options: If gateway is set to mqtt, it sets the broker port. --my-serial-port= Serial port. [/dev/ttyUSB0] --my-serial-baudrate= Serial baud rate. [115200] + --my-serial-is-pty Set the serial port to be a pseudo terminal. Use this if you want + to connect to a controller running on the same device. + --my-serial-pty= Symlink name for the PTY device. [/dev/ttyMySensorsGateway] + --my-serial-groupname= + Grant access to the specified system group for the serial device. --my-mqtt-client-id= MQTT client id. --my-mqtt-publish-topic-prefix= MQTT publish topic prefix. --my-mqtt-subscribe-topic-prefix= MQTT subscribe topic prefix. - --my-radio=[none|nrf24] Radio type, set to none to disable radio feature. - Default to rf24. + --my-radio=[none|nrf24] Radio type, set to none to disable radio feature. [rf24] --my-rf24-channel=<0-125> RF channel for the sensor net, 0-125. --my-rf24-pa-level=[RF24_PA_MAX|RF24_PA_LOW] RF24 PA level. --my-rf24-irq-pin= Pin number connected to nRF24L01 IRQ pin. For RaspberryPi use BCM numbering. --my-rx-message-buffer-size= - Buffer size for incoming messages when using rf24 interrupts. - Default is 20. + Buffer size for incoming messages when using rf24 interrupts. [20] + EOF } @@ -202,7 +199,7 @@ debug=enable gateway_type=ethernet radio_type=nrf24 -params="OS SOC CXXFLAGS LDFLAGS PREFIX CXX RF24H_LIB_DIR GATEWAY_DIR INIT_SYSTEM" +params="SOC CXXFLAGS LDFLAGS PREFIX CXX RF24H_LIB_DIR GATEWAY_DIR INIT_SYSTEM" for opt do if [ "$opt" = "-h" ] || [ "$opt" = "--help" ]; then @@ -211,9 +208,6 @@ for opt do fi optarg="${opt#*=}" case "$opt" in - --os=*) - OS="$optarg" - ;; --soc=*) SOC="$optarg" ;; @@ -259,6 +253,15 @@ for opt do --my-serial-baudrate=*) CXXFLAGS="-DMY_BAUD_RATE=${optarg} $CXXFLAGS" ;; + --my-serial-is-pty*) + CXXFLAGS="-DMY_IS_SERIAL_PTY $CXXFLAGS" + ;; + --my-serial-pty=*) + CXXFLAGS="-DMY_LINUX_SERIAL_PTY=\\\"${optarg}\\\" $CXXFLAGS" + ;; + --my-serial-groupname=*) + CXXFLAGS="-DMY_LINUX_SERIAL_GROUPNAME=\\\"${optarg}\\\" $CXXFLAGS" + ;; --my-rf24-channel=*) CXXFLAGS="-DMY_RF24_CHANNEL=${optarg} $CXXFLAGS" ;; diff --git a/core/MyHwLinuxGeneric.cpp b/core/MyHwLinuxGeneric.cpp index b116555..2db3102 100644 --- a/core/MyHwLinuxGeneric.cpp +++ b/core/MyHwLinuxGeneric.cpp @@ -71,6 +71,12 @@ void hwInit() #ifdef MY_GATEWAY_SERIAL MY_SERIALDEVICE.begin(MY_BAUD_RATE); + #ifdef MY_LINUX_SERIAL_GROUPNAME + if (!MY_SERIALDEVICE.setGroupPerm(MY_LINUX_SERIAL_GROUPNAME)) { + debug("Unable to change permission for serial port device.\n"); + exit(1); + } + #endif #endif } diff --git a/core/MyHwLinuxGeneric.h b/core/MyHwLinuxGeneric.h index 0d3e49b..eb5fee3 100644 --- a/core/MyHwLinuxGeneric.h +++ b/core/MyHwLinuxGeneric.h @@ -25,7 +25,12 @@ #include "MyHw.h" #include "SerialPort.h" -SerialPort Serial = SerialPort(MY_LINUX_SERIAL_PORT); +#ifdef MY_IS_SERIAL_PTY + SerialPort Serial = SerialPort(MY_LINUX_SERIAL_PTY, true); +#else + SerialPort Serial = SerialPort(MY_LINUX_SERIAL_PORT); +#endif + #define MY_SERIALDEVICE Serial // Define these as macros (do nothing) diff --git a/core/MyMainLinux.cpp b/core/MyMainLinux.cpp index 5798cec..fd044e4 100644 --- a/core/MyMainLinux.cpp +++ b/core/MyMainLinux.cpp @@ -20,6 +20,10 @@ void handle_sigint(int sig) detachInterrupt(MY_RF24_IRQ_PIN); #endif + #if defined(MY_GATEWAY_SERIAL) + MY_SERIALDEVICE.end(); + #endif + exit(0); } diff --git a/drivers/Linux/SerialPort.cpp b/drivers/Linux/SerialPort.cpp index 732d102..048db46 100644 --- a/drivers/Linux/SerialPort.cpp +++ b/drivers/Linux/SerialPort.cpp @@ -25,30 +25,67 @@ #include #include #include +#include +#include +#include #include "SerialPort.h" -#define SERIAL_PORT "/dev/ttyAMA0" - -SerialPort::SerialPort() +SerialPort::SerialPort(const char *port, bool isPty) : serialPort(std::string(port)), isPty(isPty) { - SerialPort(SERIAL_PORT); + sd = -1; } -SerialPort::SerialPort(const char *port) : serialPort(std::string(port)) -{} - void SerialPort::begin(int bauds) +{ + if (!open(bauds)) { + fprintf(stderr, "Failed to open serial port.\n"); + exit(1); + } +} + +bool SerialPort::open(int bauds) { speed_t speed; struct termios options; - if ((sd = open(serialPort.c_str(), O_RDWR | O_NOCTTY | O_NDELAY)) == -1) { - fprintf(stderr, "Unable to open the serial port %s - \n", serialPort.c_str()); - exit(-1); - } + if (isPty) { + sd = posix_openpt(O_RDWR | O_NOCTTY | O_NDELAY); + if (sd < 0) { + perror("Couldn't open a PTY"); + return false; + } - // nonblocking mode - fcntl(sd, F_SETFL, FNDELAY); + if (grantpt(sd) != 0) { + perror("Couldn't grant permission to the PTY"); + return false; + } + + if (unlockpt(sd) != 0) { + perror("Couldn't unlock the PTY"); + return false; + } + + // Get the current options of the port + if (tcgetattr(sd, &options) < 0) { + perror("Couldn't get term attributes"); + return false; + } + + /* create a symlink with predictable name to the PTY device */ + unlink(serialPort.c_str()); // remove the symlink if it already exists + if (symlink(ptsname(sd), serialPort.c_str()) != 0) { + fprintf(stderr, "Couldn't create a symlink '%s' to PTY! (%d) %s\n", serialPort.c_str(), errno, strerror(errno)); + return false; + } + } else { + if ((sd = ::open(serialPort.c_str(), O_RDWR | O_NOCTTY | O_NDELAY)) == -1) { + fprintf(stderr, "Unable to open the serial port %s\n", serialPort.c_str()); + return false; + } + + // nonblocking mode + fcntl(sd, F_SETFL, FNDELAY); + } switch (bauds) { case 50: speed = B50 ; break ; @@ -73,22 +110,20 @@ void SerialPort::begin(int bauds) // Get the current options of the port if (tcgetattr(sd, &options) < 0) { perror("Couldn't get term attributes"); - exit(1); + return false; } - // make raw - cfmakeraw(&options); + // Clear all the options + bzero(&options, sizeof(options)); // Set the baud rate cfsetispeed(&options, speed); cfsetospeed(&options, speed); - // turn on READ & ignore ctrl lines - options.c_cflag |= (CLOCAL | CREAD); - // 8N1 - options.c_cflag &= ~CSTOPB; - - options.c_lflag &= ~ECHOE; + // Configure the device : 8 bits, no parity, no control + options.c_cflag |= ( CLOCAL | CREAD | CS8); + // Ignore framing errors, parity errors and BREAK condition on input. + options.c_iflag |= ( IGNPAR | IGNBRK ); // Timer unused options.c_cc[VTIME]=0; @@ -98,16 +133,55 @@ void SerialPort::begin(int bauds) // Set parameters if (tcsetattr(sd, TCSANOW, &options) < 0) { perror("Couldn't set term attributes"); - exit(1); + return false; } // flush if (tcflush(sd, TCIOFLUSH) < 0) { perror("Couldn't flush serial"); - exit(1); + return false; } usleep(10000); + + return true; +} + +bool SerialPort::setGroupPerm(const char *groupName) +{ + struct group* devGrp; + const mode_t ttyPermissions = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; + const char *dev; + int ret; + + if (sd != -1 && groupName != NULL) { + devGrp = getgrnam(groupName); + if (devGrp == NULL) { + fprintf(stderr, "getgrnam: %s failed. (%d) %s\n", groupName, errno, strerror(errno)); + return false; + } + + if (isPty) { + dev = ptsname(sd); + } else { + dev = serialPort.c_str(); + } + + ret = chown(dev, -1, devGrp->gr_gid); + if (ret == -1) { + fprintf(stderr, "Could not change PTY owner! (%d) %s\n", errno, strerror(errno)); + return false; + } + + ret = chmod(dev, ttyPermissions); + if (ret != 0) { + fprintf(stderr, "Could not change PTY permissions! (%d) %s\n", errno, strerror(errno)); + return false; + } + + return true; + } + return false; } int SerialPort::available() @@ -156,4 +230,8 @@ void SerialPort::flush() void SerialPort::end() { close(sd); + + if (isPty) { + unlink(serialPort.c_str()); // remove the symlink + } } diff --git a/drivers/Linux/SerialPort.h b/drivers/Linux/SerialPort.h index e344c6d..e0f77bb 100644 --- a/drivers/Linux/SerialPort.h +++ b/drivers/Linux/SerialPort.h @@ -21,6 +21,7 @@ #define SerialPort_h #include +#include #include "Stream.h" /** @@ -32,22 +33,34 @@ class SerialPort : public Stream { private: int sd; //!< @brief file descriptor number. std::string serialPort; //!< @brief tty name. + bool isPty; //!< @brief true if serial is pseudo terminal. public: /** * @brief SerialPort constructor. */ - SerialPort(); + SerialPort(const char *port, bool isPty = false); /** - * @brief SerialPort constructor. - */ - SerialPort(const char *port); - /** - * @brief Sets the data rate in bits per second (baud) for serial data transmission. + * @brief Open the serial port and set the data rate in bits per second (baud). * - * @param serialSpeed serial port speed. + * This function will terminate the program on an error. + * + * @param bauds bits per second. */ - void begin(int serialSpeed); + void begin(int bauds); + /** + * @brief Open the serial port and set the data rate in bits per second (baud). + * + * @param bauds bits per second. + * @return @c true if no errors, else @c false. + */ + bool open(int bauds = 115200); + /** + * @brief Grant access to the specified system group for the serial device. + * + * @param groupName system group name. + */ + bool setGroupPerm(const char *groupName); /** * @brief Get the number of bytes available. * diff --git a/examples_linux/mysGateway.cpp b/examples_linux/mysGateway.cpp index baec194..1d7c745 100644 --- a/examples_linux/mysGateway.cpp +++ b/examples_linux/mysGateway.cpp @@ -29,6 +29,16 @@ // How many clients should be able to connect to this gateway (default 1) #define MY_GATEWAY_MAX_CLIENTS 10 +// Serial config +// Enable this if you are using an Arduino connected to the USB +//#define MY_LINUX_SERIAL_PORT "/dev/ttyACM0" +// Enable this if you need to connect to a controller running on the same device +//#define MY_IS_SERIAL_PTY +// Choose a symlink name for the PTY device +//#define MY_LINUX_SERIAL_PTY "/dev/ttyMySensorsGateway" +// Grant access to the specified system group for the serial device +//#define MY_LINUX_SERIAL_GROUPNAME "tty" + // MQTT options //#define MY_CONTROLLER_IP_ADDRESS 192, 168, 178, 68 //#define MY_PORT 1883