diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 522a6421..3000f9fc 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -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)) { diff --git a/src/ebusd/bushandler.h b/src/ebusd/bushandler.h index 363a1295..e3d9aead 100644 --- a/src/ebusd/bushandler.h +++ b/src/ebusd/bushandler.h @@ -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; diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index fe984d7b..bd3454ae 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -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; diff --git a/src/ebusd/main.h b/src/ebusd/main.h index e03f914c..f53f64a2 100644 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -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 diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 1ed53804..dc07a557 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -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"); diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 9b674877..07f7dda7 100644 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -22,13 +22,15 @@ #include "device.h" #include "data.h" -#include #include #include #include #include #include #include +#include +#include +#include #include #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); +} diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 3a8892ed..0184c10f 100644 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -19,6 +19,7 @@ #ifndef LIBEBUS_DEVICE_H_ #define LIBEBUS_DEVICE_H_ +#include #include #include #include @@ -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_ diff --git a/src/lib/ebus/test/test_device.cpp b/src/lib/ebus/test/test_device.cpp index 8e5a89f5..e9c3260d 100644 --- a/src/lib/ebus/test/test_device.cpp +++ b/src/lib/ebus/test/test_device.cpp @@ -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; diff --git a/src/tools/ebusfeed.cpp b/src/tools/ebusfeed.cpp index cb9a66cf..29af9a76 100644 --- a/src/tools/ebusfeed.cpp +++ b/src/tools/ebusfeed.cpp @@ -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;