From 28c5bfab5cee5f0ead31e9aec0c6fdac9b8bf289 Mon Sep 17 00:00:00 2001 From: Christopher Stawarz Date: Wed, 30 Mar 2016 11:32:23 -0400 Subject: [PATCH] NE500 now supports connection via a USB-to-serial adapter --- .../Connections/NE500SerialConnection.cpp | 152 ++++++++++++++++++ .../Connections/NE500SerialConnection.hpp | 45 ++++++ .../Connections/NE500SocketConnection.hpp | 1 - plugins/core/NE500/MWComponents.yaml | 13 +- .../NE500Plugin.xcodeproj/project.pbxproj | 6 + plugins/core/NE500/NE500PumpNetworkDevice.cpp | 14 +- plugins/core/NE500/NE500PumpNetworkDevice.h | 2 - 7 files changed, 222 insertions(+), 11 deletions(-) create mode 100644 plugins/core/NE500/Connections/NE500SerialConnection.cpp create mode 100644 plugins/core/NE500/Connections/NE500SerialConnection.hpp diff --git a/plugins/core/NE500/Connections/NE500SerialConnection.cpp b/plugins/core/NE500/Connections/NE500SerialConnection.cpp new file mode 100644 index 000000000..dbbdc5d13 --- /dev/null +++ b/plugins/core/NE500/Connections/NE500SerialConnection.cpp @@ -0,0 +1,152 @@ +// +// NE500SerialConnection.cpp +// NE500Plugin +// +// Created by Christopher Stawarz on 3/29/16. +// +// + +#include "NE500SerialConnection.hpp" + +#include + + +BEGIN_NAMESPACE_MW + + +bool NE500SerialConnection::connect() { + if (connected) { + return true; + } + + // Open the serial port read/write, with no controlling terminal, and don't wait for a connection. + // The O_NONBLOCK flag also causes subsequent I/O on the device to be non-blocking. + if (-1 == (fd = ::open(path.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK))) { + merror(M_IODEVICE_MESSAGE_DOMAIN, "Cannot open serial port (%s): %s", path.c_str(), strerror(errno)); + return false; + } + + // open() follows POSIX semantics: multiple open() calls to the same file will succeed + // unless the TIOCEXCL ioctl is issued. This will prevent additional opens except by root-owned + // processes. + if (-1 == ioctl(fd, TIOCEXCL)) { + merror(M_IODEVICE_MESSAGE_DOMAIN, "Cannot obtain exclusive use of serial port: %s", strerror(errno)); + close(fd); + return false; + } + + // Get the current options and save them, so we can restore the default settings later + if (-1 == tcgetattr(fd, &origAttrs)) { + merror(M_IODEVICE_MESSAGE_DOMAIN, "Cannot obtain current serial port attributes: %s", strerror(errno)); + close(fd); + return false; + } + + struct termios attrs = origAttrs; + cfmakeraw(&attrs); // Set raw input (non-canonical) mode + cfsetspeed(&attrs, B19200); // Set speed to 19200 baud + attrs.c_cflag |= CS8; // Use 8-bit words + attrs.c_cflag &= ~PARENB; // No parity + attrs.c_cflag &= ~CSTOPB; // 1 stop bit + + // Cause the new options to take effect immediately + if (-1 == tcsetattr(fd, TCSANOW, &attrs)) { + merror(M_IODEVICE_MESSAGE_DOMAIN, "Cannot set serial port attributes: %s", strerror(errno)); + close(fd); + return false; + } + + connected = true; + return true; +} + + +bool NE500SerialConnection::disconnect() { + if (!connected) { + return true; + } + + // Block until all written output has been sent to the device + if (-1 == tcdrain(fd)) { + merror(M_IODEVICE_MESSAGE_DOMAIN, "Serial port drain failed: %s", strerror(errno)); + } + + // Restore original options + if (-1 == tcsetattr(fd, TCSANOW, &origAttrs)) { + merror(M_IODEVICE_MESSAGE_DOMAIN, "Cannot restore previous serial port attributes: %s", strerror(errno)); + } + + if (-1 == ::close(fd)) { + merror(M_IODEVICE_MESSAGE_DOMAIN, "Cannot close serial port: %s", strerror(errno)); + } + + connected = false; + return true; +} + + +bool NE500SerialConnection::send(const std::string &command) { + if (!connected) { + return false; + } + + if (-1 == ::write(fd, command.data(), command.size())) { + merror(M_IODEVICE_MESSAGE_DOMAIN, "Cannot send data to NE500 device: %s", strerror(errno)); + return false; + } + + return true; +} + + +bool NE500SerialConnection::receive(std::string &response) { + if (!connected) { + return false; + } + + std::array buffer; + auto rc = ::read(fd, buffer.data(), buffer.size()); + + if (rc >= 0) { + + response.append(buffer.data(), rc); + + } else if (errno != EWOULDBLOCK) { + + merror(M_IODEVICE_MESSAGE_DOMAIN, "Cannot receive data from NE500 device: %s", strerror(errno)); + return false; + + } + + return true; +} + + +END_NAMESPACE_MW + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/core/NE500/Connections/NE500SerialConnection.hpp b/plugins/core/NE500/Connections/NE500SerialConnection.hpp new file mode 100644 index 000000000..cd55e396d --- /dev/null +++ b/plugins/core/NE500/Connections/NE500SerialConnection.hpp @@ -0,0 +1,45 @@ +// +// NE500SerialConnection.hpp +// NE500Plugin +// +// Created by Christopher Stawarz on 3/29/16. +// +// + +#ifndef NE500SerialConnection_hpp +#define NE500SerialConnection_hpp + +#include "NE500Connection.hpp" + +#include + + +BEGIN_NAMESPACE_MW + + +class NE500SerialConnection : public NE500Connection { + +public: + explicit NE500SerialConnection(const std::string &path) : + path(path), + fd(-1) + { } + + bool connect() override; + bool disconnect() override; + + bool send(const std::string &command) override; + bool receive(std::string &response) override; + +private: + const std::string path; + int fd; + struct termios origAttrs; + +}; + + +END_NAMESPACE_MW + + +#endif /* NE500SerialConnection_hpp */ diff --git a/plugins/core/NE500/Connections/NE500SocketConnection.hpp b/plugins/core/NE500/Connections/NE500SocketConnection.hpp index 53a1de8a7..c1aafab2d 100644 --- a/plugins/core/NE500/Connections/NE500SocketConnection.hpp +++ b/plugins/core/NE500/Connections/NE500SocketConnection.hpp @@ -33,7 +33,6 @@ class NE500SocketConnection : public NE500Connection { private: const std::string address; const int port; - int s; }; diff --git a/plugins/core/NE500/MWComponents.yaml b/plugins/core/NE500/MWComponents.yaml index 44e11abd6..389603b48 100644 --- a/plugins/core/NE500/MWComponents.yaml +++ b/plugins/core/NE500/MWComponents.yaml @@ -8,15 +8,22 @@ icon: smallIOFolder description: > Used for interfacing to a network of `NE-500 syringe pumps `_, accessed via a serial-to-ethernet - bridge + bridge or a USB-to-serial adapter parameters: - name: address required: yes - example: "10.0.254.254" + example: + - '"10.0.254.254"' + - '"/dev/cu.usbserial-FTH1RRH5"' + description: > + If `port`_ is provided, the address is taken to be a host name, + and MWorks attempts to connect to the pump network via a + `TCP `_ + socket. Otherwise, the address is assumed to be a filesystem path that + represents a serial port. - name: port - required: yes example: 100 - name: response_timeout diff --git a/plugins/core/NE500/NE500Plugin.xcodeproj/project.pbxproj b/plugins/core/NE500/NE500Plugin.xcodeproj/project.pbxproj index 8bb128d11..40a7d5417 100644 --- a/plugins/core/NE500/NE500Plugin.xcodeproj/project.pbxproj +++ b/plugins/core/NE500/NE500Plugin.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ E126F26E1BFBAF5F00CFC9FF /* NE500DeviceChannel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E126F26C1BFBAF5F00CFC9FF /* NE500DeviceChannel.cpp */; }; E12D98B716BC2217004FEF79 /* NE500Plugin.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8D5B49B6048680CD000E48DA /* NE500Plugin.bundle */; }; E15D0B0016C2B34100F331B1 /* libboost_system.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E15D0AFF16C2B34100F331B1 /* libboost_system.a */; }; + E16361131CAB04FF009752BE /* NE500SerialConnection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E16361111CAB04FF009752BE /* NE500SerialConnection.cpp */; }; E1810E101CAACB62003001C0 /* NE500Connection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E1810E0E1CAACB62003001C0 /* NE500Connection.cpp */; }; E1810E131CAACDCE003001C0 /* NE500SocketConnection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E1810E111CAACDCE003001C0 /* NE500SocketConnection.cpp */; }; E1E07E9F1C04F2D8008DD97E /* MWComponents.yaml in Resources */ = {isa = PBXBuildFile; fileRef = E1E07E9E1C04F2D8008DD97E /* MWComponents.yaml */; }; @@ -103,6 +104,8 @@ E126F26C1BFBAF5F00CFC9FF /* NE500DeviceChannel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NE500DeviceChannel.cpp; sourceTree = ""; }; E126F26D1BFBAF5F00CFC9FF /* NE500DeviceChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NE500DeviceChannel.h; sourceTree = ""; }; E15D0AFF16C2B34100F331B1 /* libboost_system.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libboost_system.a; path = "/Library/Application Support/MWorks/Developer/lib/libboost_system.a"; sourceTree = ""; }; + E16361111CAB04FF009752BE /* NE500SerialConnection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NE500SerialConnection.cpp; sourceTree = ""; }; + E16361121CAB04FF009752BE /* NE500SerialConnection.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = NE500SerialConnection.hpp; sourceTree = ""; }; E1810E0E1CAACB62003001C0 /* NE500Connection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NE500Connection.cpp; sourceTree = ""; }; E1810E0F1CAACB62003001C0 /* NE500Connection.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = NE500Connection.hpp; sourceTree = ""; }; E1810E111CAACDCE003001C0 /* NE500SocketConnection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NE500SocketConnection.cpp; sourceTree = ""; }; @@ -201,6 +204,8 @@ children = ( E1810E0F1CAACB62003001C0 /* NE500Connection.hpp */, E1810E0E1CAACB62003001C0 /* NE500Connection.cpp */, + E16361121CAB04FF009752BE /* NE500SerialConnection.hpp */, + E16361111CAB04FF009752BE /* NE500SerialConnection.cpp */, E1810E121CAACDCE003001C0 /* NE500SocketConnection.hpp */, E1810E111CAACDCE003001C0 /* NE500SocketConnection.cpp */, ); @@ -287,6 +292,7 @@ 5C4B0A600DC791D4001BC518 /* NE500PumpNetworkDevice.cpp in Sources */, 5C4B0ADE0DC793E2001BC518 /* NE500Plugin.cpp in Sources */, E126F26E1BFBAF5F00CFC9FF /* NE500DeviceChannel.cpp in Sources */, + E16361131CAB04FF009752BE /* NE500SerialConnection.cpp in Sources */, E1810E131CAACDCE003001C0 /* NE500SocketConnection.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/plugins/core/NE500/NE500PumpNetworkDevice.cpp b/plugins/core/NE500/NE500PumpNetworkDevice.cpp index 57263d3bb..d4a5151fe 100644 --- a/plugins/core/NE500/NE500PumpNetworkDevice.cpp +++ b/plugins/core/NE500/NE500PumpNetworkDevice.cpp @@ -9,6 +9,7 @@ #include "NE500PumpNetworkDevice.h" +#include "NE500SerialConnection.hpp" #include "NE500SocketConnection.hpp" @@ -27,7 +28,7 @@ void NE500PumpNetworkDevice::describeComponent(ComponentInfo &info) { info.setSignature("iodevice/ne500"); info.addParameter(ADDRESS); - info.addParameter(PORT); + info.addParameter(PORT, false); info.addParameter(RESPONSE_TIMEOUT, "100ms"); info.addParameter(LOG_PUMP_COMMANDS, "YES"); } @@ -35,13 +36,16 @@ void NE500PumpNetworkDevice::describeComponent(ComponentInfo &info) { NE500PumpNetworkDevice::NE500PumpNetworkDevice(const ParameterValueMap ¶meters) : IODevice(parameters), - address(VariablePtr(parameters[ADDRESS])->getValue().getString()), - port(parameters[PORT]), response_timeout(parameters[RESPONSE_TIMEOUT]), logPumpCommands(parameters[LOG_PUMP_COMMANDS]), active(false) { - connection.reset(new NE500SocketConnection(address, port)); + const std::string address(VariablePtr(parameters[ADDRESS])->getValue().getString()); + if (parameters[PORT].empty()) { + connection.reset(new NE500SerialConnection(address)); + } else { + connection.reset(new NE500SocketConnection(address, int(parameters[PORT]))); + } } @@ -109,7 +113,7 @@ static inline std::string removeControlChars(std::string str) { bool NE500PumpNetworkDevice::sendMessage(const std::string &pump_id, string message) { if (!connection->isConnected()) { - merror(M_IODEVICE_MESSAGE_DOMAIN, "No connection to NE500 device (%s)", address.c_str()); + merror(M_IODEVICE_MESSAGE_DOMAIN, "No connection to NE500 device"); return false; } diff --git a/plugins/core/NE500/NE500PumpNetworkDevice.h b/plugins/core/NE500/NE500PumpNetworkDevice.h index 34a5ecc98..0c93d77b9 100644 --- a/plugins/core/NE500/NE500PumpNetworkDevice.h +++ b/plugins/core/NE500/NE500PumpNetworkDevice.h @@ -20,8 +20,6 @@ BEGIN_NAMESPACE_MW class NE500PumpNetworkDevice : public IODevice, boost::noncopyable { private: - const std::string address; - const int port; const MWTime response_timeout; const bool logPumpCommands;