added support for UDP devices, added buffer for network devices, added option for sending an initial symbol after opening a network device, added option for setting transfer latency and use default 10 ms for network device, set TCP_NODELAY for TCP device socket, flush potentially buffered input on network device when connecting

This commit is contained in:
john30
2016-05-05 12:39:52 +02:00
parent 516bbe0edf
commit d5b07f5783
9 changed files with 220 additions and 56 deletions
+6 -6
View File
@@ -313,7 +313,7 @@ result_t BusHandler::handleSymbol()
break;
case bs_recvResAck:
timeout = m_slaveRecvTimeout;
timeout = m_slaveRecvTimeout+m_transferLatency;
break;
case bs_sendCmd:
@@ -354,12 +354,12 @@ result_t BusHandler::handleSymbol()
result_t result;
if (sending) {
result = m_device->send(sendSymbol);
if (result == RESULT_OK)
if (result == RESULT_OK) {
if (m_state == bs_ready)
timeout = m_busAcquireTimeout;
timeout = m_transferLatency+m_busAcquireTimeout;
else
timeout = SEND_TIMEOUT;
else {
timeout = m_transferLatency+SEND_TIMEOUT;
} else {
sending = false;
timeout = SYN_TIMEOUT;
if (startRequest != NULL && m_nextRequests.remove(startRequest)) {
@@ -371,7 +371,7 @@ result_t BusHandler::handleSymbol()
// receive next symbol (optionally check reception of sent symbol)
unsigned char recvSymbol;
result = m_device->recv(timeout, recvSymbol);
result = m_device->recv(timeout+m_transferLatency, recvSymbol);
if (!sending && result == RESULT_ERR_TIMEOUT && m_generateSynInterval > 0
&& timeout >= m_generateSynInterval && (m_state == bs_noSignal || m_state == bs_skip)) {
+7 -3
View File
@@ -303,8 +303,9 @@ public:
* @param answer whether to answer queries for the own master/slave address.
* @param busLostRetries the number of times a send is repeated due to lost arbitration.
* @param failedSendRetries the number of times a failed send is repeated (other than lost arbitration).
* @param slaveRecvTimeout the maximum time in microseconds an addressed slave is expected to acknowledge.
* @param transferLatency the bus transfer latency in microseconds.
* @param busAcquireTimeout the maximum time in microseconds for bus acquisition.
* @param slaveRecvTimeout the maximum time in microseconds an addressed slave is expected to acknowledge.
* @param lockCount the number of AUTO-SYN symbols before sending is allowed after lost arbitration, or 0 for auto detection.
* @param generateSyn whether to enable AUTO-SYN symbol generation.
* @param pollInterval the interval in seconds in which poll messages are cycled, or 0 if disabled.
@@ -312,13 +313,13 @@ public:
BusHandler(Device* device, MessageMap* messages,
const unsigned char ownAddress, const bool answer,
const unsigned int busLostRetries, const unsigned int failedSendRetries,
const unsigned int busAcquireTimeout, const unsigned int slaveRecvTimeout,
const unsigned int transferLatency, const unsigned int busAcquireTimeout, const unsigned int slaveRecvTimeout,
const unsigned int lockCount, const bool generateSyn,
const unsigned int pollInterval)
: m_device(device), m_messages(messages),
m_ownMasterAddress(ownAddress), m_ownSlaveAddress((unsigned char)(ownAddress+5)), m_answer(answer),
m_busLostRetries(busLostRetries), m_failedSendRetries(failedSendRetries),
m_busAcquireTimeout(busAcquireTimeout), m_slaveRecvTimeout(slaveRecvTimeout),
m_transferLatency(transferLatency), m_busAcquireTimeout(busAcquireTimeout), m_slaveRecvTimeout(slaveRecvTimeout),
m_masterCount(1), m_autoLockCount(lockCount==0), m_lockCount(lockCount<=3 ? 3 : lockCount), m_remainLockCount(m_autoLockCount),
m_generateSynInterval(generateSyn ? SYN_TIMEOUT*getMasterNumber(ownAddress)+SYMBOL_DURATION : 0),
m_pollInterval(pollInterval), m_lastReceive(0), m_lastPoll(0),
@@ -496,6 +497,9 @@ private:
/** the number of times a failed send is repeated (other than lost arbitration). */
const unsigned int m_failedSendRetries;
/** the bus transfer latency in microseconds. */
const unsigned int m_transferLatency;
/** the maximum time in microseconds for bus acquisition. */
const unsigned int m_busAcquireTimeout;
+24 -8
View File
@@ -65,7 +65,9 @@ static bool isDaemon = false;
static struct options opt = {
"/dev/ttyUSB0", // device
false, // noDeviceCheck
false, // readonly
false, // readOnly
false, // initialSend
-1, // latency
CONFIG_PATH, // configPath
false, // scanConfig
0, // checkConfig
@@ -108,7 +110,9 @@ const char *argp_program_bug_address = ""PACKAGE_BUGREPORT"";
static const char argpdoc[] =
"A daemon for communication with eBUS heating systems.";
#define O_CHKCFG 1
#define O_INISND 1
#define O_DEVLAT (O_INISND+1)
#define O_CHKCFG (O_DEVLAT+1)
#define O_DMPCFG (O_CHKCFG+1)
#define O_POLINT (O_DMPCFG+1)
#define O_ANSWER (O_POLINT+1)
@@ -132,9 +136,11 @@ static const char argpdoc[] =
/** the definition of the known program arguments. */
static const struct argp_option argpoptions[] = {
{NULL, 0, NULL, 0, "Device options:", 1 },
{"device", 'd', "DEV", 0, "Use DEV as eBUS device (serial or ip:port) [/dev/ttyUSB0]", 0 },
{"device", 'd', "DEV", 0, "Use DEV as eBUS device (serial or [udp:]ip:port) [/dev/ttyUSB0]", 0 },
{"nodevicecheck", 'n', NULL, 0, "Skip serial eBUS device test", 0 },
{"readonly", 'r', NULL, 0, "Only read from device, never write to it", 0 },
{"initsend", O_INISND, NULL, 0, "Send an initial escape symbol after connecting device", 0 },
{"latency", O_DEVLAT, "USEC", 0, "Transfer latency in us [0 for USB, 10000 for IP]", 0 },
{NULL, 0, NULL, 0, "Message configuration options:", 2 },
{"configpath", 'c', "PATH", 0, "Read CSV config files from PATH [" CONFIG_PATH "]", 0 },
@@ -209,12 +215,22 @@ error_t parse_opt(int key, char *arg, struct argp_state *state)
opt->noDeviceCheck = true;
break;
case 'r': // --readonly
opt->readonly = true;
opt->readOnly = true;
if (opt->scanConfig || opt->answer || opt->generateSyn) {
argp_error(state, "cannot combine readonly with scanconfig/answer/generatesyn");
return EINVAL;
}
break;
case O_INISND: // --initsend
opt->initialSend = true;
break;
case O_DEVLAT: // --latency
opt->latency = parseInt(arg, 10, 0, 200000, result);
if (result != RESULT_OK) {
argp_error(state, "invalid latency");
return EINVAL;
}
break;
// Message configuration options:
case 'c': // --configpath=/etc/ebusd
@@ -226,7 +242,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state)
break;
case 's': // --scanconfig
opt->scanConfig = true;
if (opt->readonly) {
if (opt->readOnly) {
argp_error(state, "cannot combine readonly with scanconfig/answer/generatesyn");
return EINVAL;
}
@@ -256,7 +272,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state)
break;
case O_ANSWER: // --answer
opt->answer = true;
if (opt->readonly) {
if (opt->readOnly) {
argp_error(state, "cannot combine readonly with scanconfig/answer/generatesyn");
return EINVAL;
}
@@ -298,7 +314,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state)
break;
case O_GENSYN: // --generatesyn
opt->generateSyn = true;
if (opt->readonly) {
if (opt->readOnly) {
argp_error(state, "cannot combine readonly with scanconfig/answer/generatesyn");
return EINVAL;
}
@@ -929,7 +945,7 @@ int main(int argc, char* argv[])
return EINVAL;
// open the device
Device *device = Device::create(opt.device, !opt.noDeviceCheck, opt.readonly, &logRawData);
Device *device = Device::create(opt.device, !opt.noDeviceCheck, opt.readOnly, opt.initialSend, &logRawData);
if (device == NULL) {
logError(lf_main, "unable to create device %s", opt.device);
return EINVAL;
+4 -2
View File
@@ -29,9 +29,11 @@
/** A structure holding all program options. */
struct options
{
const char* device; //!< eBUS device (serial device or ip:port) [/dev/ttyUSB0]
const char* device; //!< eBUS device (serial device or [udp:]ip:port) [/dev/ttyUSB0]
bool noDeviceCheck; //!< skip serial eBUS device test
bool readonly; //!< read-only access to the device
bool readOnly; //!< read-only access to the device
bool initialSend; //!< send an initial escape symbol after connecting device
int latency; //!< transfer latency in us [0 for USB, 10000 for IP]
const char* configPath; //!< path to CSV configuration files [/etc/ebusd]
bool scanConfig; //!< pick configuration files matching initial scan
+7 -1
View File
@@ -58,10 +58,16 @@ MainLoop::MainLoop(const struct options opt, Device *device, MessageMap* message
logError(lf_bus, "device %s not available", m_device->getName());
// create BusHandler
unsigned int latency;
if (opt.latency<0) {
latency = device->getLatency();
} else {
latency = (unsigned int)opt.latency;
}
m_busHandler = new BusHandler(m_device, m_messages,
m_address, opt.answer,
opt.acquireRetries, opt.sendRetries,
opt.acquireTimeout, opt.receiveTimeout,
latency, opt.acquireTimeout, opt.receiveTimeout,
opt.masterCount, opt.generateSyn,
opt.pollInterval);
m_busHandler->start("bushandler");
+94 -21
View File
@@ -22,13 +22,15 @@
#include "device.h"
#include "data.h"
#include <unistd.h>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <fstream>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <errno.h>
#ifdef HAVE_PPOLL
@@ -43,36 +45,45 @@ Device::~Device()
m_dumpRawStream.close();
}
Device* Device::create(const char* name, const bool checkDevice, const bool readonly,
Device* Device::create(const char* name, const bool checkDevice, const bool readOnly, const bool initialSend,
void (*logRawFunc)(const unsigned char byte, bool received))
{
if (strchr(name, '/') == NULL && strchr(name, ':') != NULL) {
char* dup = strdup(name);
char* pos = strchr(dup, ':');
char* in = strdup(name);
bool udp = false;
char* addrpos = in;
char* portpos = strchr(addrpos, ':');
if (portpos==addrpos+3 && (strncmp(addrpos, "tcp", 3)==0 || (udp=(strncmp(addrpos, "udp", 3)==0)))) {
addrpos += 4;
portpos = strchr(addrpos, ':');
}
if (portpos==NULL) {
free(in);
return NULL; // invalid protocol or missing port
}
result_t result = RESULT_OK;
unsigned int port = parseInt(pos+1, 10, 1, 65535, result);
unsigned int port = parseInt(portpos+1, 10, 1, 65535, result);
if (result!=RESULT_OK) {
free(dup);
free(in);
return NULL; // invalid port
}
struct sockaddr_in address;
memset((char*)&address, 0, sizeof(address));
*pos = 0;
if (inet_aton(dup, &address.sin_addr) == 0) {
struct hostent* h = gethostbyname(dup);
*portpos = 0;
if (inet_aton(addrpos, &address.sin_addr) == 0) {
struct hostent* h = gethostbyname(addrpos);
if (h == NULL) {
free(dup);
free(in);
return NULL; // invalid host
}
memcpy(&address.sin_addr, h->h_addr_list[0], h->h_length);
}
free(dup);
free(in);
address.sin_family = AF_INET;
address.sin_port = (in_port_t)htons((uint16_t)port);
return new NetworkDevice(name, address, readonly, logRawFunc);
return new NetworkDevice(name, address, readOnly, initialSend, logRawFunc, udp);
}
return new SerialDevice(name, checkDevice, readonly, logRawFunc);
return new SerialDevice(name, checkDevice, readOnly, initialSend, logRawFunc);
}
void Device::close()
@@ -99,7 +110,7 @@ result_t Device::send(const unsigned char value)
if (!isValid())
return RESULT_ERR_DEVICE;
if (m_readonly || write(m_fd, &value, 1) != 1)
if (m_readOnly || write(value) != 1)
return RESULT_ERR_SEND;
if (m_logRaw && m_logRawFunc != NULL)
@@ -113,7 +124,7 @@ result_t Device::recv(const long timeout, unsigned char& value)
if (!isValid())
return RESULT_ERR_DEVICE;
if (timeout > 0) {
if (!available() && timeout > 0) {
int ret;
struct timespec tdiff;
@@ -148,7 +159,7 @@ result_t Device::recv(const long timeout, unsigned char& value)
}
// directly read byte from device
ssize_t nbytes = read(m_fd, &value, 1);
ssize_t nbytes = read(value);
if (nbytes == 0)
return RESULT_ERR_EOF;
if (nbytes < 0)
@@ -251,6 +262,9 @@ result_t SerialDevice::open()
// set serial device into blocking mode
fcntl(m_fd, F_SETFL, fcntl(m_fd, F_GETFL) & ~O_NONBLOCK);
if (m_initialSend && write(ESC) != 1) {
return RESULT_ERR_SEND;
}
return RESULT_OK;
}
@@ -281,17 +295,43 @@ result_t NetworkDevice::open()
if (m_fd != -1)
close();
int ret;
m_fd = socket(AF_INET, SOCK_STREAM, 0);
m_fd = socket(AF_INET, m_udp ? SOCK_DGRAM : SOCK_STREAM, 0);
if (m_fd < 0)
return RESULT_ERR_GENERIC_IO;
ret = connect(m_fd, (struct sockaddr*)&m_address, sizeof(m_address));
int ret;
if (m_udp) {
struct sockaddr_in address = m_address;
address.sin_addr.s_addr = INADDR_ANY;
ret = bind(m_fd, (struct sockaddr*)&address, sizeof(address));
} else {
int value = 1;
ret = setsockopt(m_fd, IPPROTO_TCP, TCP_NODELAY, (void*)&value, sizeof(value));
}
if (ret==0) {
ret = connect(m_fd, (struct sockaddr*)&m_address, sizeof(m_address));
}
if (ret < 0) {
close();
return RESULT_ERR_GENERIC_IO;
}
int cnt;
if (ioctl(m_fd, FIONREAD, &cnt) >= 0 && cnt > 1) {
// skip buffered input
unsigned char buf[256];
while (::read(m_fd, &buf, 256) > 0);
}
if (m_bufSize==0) {
m_bufSize = MAX_LEN+1;
m_buffer = (unsigned char*)malloc(m_bufSize);
if (!m_buffer) {
m_bufSize = 0;
}
}
m_bufLen = 0;
if (m_initialSend && write(ESC) != 1) {
return RESULT_ERR_SEND;
}
return RESULT_OK;
}
@@ -300,6 +340,39 @@ void NetworkDevice::checkDevice()
unsigned char value;
ssize_t c = ::recv(m_fd, &value, 1, MSG_PEEK | MSG_DONTWAIT);
if (c == 0 || (c < 0 && errno != EAGAIN)) {
m_bufLen = 0; // flush read buffer
close();
}
}
bool NetworkDevice::available()
{
return m_buffer && m_bufLen>0;
}
ssize_t NetworkDevice::write(const unsigned char value)
{
m_bufLen = 0; // flush read buffer
return Device::write(value);
}
ssize_t NetworkDevice::read(unsigned char& value)
{
if (available()) {
value = m_buffer[m_bufPos];
m_bufPos = (unsigned char)((m_bufPos+1)%m_bufSize);
m_bufLen--;
return 1;
}
if (m_bufSize>0) {
ssize_t size = ::read(m_fd, m_buffer, m_bufSize);
if (size<=0) {
return size;
}
value = m_buffer[0];
m_bufPos = 1;
m_bufLen = (unsigned char)(size-1);
return size;
}
return Device::read(value);
}
+76 -13
View File
@@ -19,6 +19,7 @@
#ifndef LIBEBUS_DEVICE_H_
#define LIBEBUS_DEVICE_H_
#include <unistd.h>
#include <termios.h>
#include <iostream>
#include <fstream>
@@ -48,12 +49,13 @@ public:
* Construct a new instance.
* @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network).
* @param checkDevice whether to regularly check the device availability (only for serial devices).
* @param readonly whether to allow read access to the device only.
* @param readOnly whether to allow read access to the device only.
* @param initialSend whether to send an initial @a ESC symbol in @a open().
* @param logRawFunc the function to call for logging raw data, or NULL.
*/
Device(const char* name, const bool checkDevice, const bool readonly,
Device(const char* name, const bool checkDevice, const bool readOnly, const bool initialSend,
void (*logRawFunc)(const unsigned char byte, bool received))
: m_name(name), m_checkDevice(checkDevice), m_readonly(readonly), m_fd(-1),
: m_name(name), m_checkDevice(checkDevice), m_readOnly(readOnly), m_initialSend(initialSend), m_fd(-1),
m_logRaw(false), m_logRawFunc(logRawFunc),
m_dumpRaw(false), m_dumpRawFile(NULL), m_dumpRawMaxSize(0), m_dumpRawStream(), m_dumpRawFileSize(0) {}
@@ -66,14 +68,21 @@ public:
* Factory method for creating a new instance.
* @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network).
* @param checkDevice whether to regularly check the device availability (only for serial devices).
* @param readonly whether to allow read access to the device only.
* @param readOnly whether to allow read access to the device only.
* @param initialSend whether to send an initial @a ESC symbol in @a open().
* @param logRawFunc the function to call for logging raw data, or NULL.
* @return the new @a Device, or NULL on error.
* Note: the caller needs to free the created instance.
*/
static Device* create(const char* name, const bool checkDevice=true, const bool readonly=false,
static Device* create(const char* name, const bool checkDevice=true, const bool readOnly=false, const bool initialSend=false,
void (*logRawFunc)(const unsigned char byte, bool received)=NULL);
/**
* Get the transfer latency of this device.
* @return the transfer latency in microseconds.
*/
virtual unsigned int getLatency() const { return 0; }
/**
* Open the file descriptor.
* @return the @a result_t code.
@@ -154,6 +163,26 @@ protected:
*/
virtual void checkDevice() = 0; // abstract
/**
* Check whether a byte is available immediately (without waiting).
* @return true when a a byte is available immediately.
*/
virtual bool available() { return false; }
/**
* Write a single byte.
* @param value the byte value to write.
* @return the number of bytes written, or -1 on error.
*/
virtual ssize_t write(const unsigned char value) { return ::write(m_fd, &value, 1); }
/**
* Read a single byte.
* @param value the reference in which the read byte value is stored.
* @return the number of bytes read, or -1 on error.
*/
virtual ssize_t read(unsigned char& value) { return ::read(m_fd, &value, 1); }
protected:
/** the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). */
const char* m_name;
@@ -162,7 +191,10 @@ protected:
const bool m_checkDevice;
/** whether to allow read access to the device only. */
const bool m_readonly;
const bool m_readOnly;
/** whether to send an initial @a ESC symbol in @a open(). */
const bool m_initialSend;
/** the opened file descriptor, or -1. */
int m_fd;
@@ -201,12 +233,13 @@ public:
* Construct a new instance.
* @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network).
* @param checkDevice whether to regularly check the device availability (only for serial devices).
* @param readonly whether to allow read access to the device only.
* @param readOnly whether to allow read access to the device only.
* @param initialSend whether to send an initial @a ESC symbol in @a open().
* @param logRawFunc the function to call for logging raw data, or NULL.
*/
SerialDevice(const char* name, const bool checkDevice, const bool readonly,
SerialDevice(const char* name, const bool checkDevice, const bool readOnly, const bool initialSend,
void (*logRawFunc)(const unsigned char byte, bool received))
: Device(name, checkDevice, readonly, logRawFunc) {}
: Device(name, checkDevice, readOnly, initialSend, logRawFunc) {}
// @copydoc
virtual result_t open();
@@ -234,12 +267,18 @@ public:
* Construct a new instance.
* @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network).
* @param address the socket address of the device.
* @param readonly whether to allow read access to the device only.
* @param readOnly whether to allow read access to the device only.
* @param initialSend whether to send an initial @a ESC symbol in @a open().
* @param logRawFunc the function to call for logging raw data, or NULL.
* @param udp true for UDP, false to TCP.
*/
NetworkDevice(const char* name, const struct sockaddr_in address, const bool readonly,
void (*logRawFunc)(const unsigned char byte, bool received))
: Device(name, true, readonly, logRawFunc), m_address(address) {}
NetworkDevice(const char* name, const struct sockaddr_in address, const bool readOnly, const bool initialSend,
void (*logRawFunc)(const unsigned char byte, bool received), const bool udp)
: Device(name, true, readOnly, initialSend, logRawFunc), m_address(address), m_udp(udp),
m_buffer(NULL), m_bufSize(0), m_bufLen(0), m_bufPos(0) {}
// @copydoc
virtual unsigned int getLatency() const { return 10000; }
// @copydoc
virtual result_t open();
@@ -248,10 +287,34 @@ protected:
// @copydoc
virtual void checkDevice();
// @copydoc
virtual bool available();
// @copydoc
virtual ssize_t write(const unsigned char value);
// @copydoc
virtual ssize_t read(unsigned char& value);
private:
/** the socket address of the device. */
const struct sockaddr_in m_address;
/** true for UDP, false to TCP. */
const bool m_udp;
/** the buffer memory, or NULL. */
unsigned char* m_buffer;
/** the buffer size. */
unsigned char m_bufSize;
/** the buffer fill length. */
unsigned char m_bufLen;
/** the buffer read position. */
unsigned char m_bufPos;
};
#endif // LIBEBUS_DEVICE_H_
+1 -1
View File
@@ -24,7 +24,7 @@ using namespace std;
int main ()
{
Device* device = Device::create("/dev/ttyUSB20", true, NULL);
Device* device = Device::create("/dev/ttyUSB20", true, false, NULL);
if (device == NULL) {
cout << "unable to create device" << endl;
return -1;
+1 -1
View File
@@ -129,7 +129,7 @@ int main(int argc, char* argv[])
if (argp_parse(&argp, argc, argv, ARGP_IN_ORDER, NULL, &opt) != 0)
return EINVAL;
Device* device = Device::create(opt.device, false, NULL);
Device* device = Device::create(opt.device, false, false, NULL);
if (device == NULL) {
cout << "unable to create device " << opt.device << endl;
return EINVAL;