rework ebus protocol engine (consume buffered data in a row, avoid starting arbitration when data is buffered, separate transport to/from device)
This commit is contained in:
+3
-1
@@ -10,13 +10,15 @@
|
|||||||
* fix potentially unusable SSL context
|
* fix potentially unusable SSL context
|
||||||
* fix SYN generator timing
|
* fix SYN generator timing
|
||||||
* fix missing check for PB/SB validity
|
* fix missing check for PB/SB validity
|
||||||
|
* fix non-SSL build
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
* add temperatures in Kelvin and ... to Home Assistant MQTT discovery integration
|
* add temperatures in Kelvin and ... to Home Assistant MQTT discovery integration
|
||||||
* add options to turn off scanconfig and limit number of retries
|
* add options to turn off scanconfig and limit number of retries
|
||||||
* remove dependency on argp
|
* remove dependency on argp
|
||||||
* add time fields to Home Assistant MQTT discovery integration
|
* add time fields to Home Assistant MQTT discovery integration
|
||||||
+ add templates endpoint to HTTP JSON
|
* add templates endpoint to HTTP JSON
|
||||||
|
* add reworked eBUS protocol engine that is especially useful for slow network issues
|
||||||
|
|
||||||
|
|
||||||
# 23.2 (2023-07-08)
|
# 23.2 (2023-07-08)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ set(libebus_a_SOURCES
|
|||||||
datatype.h datatype.cpp
|
datatype.h datatype.cpp
|
||||||
data.h data.cpp
|
data.h data.cpp
|
||||||
device.h device.cpp
|
device.h device.cpp
|
||||||
|
transport.h transport.cpp
|
||||||
protocol.h protocol.cpp
|
protocol.h protocol.cpp
|
||||||
protocol_direct.h protocol_direct.cpp
|
protocol_direct.h protocol_direct.cpp
|
||||||
message.h message.cpp
|
message.h message.cpp
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ libebus_a_SOURCES = \
|
|||||||
datatype.h datatype.cpp \
|
datatype.h datatype.cpp \
|
||||||
data.h data.cpp \
|
data.h data.cpp \
|
||||||
device.h device.cpp \
|
device.h device.cpp \
|
||||||
|
transport.h transport.cpp \
|
||||||
protocol.h protocol.cpp \
|
protocol.h protocol.cpp \
|
||||||
protocol_direct.h protocol_direct.cpp \
|
protocol_direct.h protocol_direct.cpp \
|
||||||
message.h message.cpp \
|
message.h message.cpp \
|
||||||
|
|||||||
+220
-615
@@ -21,28 +21,10 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "lib/ebus/device.h"
|
#include "lib/ebus/device.h"
|
||||||
#include <fcntl.h>
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <sys/file.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#ifdef HAVE_LINUX_SERIAL
|
|
||||||
# include <linux/serial.h>
|
|
||||||
#endif
|
|
||||||
#ifdef HAVE_FREEBSD_UFTDI
|
|
||||||
# include <dev/usb/uftdiio.h>
|
|
||||||
#endif
|
|
||||||
#ifdef HAVE_PPOLL
|
|
||||||
# include <poll.h>
|
|
||||||
#endif
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <fstream>
|
|
||||||
#include <ios>
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include "lib/ebus/data.h"
|
|
||||||
#include "lib/utils/clock.h"
|
#include "lib/utils/clock.h"
|
||||||
#include "lib/utils/tcpsocket.h"
|
|
||||||
|
|
||||||
namespace ebusd {
|
namespace ebusd {
|
||||||
|
|
||||||
@@ -53,13 +35,6 @@ using std::setw;
|
|||||||
using std::setprecision;
|
using std::setprecision;
|
||||||
using std::fixed;
|
using std::fixed;
|
||||||
|
|
||||||
|
|
||||||
#define MTU 1540
|
|
||||||
|
|
||||||
#ifndef POLLRDHUP
|
|
||||||
#define POLLRDHUP 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// ebusd enhanced protocol IDs:
|
// ebusd enhanced protocol IDs:
|
||||||
#define ENH_REQ_INIT ((uint8_t)0x0)
|
#define ENH_REQ_INIT ((uint8_t)0x0)
|
||||||
#define ENH_RES_RESETTED ((uint8_t)0x0)
|
#define ENH_RES_RESETTED ((uint8_t)0x0)
|
||||||
@@ -85,66 +60,107 @@ using std::fixed;
|
|||||||
#define makeEnhancedByte2(cmd, data) (uint8_t)(ENH_BYTE2 | ((data)&0x3f))
|
#define makeEnhancedByte2(cmd, data) (uint8_t)(ENH_BYTE2 | ((data)&0x3f))
|
||||||
#define makeEnhancedSequence(cmd, data) {makeEnhancedByte1(cmd, data), makeEnhancedByte2(cmd, data)}
|
#define makeEnhancedSequence(cmd, data) {makeEnhancedByte1(cmd, data), makeEnhancedByte2(cmd, data)}
|
||||||
|
|
||||||
#ifdef DEBUG_RAW_TRAFFIC
|
|
||||||
#define DEBUG_RAW_TRAFFIC_HEAD(format, args...) fprintf(stdout, "%lld raw: " format, clockGetMillis(), args)
|
|
||||||
#define DEBUG_RAW_TRAFFIC_ITEM(args...) fprintf(stdout, args)
|
|
||||||
#define DEBUG_RAW_TRAFFIC_FINAL() fprintf(stdout, "\n"); fflush(stdout)
|
|
||||||
#undef DEBUG_RAW_TRAFFIC
|
|
||||||
#define DEBUG_RAW_TRAFFIC(format, args...) fprintf(stdout, "%lld raw: " format "\n", clockGetMillis(), args); fflush(stdout)
|
|
||||||
#else
|
|
||||||
#define DEBUG_RAW_TRAFFIC_HEAD(format, args...)
|
|
||||||
#undef DEBUG_RAW_TRAFFIC_ITEM
|
|
||||||
#define DEBUG_RAW_TRAFFIC_FINAL()
|
|
||||||
#define DEBUG_RAW_TRAFFIC(format, args...)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Device::Device(const char* name)
|
result_t PlainCharDevice::send(symbol_t value) {
|
||||||
: m_name(name), m_listener(nullptr) {
|
result_t result = m_transport->write(&value, 1);
|
||||||
|
if (result == RESULT_OK && m_listener != nullptr) {
|
||||||
|
m_listener->notifyDeviceData(&value, 1, false);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result_t PlainCharDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) {
|
||||||
|
if (m_arbitrationMaster != SYN) {
|
||||||
|
*arbitrationState = as_running;
|
||||||
|
}
|
||||||
|
uint64_t until = timeout == 0 ? 0 : clockGetMillis() + timeout + m_transport->getLatency();
|
||||||
|
const uint8_t* data = nullptr;
|
||||||
|
size_t len = 0;
|
||||||
|
result_t result;
|
||||||
|
do {
|
||||||
|
result = m_transport->read(timeout, &data, &len);
|
||||||
|
if (result == RESULT_OK) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (result != RESULT_ERR_TIMEOUT) {
|
||||||
|
cancelRunningArbitration(arbitrationState);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (timeout == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint64_t now = clockGetMillis();
|
||||||
|
if (timeout == 0 || now >= until) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
timeout = static_cast<unsigned>(until - now);
|
||||||
|
} while (true);
|
||||||
|
if (result == RESULT_OK && len > 0 && data) {
|
||||||
|
*value = *data;
|
||||||
|
m_transport->readConsumed(1);
|
||||||
|
if (m_listener != nullptr) {
|
||||||
|
m_listener->notifyDeviceData(value, 1, true);
|
||||||
|
}
|
||||||
|
if (len > 1) {
|
||||||
|
result = RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
if (*value != SYN || m_arbitrationMaster == SYN || m_arbitrationCheck) {
|
||||||
|
if (m_arbitrationMaster != SYN) {
|
||||||
|
if (m_arbitrationCheck) {
|
||||||
|
*arbitrationState = *value == m_arbitrationMaster ? as_won : as_lost;
|
||||||
|
m_arbitrationMaster = SYN;
|
||||||
|
m_arbitrationCheck = 0;
|
||||||
|
} else {
|
||||||
|
*arbitrationState = m_arbitrationMaster == SYN ? as_none : as_start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (len == 1) {
|
||||||
|
// arbitration executed by ebusd itself
|
||||||
|
bool wrote = m_transport->write(&m_arbitrationMaster, 1) == RESULT_OK; // send as fast as possible
|
||||||
|
if (!wrote) {
|
||||||
|
cancelRunningArbitration(arbitrationState);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (m_listener != nullptr) {
|
||||||
|
m_listener->notifyDeviceData(&m_arbitrationMaster, 1, false);
|
||||||
|
}
|
||||||
|
m_arbitrationCheck = 1;
|
||||||
|
*arbitrationState = as_running;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result_t CharDevice::startArbitration(symbol_t masterAddress) {
|
||||||
|
if (m_arbitrationCheck) {
|
||||||
|
if (masterAddress != SYN) {
|
||||||
|
return RESULT_ERR_ARB_RUNNING; // should not occur
|
||||||
|
}
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
m_arbitrationMaster = masterAddress;
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CharDevice::cancelRunningArbitration(ArbitrationState* arbitrationState) {
|
||||||
|
if (m_arbitrationMaster == SYN) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (arbitrationState) {
|
||||||
|
*arbitrationState = as_error;
|
||||||
|
}
|
||||||
|
m_arbitrationMaster = SYN;
|
||||||
|
m_arbitrationCheck = 0;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void EnhancedCharDevice::formatInfo(ostringstream* ostream, bool verbose, bool prefix) {
|
||||||
FileDevice::FileDevice(const char* name, bool checkDevice, unsigned int latency,
|
CharDevice::formatInfo(ostream, verbose, prefix);
|
||||||
EnhancedLevel enhancedLevel)
|
|
||||||
: Device(name),
|
|
||||||
m_checkDevice(checkDevice),
|
|
||||||
m_latency(HOST_LATENCY_MS+(enhancedLevel?ENHANCED_LATENCY_MS:0)+latency),
|
|
||||||
m_enhancedLevel(enhancedLevel), m_fd(-1), m_resetRequested(false),
|
|
||||||
m_arbitrationMaster(SYN), m_arbitrationCheck(0),
|
|
||||||
m_bufSize(((MAX_LEN+1+3)/4)*4), m_bufLen(0), m_bufPos(0),
|
|
||||||
m_sendBuf(nullptr), m_sendBufSize(0),
|
|
||||||
m_extraFatures(0), m_infoReqTime(0), m_infoLen(0), m_infoPos(0) {
|
|
||||||
m_buffer = reinterpret_cast<symbol_t*>(malloc(m_bufSize));
|
|
||||||
if (!m_buffer) {
|
|
||||||
m_bufSize = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileDevice::~FileDevice() {
|
|
||||||
close();
|
|
||||||
if (m_buffer) {
|
|
||||||
free(m_buffer);
|
|
||||||
m_buffer = nullptr;
|
|
||||||
}
|
|
||||||
if (m_sendBuf) {
|
|
||||||
free(m_sendBuf);
|
|
||||||
m_sendBuf = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileDevice::formatInfo(ostringstream* ostream, bool verbose, bool prefix) {
|
|
||||||
if (prefix) {
|
if (prefix) {
|
||||||
*ostream << m_name;
|
*ostream << ", enhanced";
|
||||||
string info = getEnhancedProtoInfo();
|
|
||||||
if (!info.empty()) {
|
|
||||||
*ostream << ", " << info;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isValid()) {
|
|
||||||
*ostream << ", invalid";
|
|
||||||
}
|
|
||||||
if (!m_enhancedLevel) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bool infoAdded = false;
|
bool infoAdded = false;
|
||||||
@@ -163,58 +179,18 @@ void FileDevice::formatInfo(ostringstream* ostream, bool verbose, bool prefix) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileDevice::formatInfoJson(ostringstream* ostream) {
|
void EnhancedCharDevice::formatInfoJson(ostringstream* ostream) const {
|
||||||
if (m_enhancedLevel) {
|
|
||||||
string ver = getEnhancedVersion();
|
string ver = getEnhancedVersion();
|
||||||
if (!ver.empty()) {
|
if (!ver.empty()) {
|
||||||
*ostream << ",\"dv\":\"" << ver << "\"";
|
*ostream << ",\"dv\":\"" << ver << "\"";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
result_t FileDevice::open() {
|
result_t EnhancedCharDevice::requestEnhancedInfo(symbol_t infoId, bool wait) {
|
||||||
close();
|
if (m_extraFatures == 0) {
|
||||||
return m_bufSize == 0 ? RESULT_ERR_DEVICE : RESULT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
result_t FileDevice::afterOpen() {
|
|
||||||
if (m_enhancedLevel) {
|
|
||||||
symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_INIT, 0x01); // extra feature: info
|
|
||||||
DEBUG_RAW_TRAFFIC("enhanced > %2.2x %2.2x", buf[0], buf[1]);
|
|
||||||
if (::write(m_fd, buf, 2) != 2) {
|
|
||||||
return RESULT_ERR_SEND;
|
|
||||||
}
|
|
||||||
if (m_listener != nullptr) {
|
|
||||||
m_listener->notifyDeviceStatus(false, "resetting");
|
|
||||||
}
|
|
||||||
m_resetRequested = true;
|
|
||||||
}
|
|
||||||
return RESULT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileDevice::close() {
|
|
||||||
if (m_fd != -1) {
|
|
||||||
::close(m_fd);
|
|
||||||
m_fd = -1;
|
|
||||||
}
|
|
||||||
m_bufLen = 0; // flush read buffer
|
|
||||||
m_extraFatures = 0; // reset state
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FileDevice::isValid() {
|
|
||||||
if (m_fd == -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (m_checkDevice) {
|
|
||||||
checkDevice();
|
|
||||||
}
|
|
||||||
return m_fd != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
result_t FileDevice::requestEnhancedInfo(symbol_t infoId) {
|
|
||||||
if (!m_enhancedLevel || m_extraFatures == 0) {
|
|
||||||
return RESULT_ERR_INVALID_ARG;
|
return RESULT_ERR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
|
if (wait) {
|
||||||
for (unsigned int i = 0; i < 4; i++) {
|
for (unsigned int i = 0; i < 4; i++) {
|
||||||
if (m_infoLen == 0) {
|
if (m_infoLen == 0) {
|
||||||
break;
|
break;
|
||||||
@@ -233,76 +209,27 @@ result_t FileDevice::requestEnhancedInfo(symbol_t infoId) {
|
|||||||
return RESULT_ERR_DUPLICATE;
|
return RESULT_ERR_DUPLICATE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (infoId == 0xff) {
|
if (infoId == 0xff) {
|
||||||
// just waited for completion
|
// just waited for completion
|
||||||
return RESULT_OK;
|
return RESULT_OK;
|
||||||
}
|
}
|
||||||
return sendSequence(sid_info, &infoId, 1);
|
uint8_t buf[] = makeEnhancedSequence(ENH_REQ_INFO, infoId);
|
||||||
}
|
result_t result = m_transport->write(buf, 2);
|
||||||
|
if (result == RESULT_OK) {
|
||||||
result_t FileDevice::sendSequence(SequenceId id, const uint8_t* data, size_t len) {
|
m_infoBuf[0] = infoId;
|
||||||
if (!isValid()) {
|
|
||||||
return RESULT_ERR_DEVICE;
|
|
||||||
}
|
|
||||||
if (m_enhancedLevel >= el_basic && id == sid_info && (m_extraFatures&0x01) != 0) {
|
|
||||||
// request is supported
|
|
||||||
} else {
|
|
||||||
return RESULT_ERR_NOTFOUND; // not supported
|
|
||||||
}
|
|
||||||
size_t pos = (1+len)*2;
|
|
||||||
if (pos > m_sendBufSize) {
|
|
||||||
m_sendBuf = static_cast<uint8_t*>(realloc(m_sendBuf, pos));
|
|
||||||
if (!m_sendBuf) {
|
|
||||||
m_sendBufSize = 0;
|
|
||||||
return RESULT_ERR_DEVICE;
|
|
||||||
}
|
|
||||||
m_sendBufSize = pos;
|
|
||||||
}
|
|
||||||
pos = 0;
|
|
||||||
uint8_t cmd = id == sid_info ? ENH_REQ_INFO : 0;
|
|
||||||
if (cmd == 0) {
|
|
||||||
return RESULT_ERR_NOTFOUND; // not supported
|
|
||||||
}
|
|
||||||
if (cmd & 0x8) {
|
|
||||||
// sequence-encoded
|
|
||||||
m_sendBuf[pos++] = makeEnhancedByte1(cmd, len);
|
|
||||||
m_sendBuf[pos++] = makeEnhancedByte2(cmd, len);
|
|
||||||
cmd = static_cast<uint8_t>(cmd & ~0x8);
|
|
||||||
} else {
|
|
||||||
// direct-encoded
|
|
||||||
uint8_t val = data ? *data : 0;
|
|
||||||
m_sendBuf[pos++] = makeEnhancedByte1(cmd, val);
|
|
||||||
m_sendBuf[pos++] = makeEnhancedByte2(cmd, val);
|
|
||||||
if (id == sid_info) {
|
|
||||||
m_infoBuf[0] = val;
|
|
||||||
m_infoLen = 0;
|
|
||||||
}
|
|
||||||
if (len > 0) {
|
|
||||||
len--;
|
|
||||||
data++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (len > 0) {
|
|
||||||
m_sendBuf[pos++] = makeEnhancedByte1(cmd, *data);
|
|
||||||
m_sendBuf[pos++] = makeEnhancedByte2(cmd, *data);
|
|
||||||
data++;
|
|
||||||
len--;
|
|
||||||
}
|
|
||||||
// DEBUG_RAW_TRAFFIC("enhanced > %2.2x %2.2x", buf[0], buf[1]);
|
|
||||||
ssize_t sent = ::write(m_fd, m_sendBuf, pos);
|
|
||||||
if (sent < 0 || static_cast<unsigned>(sent) != pos) {
|
|
||||||
return RESULT_ERR_DEVICE;
|
|
||||||
}
|
|
||||||
if (id == sid_info) {
|
|
||||||
m_infoLen = 1;
|
m_infoLen = 1;
|
||||||
m_infoPos = 1;
|
m_infoPos = 1;
|
||||||
time(&m_infoReqTime);
|
time(&m_infoReqTime);
|
||||||
|
} else {
|
||||||
|
m_infoLen = 0;
|
||||||
|
m_infoPos = 0;
|
||||||
}
|
}
|
||||||
return RESULT_OK;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
string FileDevice::getEnhancedInfos() {
|
string EnhancedCharDevice::getEnhancedInfos() {
|
||||||
if (!m_enhancedLevel || m_extraFatures == 0) {
|
if (m_extraFatures == 0) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
result_t res;
|
result_t res;
|
||||||
@@ -348,359 +275,159 @@ string FileDevice::getEnhancedInfos() {
|
|||||||
+ m_enhInfoBusVoltage;
|
+ m_enhInfoBusVoltage;
|
||||||
}
|
}
|
||||||
|
|
||||||
result_t FileDevice::send(symbol_t value) {
|
result_t EnhancedCharDevice::send(symbol_t value) {
|
||||||
if (!isValid()) {
|
uint8_t buf[] = makeEnhancedSequence(ENH_REQ_SEND, value);
|
||||||
return RESULT_ERR_DEVICE;
|
result_t result = m_transport->write(buf, 2);
|
||||||
|
if (result == RESULT_OK && m_listener != nullptr) {
|
||||||
|
m_listener->notifyDeviceData(&value, 1, false);
|
||||||
}
|
}
|
||||||
if (!write(value)) {
|
return result;
|
||||||
return RESULT_ERR_SEND;
|
|
||||||
}
|
|
||||||
if (m_listener != nullptr) {
|
|
||||||
m_listener->notifyDeviceData(value, false);
|
|
||||||
}
|
|
||||||
return RESULT_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
result_t EnhancedCharDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) {
|
||||||
* the maximum duration in milliseconds to wait for an enhanced sequence to complete after the first part was already
|
|
||||||
* retrieved (3ms rounded up to the next 10ms): 2* (Start+8Bit+Stop+Extra @ 9600Bd)
|
|
||||||
*/
|
|
||||||
#define ENHANCED_COMPLETE_WAIT_DURATION 10
|
|
||||||
|
|
||||||
|
|
||||||
bool FileDevice::cancelRunningArbitration(ArbitrationState* arbitrationState) {
|
|
||||||
if (m_enhancedLevel && m_arbitrationMaster != SYN) {
|
|
||||||
*arbitrationState = as_error;
|
|
||||||
m_arbitrationMaster = SYN;
|
|
||||||
m_arbitrationCheck = 0;
|
|
||||||
write(SYN, true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (m_enhancedLevel || m_arbitrationMaster == SYN) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*arbitrationState = as_error;
|
|
||||||
m_arbitrationMaster = SYN;
|
|
||||||
m_arbitrationCheck = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
result_t FileDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) {
|
|
||||||
if (m_arbitrationMaster != SYN) {
|
if (m_arbitrationMaster != SYN) {
|
||||||
*arbitrationState = as_running;
|
*arbitrationState = as_running;
|
||||||
}
|
}
|
||||||
if (!isValid()) {
|
uint64_t until = timeout == 0 ? 0 : clockGetMillis() + timeout + m_transport->getLatency();
|
||||||
cancelRunningArbitration(arbitrationState);
|
const uint8_t* data = nullptr;
|
||||||
return RESULT_ERR_DEVICE;
|
size_t len = 0;
|
||||||
}
|
result_t result;
|
||||||
bool repeated = false;
|
|
||||||
timeout += m_latency;
|
|
||||||
uint64_t until = clockGetMillis() + timeout;
|
|
||||||
do {
|
do {
|
||||||
bool isAvailable = available();
|
result = m_transport->read(timeout, &data, &len);
|
||||||
if (!isAvailable && timeout > 0) {
|
if (result == RESULT_OK) {
|
||||||
int ret;
|
result = handleEnhancedBufferedData(data, len, value, arbitrationState);
|
||||||
struct timespec tdiff;
|
if (result >= RESULT_OK) {
|
||||||
|
break;
|
||||||
// set select timeout
|
|
||||||
tdiff.tv_sec = timeout/1000;
|
|
||||||
tdiff.tv_nsec = (timeout%1000)*1000000;
|
|
||||||
|
|
||||||
#ifdef HAVE_PPOLL
|
|
||||||
nfds_t nfds = 1;
|
|
||||||
struct pollfd fds[nfds];
|
|
||||||
|
|
||||||
memset(fds, 0, sizeof(fds));
|
|
||||||
|
|
||||||
fds[0].fd = m_fd;
|
|
||||||
fds[0].events = POLLIN | POLLERR | POLLHUP | POLLRDHUP;
|
|
||||||
ret = ppoll(fds, nfds, &tdiff, nullptr);
|
|
||||||
if (ret >= 0 && fds[0].revents & (POLLERR | POLLHUP | POLLRDHUP)) {
|
|
||||||
ret = -1;
|
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
#ifdef HAVE_PSELECT
|
|
||||||
fd_set readfds, exceptfds;
|
|
||||||
|
|
||||||
FD_ZERO(&readfds);
|
|
||||||
FD_ZERO(&exceptfds);
|
|
||||||
FD_SET(m_fd, &readfds);
|
|
||||||
|
|
||||||
ret = pselect(m_fd + 1, &readfds, nullptr, &exceptfds, &tdiff, nullptr);
|
|
||||||
if (ret >= 1 && FD_ISSET(m_fd, &exceptfds)) {
|
|
||||||
ret = -1;
|
|
||||||
}
|
}
|
||||||
#else
|
if (result != RESULT_ERR_TIMEOUT) {
|
||||||
ret = 1; // ignore timeout if neither ppoll nor pselect are available
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
if (ret == -1) {
|
|
||||||
DEBUG_RAW_TRAFFIC("poll error %d", errno);
|
|
||||||
close();
|
|
||||||
cancelRunningArbitration(arbitrationState);
|
cancelRunningArbitration(arbitrationState);
|
||||||
return RESULT_ERR_DEVICE;
|
return result;
|
||||||
}
|
}
|
||||||
if (ret == 0) {
|
if (timeout == 0) {
|
||||||
return RESULT_ERR_TIMEOUT;
|
break;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// directly read byte from device
|
|
||||||
bool incomplete = false;
|
|
||||||
if (read(value, isAvailable, arbitrationState, &incomplete)) {
|
|
||||||
break; // don't repeat on successful read
|
|
||||||
}
|
|
||||||
if (!isAvailable && incomplete && !repeated) {
|
|
||||||
// for a two-byte transfer another poll is needed
|
|
||||||
repeated = true;
|
|
||||||
timeout = m_latency+ENHANCED_COMPLETE_WAIT_DURATION;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
uint64_t now = clockGetMillis();
|
uint64_t now = clockGetMillis();
|
||||||
if (now >= until) {
|
if (timeout == 0 || now >= until) {
|
||||||
return RESULT_ERR_TIMEOUT;
|
break;
|
||||||
}
|
}
|
||||||
timeout = static_cast<unsigned>(until - now);
|
timeout = static_cast<unsigned>(until - now);
|
||||||
} while (true);
|
} while (true);
|
||||||
if (m_enhancedLevel || *value != SYN || m_arbitrationMaster == SYN || m_arbitrationCheck) {
|
return result;
|
||||||
if (m_listener != nullptr) {
|
|
||||||
m_listener->notifyDeviceData(*value, true);
|
|
||||||
}
|
|
||||||
if (!m_enhancedLevel && m_arbitrationMaster != SYN) {
|
|
||||||
if (m_arbitrationCheck) {
|
|
||||||
*arbitrationState = *value == m_arbitrationMaster ? as_won : as_lost;
|
|
||||||
m_arbitrationMaster = SYN;
|
|
||||||
m_arbitrationCheck = 0;
|
|
||||||
} else {
|
|
||||||
*arbitrationState = m_arbitrationMaster == SYN ? as_none : as_start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return RESULT_OK;
|
|
||||||
}
|
|
||||||
// non-enhanced: arbitration executed by ebusd itself
|
|
||||||
bool wrote = write(m_arbitrationMaster); // send as fast as possible
|
|
||||||
if (m_listener != nullptr) {
|
|
||||||
m_listener->notifyDeviceData(*value, true);
|
|
||||||
}
|
|
||||||
if (!wrote) {
|
|
||||||
cancelRunningArbitration(arbitrationState);
|
|
||||||
return RESULT_OK;
|
|
||||||
}
|
|
||||||
if (m_listener != nullptr) {
|
|
||||||
m_listener->notifyDeviceData(m_arbitrationMaster, false);
|
|
||||||
}
|
|
||||||
m_arbitrationCheck = 1;
|
|
||||||
*arbitrationState = as_running;
|
|
||||||
return RESULT_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result_t FileDevice::startArbitration(symbol_t masterAddress) {
|
result_t EnhancedCharDevice::startArbitration(symbol_t masterAddress) {
|
||||||
if (m_arbitrationCheck) {
|
if (m_arbitrationCheck) {
|
||||||
if (masterAddress != SYN) {
|
if (masterAddress != SYN) {
|
||||||
return RESULT_ERR_ARB_RUNNING; // should not occur
|
return RESULT_ERR_ARB_RUNNING; // should not occur
|
||||||
}
|
}
|
||||||
m_arbitrationCheck = 0;
|
if (!cancelRunningArbitration(nullptr)) {
|
||||||
m_arbitrationMaster = SYN;
|
|
||||||
if (m_enhancedLevel) {
|
|
||||||
// cancel running arbitration
|
|
||||||
if (!write(SYN, true)) {
|
|
||||||
return RESULT_ERR_SEND;
|
return RESULT_ERR_SEND;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return RESULT_OK;
|
return RESULT_OK;
|
||||||
}
|
}
|
||||||
m_arbitrationMaster = masterAddress;
|
m_arbitrationMaster = masterAddress;
|
||||||
if (m_enhancedLevel && masterAddress != SYN) {
|
if (masterAddress != SYN) {
|
||||||
if (!write(masterAddress, true)) {
|
uint8_t buf[] = makeEnhancedSequence(ENH_REQ_START, masterAddress);
|
||||||
|
result_t result = m_transport->write(buf, 2);
|
||||||
|
if (result != RESULT_OK) {
|
||||||
m_arbitrationMaster = SYN;
|
m_arbitrationMaster = SYN;
|
||||||
return RESULT_ERR_SEND;
|
return result;
|
||||||
}
|
}
|
||||||
m_arbitrationCheck = 1;
|
m_arbitrationCheck = 1;
|
||||||
}
|
}
|
||||||
return RESULT_OK;
|
return RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileDevice::write(symbol_t value, bool startArbitration) {
|
bool EnhancedCharDevice::cancelRunningArbitration(ArbitrationState* arbitrationState) {
|
||||||
if (m_enhancedLevel) {
|
if (!CharDevice::cancelRunningArbitration(arbitrationState)) {
|
||||||
symbol_t buf[2] = makeEnhancedSequence(startArbitration ? ENH_REQ_START : ENH_REQ_SEND, value);
|
return false;
|
||||||
DEBUG_RAW_TRAFFIC("enhanced > %2.2x %2.2x", buf[0], buf[1]);
|
|
||||||
return ::write(m_fd, buf, 2) == 2;
|
|
||||||
}
|
}
|
||||||
DEBUG_RAW_TRAFFIC("> %2.2x", value);
|
symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_START, SYN);
|
||||||
#ifdef SIMULATE_NON_WRITABILITY
|
return m_transport->write(buf, 2) == RESULT_OK;
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
return ::write(m_fd, &value, 1) == 1;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileDevice::available() {
|
result_t EnhancedCharDevice::notifyTransportStatus(bool opened) {
|
||||||
if (m_bufLen <= 0) {
|
result_t result = CharDevice::notifyTransportStatus(opened); // always OK
|
||||||
return false;
|
if (opened) {
|
||||||
}
|
symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_INIT, 0x01); // extra feature: info
|
||||||
if (!m_enhancedLevel) {
|
result = m_transport->write(buf, 2);
|
||||||
return true;
|
if (result != RESULT_OK) {
|
||||||
}
|
return result;
|
||||||
// peek into the received enhanced proto bytes to determine received bus symbol availability
|
|
||||||
for (size_t pos = 0; pos < m_bufLen; pos++) {
|
|
||||||
symbol_t ch = m_buffer[(pos+m_bufPos)%m_bufSize];
|
|
||||||
if (!(ch&ENH_BYTE_FLAG)) {
|
|
||||||
DEBUG_RAW_TRAFFIC("avail direct @%d+%d %2.2x", m_bufPos, pos, ch);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if ((ch&ENH_BYTE_MASK) == ENH_BYTE1) {
|
|
||||||
if (pos+1 >= m_bufLen) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
symbol_t cmd = (ch >> 2)&0xf;
|
|
||||||
// peek into next byte to check if enhanced sequence is ok
|
|
||||||
ch = m_buffer[(pos+m_bufPos+1)%m_bufSize];
|
|
||||||
if (!(ch&ENH_BYTE_FLAG) || (ch&ENH_BYTE_MASK) != ENH_BYTE2) {
|
|
||||||
DEBUG_RAW_TRAFFIC("avail enhanced following bad @%d+%d %2.2x %2.2x", m_bufPos, pos,
|
|
||||||
m_buffer[(pos+m_bufPos)%m_bufSize], ch);
|
|
||||||
if (m_listener != nullptr) {
|
|
||||||
m_listener->notifyDeviceStatus(true, "unexpected available enhanced following byte 1");
|
|
||||||
}
|
|
||||||
// drop first byte of invalid sequence
|
|
||||||
m_bufPos = (m_bufPos + 1) % m_bufSize;
|
|
||||||
m_bufLen--;
|
|
||||||
pos--; // check same pos again
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cmd == ENH_RES_RECEIVED || cmd == ENH_RES_STARTED || cmd == ENH_RES_FAILED) {
|
|
||||||
// found a sequence that yields in available bus byte
|
|
||||||
DEBUG_RAW_TRAFFIC("avail enhanced @%d+%d %2.2x %2.2x", m_bufPos, pos, m_buffer[(pos+m_bufPos)%m_bufSize], ch);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
DEBUG_RAW_TRAFFIC("avail enhanced skip cmd %d @%d+%d %2.2x", cmd, m_bufPos, pos, ch);
|
|
||||||
pos++; // skip enhanced sequence of 2 bytes
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
DEBUG_RAW_TRAFFIC("avail enhanced bad @%d+%d %2.2x", m_bufPos, pos, ch);
|
|
||||||
if (m_listener != nullptr) {
|
|
||||||
m_listener->notifyDeviceStatus(true, "unexpected available enhanced byte 2");
|
|
||||||
}
|
|
||||||
// skip byte from erroneous protocol
|
|
||||||
m_bufPos = (m_bufPos+1)%m_bufSize;
|
|
||||||
m_bufLen--;
|
|
||||||
pos--; // check byte 2 again from scratch and allow as byte 1
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FileDevice::read(symbol_t* value, bool isAvailable, ArbitrationState* arbitrationState, bool* incomplete) {
|
|
||||||
if (!isAvailable) {
|
|
||||||
if (m_bufLen > 0 && m_bufPos != 0) {
|
|
||||||
if (m_bufLen > m_bufSize / 2) {
|
|
||||||
// more than half of input buffer consumed is taken as signal that ebusd is too slow
|
|
||||||
m_bufLen = 0;
|
|
||||||
if (m_listener != nullptr) {
|
|
||||||
m_listener->notifyDeviceStatus(true, "buffer overflow");
|
|
||||||
}
|
}
|
||||||
|
m_resetRequested = true;
|
||||||
} else {
|
} else {
|
||||||
size_t tail;
|
// reset state
|
||||||
if (m_bufPos+m_bufLen > m_bufSize) {
|
m_extraFatures = 0;
|
||||||
// move wrapped tail away
|
m_infoLen = 0;
|
||||||
tail = (m_bufPos+m_bufLen) % m_bufSize;
|
m_arbitrationMaster = SYN;
|
||||||
size_t head = m_bufLen-tail;
|
m_arbitrationCheck = 0;
|
||||||
memmove(m_buffer+head, m_buffer, tail);
|
|
||||||
DEBUG_RAW_TRAFFIC("move tail %d @0 to @%d", tail, head);
|
|
||||||
} else {
|
|
||||||
tail = 0;
|
|
||||||
}
|
}
|
||||||
// move head to first position
|
return result;
|
||||||
memmove(m_buffer, m_buffer + m_bufPos, m_bufLen - tail);
|
|
||||||
DEBUG_RAW_TRAFFIC("move head %d @%d to 0", m_bufLen - tail, m_bufPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_bufPos = 0;
|
|
||||||
// fill up the buffer
|
|
||||||
ssize_t size = ::read(m_fd, m_buffer + m_bufLen, m_bufSize - m_bufLen);
|
|
||||||
if (size <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#ifdef DEBUG_RAW_TRAFFIC_ITEM
|
|
||||||
DEBUG_RAW_TRAFFIC_HEAD("%d+%d <", m_bufLen, size);
|
|
||||||
for (int pos=0; pos < size; pos++) {
|
|
||||||
DEBUG_RAW_TRAFFIC_ITEM(" %2.2x", m_buffer[(m_bufLen+pos)%m_bufSize]);
|
|
||||||
}
|
|
||||||
DEBUG_RAW_TRAFFIC_FINAL();
|
|
||||||
#endif
|
|
||||||
m_bufLen += size;
|
|
||||||
}
|
|
||||||
if (m_enhancedLevel) {
|
|
||||||
if (handleEnhancedBufferedData(value, arbitrationState)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!available()) {
|
|
||||||
if (incomplete) {
|
|
||||||
*incomplete = m_enhancedLevel && m_bufLen > 0;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!m_enhancedLevel) {
|
|
||||||
*value = m_buffer[m_bufPos];
|
|
||||||
m_bufPos = (m_bufPos+1)%m_bufSize;
|
|
||||||
m_bufLen--;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return handleEnhancedBufferedData(value, arbitrationState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbitrationState) {
|
|
||||||
while (m_bufLen > 0) {
|
result_t EnhancedCharDevice::handleEnhancedBufferedData(const uint8_t* data, size_t len,
|
||||||
symbol_t ch = m_buffer[m_bufPos];
|
symbol_t* value, ArbitrationState* arbitrationState) {
|
||||||
|
bool valueSet = false;
|
||||||
|
bool sent = false;
|
||||||
|
bool more = false;
|
||||||
|
size_t pos;
|
||||||
|
for (pos = 0; pos < len; pos++) {
|
||||||
|
symbol_t ch = data[pos];
|
||||||
if (!(ch&ENH_BYTE_FLAG)) {
|
if (!(ch&ENH_BYTE_FLAG)) {
|
||||||
|
if (valueSet) {
|
||||||
|
more = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
*value = ch;
|
*value = ch;
|
||||||
m_bufPos = (m_bufPos+1)%m_bufSize;
|
valueSet = true;
|
||||||
m_bufLen--;
|
continue;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
uint8_t kind = ch&ENH_BYTE_MASK;
|
uint8_t kind = ch&ENH_BYTE_MASK;
|
||||||
if (kind == ENH_BYTE1 && m_bufLen < 2) {
|
if (kind == ENH_BYTE1 && len < pos + 2) {
|
||||||
return false; // transfer not complete yet
|
break; // transfer not complete yet
|
||||||
}
|
}
|
||||||
m_bufPos = (m_bufPos+1)%m_bufSize;
|
|
||||||
m_bufLen--;
|
|
||||||
if (kind == ENH_BYTE2) {
|
if (kind == ENH_BYTE2) {
|
||||||
if (m_listener != nullptr) {
|
if (m_listener != nullptr) {
|
||||||
m_listener->notifyDeviceStatus(true, "unexpected enhanced byte 2");
|
m_listener->notifyDeviceStatus(true, "unexpected enhanced byte 2");
|
||||||
}
|
}
|
||||||
return false;
|
continue;
|
||||||
}
|
}
|
||||||
// kind is ENH_BYTE1
|
// kind is ENH_BYTE1
|
||||||
symbol_t ch2 = m_buffer[m_bufPos];
|
pos++;
|
||||||
m_bufPos = (m_bufPos + 1) % m_bufSize;
|
symbol_t ch2 = data[pos];
|
||||||
m_bufLen--;
|
|
||||||
if ((ch2 & ENH_BYTE_MASK) != ENH_BYTE2) {
|
if ((ch2 & ENH_BYTE_MASK) != ENH_BYTE2) {
|
||||||
if (m_listener != nullptr) {
|
if (m_listener != nullptr) {
|
||||||
m_listener->notifyDeviceStatus(true, "missing enhanced byte 2");
|
m_listener->notifyDeviceStatus(true, "missing enhanced byte 2");
|
||||||
}
|
}
|
||||||
return false;
|
continue;
|
||||||
}
|
}
|
||||||
symbol_t data = (symbol_t)(((ch&0x03) << 6) | (ch2&0x3f));
|
symbol_t data = (symbol_t)(((ch&0x03) << 6) | (ch2&0x3f));
|
||||||
symbol_t cmd = (ch >> 2)&0xf;
|
symbol_t cmd = (ch >> 2)&0xf;
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case ENH_RES_STARTED:
|
case ENH_RES_STARTED:
|
||||||
*arbitrationState = as_won;
|
|
||||||
if (m_listener != NULL) {
|
|
||||||
m_listener->notifyDeviceData(data, false);
|
|
||||||
}
|
|
||||||
m_arbitrationMaster = SYN;
|
|
||||||
m_arbitrationCheck = 0;
|
|
||||||
*value = data;
|
|
||||||
return true;
|
|
||||||
case ENH_RES_FAILED:
|
case ENH_RES_FAILED:
|
||||||
*arbitrationState = as_lost;
|
if (valueSet) {
|
||||||
if (m_listener != NULL) {
|
more = true;
|
||||||
m_listener->notifyDeviceData(m_arbitrationMaster, false);
|
pos--; // keep ENH_BYTE1 for later run
|
||||||
|
len = 0; // abort outer loop
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
sent = cmd == ENH_RES_STARTED;
|
||||||
|
*arbitrationState = sent ? as_won : as_lost;
|
||||||
m_arbitrationMaster = SYN;
|
m_arbitrationMaster = SYN;
|
||||||
m_arbitrationCheck = 0;
|
m_arbitrationCheck = 0;
|
||||||
*value = data;
|
*value = data;
|
||||||
return true;
|
valueSet = true;
|
||||||
|
break;
|
||||||
case ENH_RES_RECEIVED:
|
case ENH_RES_RECEIVED:
|
||||||
|
if (valueSet) {
|
||||||
|
more = true;
|
||||||
|
pos--; // keep ENH_BYTE1 for later run
|
||||||
|
len = 0; // abort outer loop
|
||||||
|
break;
|
||||||
|
}
|
||||||
*value = data;
|
*value = data;
|
||||||
if (data == SYN && *arbitrationState == as_running && m_arbitrationCheck) {
|
if (data == SYN && *arbitrationState == as_running && m_arbitrationCheck) {
|
||||||
if (m_arbitrationCheck < 3) { // wait for three SYN symbols before switching to timeout
|
if (m_arbitrationCheck < 3) { // wait for three SYN symbols before switching to timeout
|
||||||
@@ -711,7 +438,8 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a
|
|||||||
m_arbitrationCheck = 0;
|
m_arbitrationCheck = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
valueSet = true;
|
||||||
|
break;
|
||||||
case ENH_RES_RESETTED:
|
case ENH_RES_RESETTED:
|
||||||
if (*arbitrationState != as_none) {
|
if (*arbitrationState != as_none) {
|
||||||
*arbitrationState = as_error;
|
*arbitrationState = as_error;
|
||||||
@@ -723,23 +451,24 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a
|
|||||||
m_enhInfoBusVoltage = "";
|
m_enhInfoBusVoltage = "";
|
||||||
m_infoLen = 0;
|
m_infoLen = 0;
|
||||||
m_extraFatures = data;
|
m_extraFatures = data;
|
||||||
if (m_resetRequested) {
|
|
||||||
m_resetRequested = false;
|
|
||||||
if (m_extraFatures&0x01) {
|
|
||||||
sendSequence(sid_info); // request version, ignore result
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
close(); // on self-reset of device close and reopen it to have a clean startup
|
|
||||||
cancelRunningArbitration(arbitrationState);
|
|
||||||
}
|
|
||||||
if (m_listener != nullptr) {
|
if (m_listener != nullptr) {
|
||||||
m_listener->notifyDeviceStatus(false, (m_extraFatures&0x01) ? "reset, supports info" : "reset");
|
m_listener->notifyDeviceStatus(false, (m_extraFatures&0x01) ? "reset, supports info" : "reset");
|
||||||
}
|
}
|
||||||
|
if (m_resetRequested) {
|
||||||
|
m_resetRequested = false;
|
||||||
|
if (m_extraFatures&0x01) {
|
||||||
|
requestEnhancedInfo(0, false); // request version, ignore result
|
||||||
|
}
|
||||||
|
valueSet = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_transport->close(); // on self-reset of device close and reopen it to have a clean startup
|
||||||
|
cancelRunningArbitration(arbitrationState);
|
||||||
break;
|
break;
|
||||||
case ENH_RES_INFO:
|
case ENH_RES_INFO:
|
||||||
if (m_infoLen == 1) {
|
if (m_infoLen == 1) {
|
||||||
m_infoLen = data+1;
|
m_infoLen = data+1;
|
||||||
} else if (m_infoPos < m_infoLen && m_infoPos < sizeof(m_infoBuf)) {
|
} else if (m_infoLen && m_infoPos < m_infoLen && m_infoPos < sizeof(m_infoBuf)) {
|
||||||
m_infoBuf[m_infoPos++] = data;
|
m_infoBuf[m_infoPos++] = data;
|
||||||
if (m_infoPos >= m_infoLen) {
|
if (m_infoPos >= m_infoLen) {
|
||||||
notifyInfoRetrieved();
|
notifyInfoRetrieved();
|
||||||
@@ -778,13 +507,21 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a
|
|||||||
string str = stream.str();
|
string str = stream.str();
|
||||||
m_listener->notifyDeviceStatus(true, str.c_str());
|
m_listener->notifyDeviceStatus(true, str.c_str());
|
||||||
}
|
}
|
||||||
return false;
|
len = 0; // abort outer loop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (len == 0) {
|
||||||
|
break; // abort received
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
m_transport->readConsumed(pos);
|
||||||
|
if (valueSet && m_listener != nullptr) {
|
||||||
|
m_listener->notifyDeviceData(value, 1, !sent);
|
||||||
|
}
|
||||||
|
return more ? RESULT_CONTINUE : valueSet ? RESULT_OK : RESULT_ERR_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileDevice::notifyInfoRetrieved() {
|
void EnhancedCharDevice::notifyInfoRetrieved() {
|
||||||
symbol_t id = m_infoBuf[0];
|
symbol_t id = m_infoBuf[0];
|
||||||
symbol_t* data = m_infoBuf+1;
|
symbol_t* data = m_infoBuf+1;
|
||||||
size_t len = m_infoLen-1;
|
size_t len = m_infoLen-1;
|
||||||
@@ -883,136 +620,4 @@ void FileDevice::notifyInfoRetrieved() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
result_t SerialDevice::open() {
|
|
||||||
result_t result = FileDevice::open();
|
|
||||||
if (result != RESULT_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
struct termios newSettings;
|
|
||||||
|
|
||||||
// open file descriptor
|
|
||||||
m_fd = ::open(m_name, O_RDWR | O_NOCTTY | O_NDELAY);
|
|
||||||
|
|
||||||
if (m_fd < 0) {
|
|
||||||
return RESULT_ERR_NOTFOUND;
|
|
||||||
}
|
|
||||||
if (isatty(m_fd) == 0) {
|
|
||||||
close();
|
|
||||||
return RESULT_ERR_NOTFOUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flock(m_fd, LOCK_EX|LOCK_NB) != 0) {
|
|
||||||
close();
|
|
||||||
return RESULT_ERR_DEVICE;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef HAVE_LINUX_SERIAL
|
|
||||||
struct serial_struct serial;
|
|
||||||
if (ioctl(m_fd, TIOCGSERIAL, &serial) == 0) {
|
|
||||||
serial.flags |= ASYNC_LOW_LATENCY;
|
|
||||||
ioctl(m_fd, TIOCSSERIAL, &serial);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef HAVE_FREEBSD_UFTDI
|
|
||||||
int param = 0;
|
|
||||||
// flush tx/rx and set low latency on uftdi device
|
|
||||||
if (ioctl(m_fd, UFTDIIOC_GET_LATENCY, ¶m) == 0) {
|
|
||||||
ioctl(m_fd, UFTDIIOC_RESET_IO, ¶m);
|
|
||||||
param = 1;
|
|
||||||
ioctl(m_fd, UFTDIIOC_SET_LATENCY, ¶m);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// save current settings
|
|
||||||
tcgetattr(m_fd, &m_oldSettings);
|
|
||||||
|
|
||||||
// create new settings
|
|
||||||
memset(&newSettings, 0, sizeof(newSettings));
|
|
||||||
|
|
||||||
#ifdef HAVE_CFSETSPEED
|
|
||||||
cfsetspeed(&newSettings, m_enhancedLevel ? (m_enhancedLevel >= el_speed ? B115200 : B9600) : B2400);
|
|
||||||
#else
|
|
||||||
cfsetispeed(&newSettings, m_enhancedLevel ? (m_enhancedLevel >= el_speed ? B115200 : B9600) : B2400);
|
|
||||||
cfsetospeed(&newSettings, m_enhancedLevel ? (m_enhancedLevel >= el_speed ? B115200 : B9600) : B2400);
|
|
||||||
#endif
|
|
||||||
newSettings.c_cflag |= (CS8 | CLOCAL | CREAD);
|
|
||||||
newSettings.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // non-canonical mode
|
|
||||||
newSettings.c_iflag |= IGNPAR; // ignore parity errors
|
|
||||||
newSettings.c_oflag &= ~OPOST;
|
|
||||||
|
|
||||||
// non-canonical mode: read() blocks until at least one byte is available
|
|
||||||
newSettings.c_cc[VMIN] = 1;
|
|
||||||
newSettings.c_cc[VTIME] = 0;
|
|
||||||
|
|
||||||
// empty device buffer
|
|
||||||
tcflush(m_fd, TCIFLUSH);
|
|
||||||
|
|
||||||
// activate new settings of serial device
|
|
||||||
if (tcsetattr(m_fd, TCSANOW, &newSettings)) {
|
|
||||||
close();
|
|
||||||
return RESULT_ERR_DEVICE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set serial device into blocking mode
|
|
||||||
fcntl(m_fd, F_SETFL, fcntl(m_fd, F_GETFL) & ~O_NONBLOCK);
|
|
||||||
|
|
||||||
return afterOpen();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SerialDevice::close() {
|
|
||||||
if (m_fd != -1) {
|
|
||||||
// empty device buffer
|
|
||||||
tcflush(m_fd, TCIOFLUSH);
|
|
||||||
|
|
||||||
// restore previous settings of the device
|
|
||||||
tcsetattr(m_fd, TCSANOW, &m_oldSettings);
|
|
||||||
}
|
|
||||||
FileDevice::close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SerialDevice::checkDevice() {
|
|
||||||
int cnt;
|
|
||||||
if (ioctl(m_fd, FIONREAD, &cnt) == -1) {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result_t NetworkDevice::open() {
|
|
||||||
result_t result = FileDevice::open();
|
|
||||||
if (result != RESULT_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
m_fd = socketConnect(m_hostOrIp, m_port, m_udp, nullptr, 5, 2); // wait up to 5 seconds for established connection
|
|
||||||
if (m_fd < 0) {
|
|
||||||
return RESULT_ERR_GENERIC_IO;
|
|
||||||
}
|
|
||||||
if (!m_udp) {
|
|
||||||
usleep(25000); // wait 25ms for potential initial garbage
|
|
||||||
}
|
|
||||||
int cnt;
|
|
||||||
symbol_t buf[MTU];
|
|
||||||
int ioerr;
|
|
||||||
while ((ioerr=ioctl(m_fd, FIONREAD, &cnt)) >= 0 && cnt > 1) {
|
|
||||||
// skip buffered input
|
|
||||||
ssize_t read = ::read(m_fd, &buf, MTU);
|
|
||||||
if (read <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ioerr < 0) {
|
|
||||||
close();
|
|
||||||
return RESULT_ERR_GENERIC_IO;
|
|
||||||
}
|
|
||||||
return afterOpen();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkDevice::checkDevice() {
|
|
||||||
int cnt;
|
|
||||||
if (ioctl(m_fd, FIONREAD, &cnt) < 0) {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ebusd
|
} // namespace ebusd
|
||||||
|
|||||||
+118
-298
@@ -19,15 +19,9 @@
|
|||||||
#ifndef LIB_EBUS_DEVICE_H_
|
#ifndef LIB_EBUS_DEVICE_H_
|
||||||
#define LIB_EBUS_DEVICE_H_
|
#define LIB_EBUS_DEVICE_H_
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <termios.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "lib/ebus/result.h"
|
#include "lib/ebus/result.h"
|
||||||
|
#include "lib/ebus/transport.h"
|
||||||
#include "lib/ebus/symbol.h"
|
#include "lib/ebus/symbol.h"
|
||||||
|
|
||||||
namespace ebusd {
|
namespace ebusd {
|
||||||
@@ -35,25 +29,12 @@ namespace ebusd {
|
|||||||
/** @file lib/ebus/device.h
|
/** @file lib/ebus/device.h
|
||||||
* Classes providing access to the eBUS.
|
* Classes providing access to the eBUS.
|
||||||
*
|
*
|
||||||
* A @a Device is either a @a SerialDevice directly connected to a local tty
|
* A @a Device allows to send and receive data to/from a local or remote eBUS
|
||||||
* port or a remote @a NetworkDevice handled via a TCP socket. It allows to
|
* device while optionally dumping the data to a file and/or forwarding it to
|
||||||
* send and receive bytes to/from the eBUS while optionally dumping the data
|
* a logging function.
|
||||||
* to a file and/or forwarding it to a logging function.
|
* The data transport itself is handled by a @a Transport instance.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** the transfer latency of the network device [ms]. */
|
|
||||||
#define NETWORK_LATENCY_MS 30
|
|
||||||
|
|
||||||
/** the extra transfer latency to take into account for enhanced protocol. */
|
|
||||||
#define ENHANCED_LATENCY_MS 10
|
|
||||||
|
|
||||||
/** the latency of the host [ms]. */
|
|
||||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
|
||||||
#define HOST_LATENCY_MS 20
|
|
||||||
#else
|
|
||||||
#define HOST_LATENCY_MS 10
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/** the arbitration state handled by @a Device. */
|
/** the arbitration state handled by @a Device. */
|
||||||
enum ArbitrationState {
|
enum ArbitrationState {
|
||||||
as_none, //!< no arbitration in process
|
as_none, //!< no arbitration in process
|
||||||
@@ -65,11 +46,6 @@ enum ArbitrationState {
|
|||||||
as_won, //!< arbitration won
|
as_won, //!< arbitration won
|
||||||
};
|
};
|
||||||
|
|
||||||
/** the sequence IDs as handled by @a FileDevice. */
|
|
||||||
enum SequenceId {
|
|
||||||
sid_info, //!< send/receive info
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for listening to data received on/sent to a device.
|
* Interface for listening to data received on/sent to a device.
|
||||||
*/
|
*/
|
||||||
@@ -81,11 +57,12 @@ class DeviceListener {
|
|||||||
virtual ~DeviceListener() {}
|
virtual ~DeviceListener() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener method that is called when a symbol was received from/sent to eBUS.
|
* Listener method that is called when symbols were received from/sent to eBUS.
|
||||||
* @param symbol the received/sent symbol.
|
* @param data the received/sent data.
|
||||||
|
* @param len the length of received/sent data.
|
||||||
* @param received @a true on reception, @a false on sending.
|
* @param received @a true on reception, @a false on sending.
|
||||||
*/
|
*/
|
||||||
virtual void notifyDeviceData(symbol_t symbol, bool received) = 0; // abstract
|
virtual void notifyDeviceData(symbol_t* data, size_t len, bool received) = 0; // abstract
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to notify a status message from the device.
|
* Called to notify a status message from the device.
|
||||||
@@ -99,25 +76,32 @@ class DeviceListener {
|
|||||||
/**
|
/**
|
||||||
* The base class for accessing an eBUS.
|
* The base class for accessing an eBUS.
|
||||||
*/
|
*/
|
||||||
class Device {
|
class Device : public TransportListener {
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
* Construct a new instance.
|
* Construct a new instance.
|
||||||
* @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network).
|
* @param transport the @a Transport to use.
|
||||||
*/
|
*/
|
||||||
explicit Device(const char* name);
|
explicit Device(Transport* transport)
|
||||||
|
: m_transport(transport), m_listener(nullptr) {
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Destructor.
|
* Destructor.
|
||||||
*/
|
*/
|
||||||
virtual ~Device() { }
|
virtual ~Device() {
|
||||||
|
if (m_transport) {
|
||||||
|
delete m_transport;
|
||||||
|
m_transport = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the device name.
|
* Get the device name.
|
||||||
* @return the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network).
|
* @return the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network).
|
||||||
*/
|
*/
|
||||||
const char* getName() const { return m_name; }
|
const char* getName() const { return m_transport->getName(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the @a DeviceListener.
|
* Set the @a DeviceListener.
|
||||||
@@ -131,43 +115,75 @@ class Device {
|
|||||||
* @param verbose whether to add verbose infos.
|
* @param verbose whether to add verbose infos.
|
||||||
* @param prefix true for the synchronously retrievable prefix, false for the potentially asynchronous suffix.
|
* @param prefix true for the synchronously retrievable prefix, false for the potentially asynchronous suffix.
|
||||||
*/
|
*/
|
||||||
virtual void formatInfo(ostringstream* output, bool verbose, bool prefix) = 0;
|
virtual void formatInfo(ostringstream* output, bool verbose, bool prefix) {
|
||||||
|
if (prefix) {
|
||||||
|
*output << m_transport->getName() << ", " << m_transport->getTransportInfo();
|
||||||
|
} else if (!m_transport->isValid()) {
|
||||||
|
*output << ", invalid";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format device infos in JSON format.
|
* Format device infos in JSON format.
|
||||||
* @param output the @a ostringstream to append the infos to.
|
* @param output the @a ostringstream to append the infos to.
|
||||||
*/
|
*/
|
||||||
virtual void formatInfoJson(ostringstream* output) = 0;
|
virtual void formatInfoJson(ostringstream* output) const {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the device supports checking for version updates.
|
||||||
|
*/
|
||||||
|
virtual bool supportsUpdateCheck() const { return false; }
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
virtual result_t notifyTransportStatus(bool opened) {
|
||||||
|
m_listener->notifyDeviceStatus(!opened, opened ? "transport opened" : "transport closed");
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
virtual void notifyTransportMessage(bool error, const char* message) {
|
||||||
|
m_listener->notifyDeviceStatus(error, message);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the file descriptor.
|
* Open the file descriptor.
|
||||||
* @return the @a result_t code.
|
* @return the @a result_t code.
|
||||||
*/
|
*/
|
||||||
virtual result_t open() = 0;
|
virtual result_t open() { return m_transport->open(); }
|
||||||
|
|
||||||
/**
|
|
||||||
* Has to be called by subclasses upon successful opening the device as last action in open().
|
|
||||||
* @return the @a result_t code.
|
|
||||||
*/
|
|
||||||
virtual result_t afterOpen() { return RESULT_OK; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the file descriptor if opened.
|
|
||||||
*/
|
|
||||||
virtual void close() = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether the device is opened and available.
|
* Return whether the device is opened and available.
|
||||||
* @return whether the device is opened and available.
|
* @return whether the device is opened and available.
|
||||||
*/
|
*/
|
||||||
virtual bool isValid() = 0;
|
virtual bool isValid() { return m_transport->isValid(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** the @a Transport to use. */
|
||||||
|
Transport* m_transport;
|
||||||
|
|
||||||
|
/** the @a DeviceListener, or nullptr. */
|
||||||
|
DeviceListener* m_listener;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class CharDevice : public Device {
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Construct a new instance.
|
||||||
|
* @param transport the @a Transport to use.
|
||||||
|
*/
|
||||||
|
explicit CharDevice(Transport* transport)
|
||||||
|
: Device(transport), m_arbitrationMaster(SYN), m_arbitrationCheck(0) {
|
||||||
|
transport->setListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
/**
|
/**
|
||||||
* Write a single byte to the device.
|
* Write a single byte to the device.
|
||||||
* @param value the byte value to write.
|
* @param value the byte value to write.
|
||||||
* @return the @a result_t code.
|
* @return the @a result_t code.
|
||||||
*/
|
*/
|
||||||
virtual result_t send(symbol_t value) = 0;
|
virtual result_t send(symbol_t value) = 0; // abstract
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a single byte from the device.
|
* Read a single byte from the device.
|
||||||
@@ -177,7 +193,7 @@ class Device {
|
|||||||
* @a as_won, the received byte is the master address that was successfully arbitrated with.
|
* @a as_won, the received byte is the master address that was successfully arbitrated with.
|
||||||
* @return the result_t code.
|
* @return the result_t code.
|
||||||
*/
|
*/
|
||||||
virtual result_t recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) = 0;
|
virtual result_t recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) = 0; // abstract
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the arbitration with the specified master address. A subsequent request while an arbitration is currently in
|
* Start the arbitration with the specified master address. A subsequent request while an arbitration is currently in
|
||||||
@@ -185,74 +201,65 @@ class Device {
|
|||||||
* @param masterAddress the master address, or @a SYN to cancel a previous arbitration request.
|
* @param masterAddress the master address, or @a SYN to cancel a previous arbitration request.
|
||||||
* @return the result_t code.
|
* @return the result_t code.
|
||||||
*/
|
*/
|
||||||
virtual result_t startArbitration(symbol_t masterAddress) = 0;
|
virtual result_t startArbitration(symbol_t masterAddress);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether the device is currently in arbitration.
|
* Return whether the device is currently in arbitration.
|
||||||
* @return true when the device is currently in arbitration.
|
* @return true when the device is currently in arbitration.
|
||||||
*/
|
*/
|
||||||
virtual bool isArbitrating() const = 0;
|
virtual bool isArbitrating() const { return m_arbitrationMaster != SYN; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether the device supports checking for version updates.
|
* Cancel a running arbitration.
|
||||||
|
* @param arbitrationState the reference in which @a as_error is stored when cancelled.
|
||||||
|
* @return true if it was cancelled, false if not.
|
||||||
*/
|
*/
|
||||||
virtual bool supportsUpdateCheck() const = 0;
|
virtual bool cancelRunningArbitration(ArbitrationState* arbitrationState);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/** the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). */
|
/** the arbitration master address to send when in arbitration, or @a SYN. */
|
||||||
const char* m_name;
|
symbol_t m_arbitrationMaster;
|
||||||
|
|
||||||
/** the @a DeviceListener, or nullptr. */
|
/** >0 when in arbitration and the next received symbol needs to be checked against the sent master address,
|
||||||
DeviceListener* m_listener;
|
* incremented with each received SYN when arbitration was not performed as expected and needs to be stopped. */
|
||||||
|
size_t m_arbitrationCheck;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/** the possible enhanced protocol levels. */
|
class PlainCharDevice : public CharDevice {
|
||||||
enum EnhancedLevel {
|
|
||||||
el_none = 0, //!< non-enhanced
|
|
||||||
el_basic = 1, //!< enhanced basic
|
|
||||||
el_speed = 2, //!< enhanced high-speed
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The common base class for devices using a file descriptor.
|
|
||||||
*/
|
|
||||||
class FileDevice : public Device {
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
* @param latency the bus transfer latency in milliseconds.
|
|
||||||
* @param enhancedLevel whether to use the ebusd enhanced protocol.
|
|
||||||
*/
|
|
||||||
FileDevice(const char* name, bool checkDevice, unsigned int latency,
|
|
||||||
EnhancedLevel enhancedLevel);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Destructor.
|
* Construct a new instance.
|
||||||
|
* @param transport the @a Transport to use.
|
||||||
*/
|
*/
|
||||||
virtual ~FileDevice();
|
explicit PlainCharDevice(Transport* transport)
|
||||||
|
: CharDevice(transport) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
result_t send(symbol_t value) override;
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
result_t recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class EnhancedCharDevice : public CharDevice {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Construct a new instance.
|
||||||
|
* @param transport the @a Transport to use.
|
||||||
|
*/
|
||||||
|
explicit EnhancedCharDevice(Transport* transport)
|
||||||
|
: CharDevice(transport), m_resetRequested(false),
|
||||||
|
m_extraFatures(0), m_infoReqTime(0), m_infoLen(0), m_infoPos(0) {
|
||||||
|
}
|
||||||
|
|
||||||
// @copydoc
|
// @copydoc
|
||||||
void formatInfo(ostringstream* output, bool verbose, bool prefix) override;
|
void formatInfo(ostringstream* output, bool verbose, bool prefix) override;
|
||||||
|
|
||||||
// @copydoc
|
// @copydoc
|
||||||
void formatInfoJson(ostringstream* output) override;
|
void formatInfoJson(ostringstream* output) const override;
|
||||||
|
|
||||||
// @copydoc
|
|
||||||
result_t open() override;
|
|
||||||
|
|
||||||
// @copydoc
|
|
||||||
result_t afterOpen() override;
|
|
||||||
|
|
||||||
// @copydoc
|
|
||||||
void close() override;
|
|
||||||
|
|
||||||
// @copydoc
|
|
||||||
bool isValid() override;
|
|
||||||
|
|
||||||
// @copydoc
|
// @copydoc
|
||||||
result_t send(symbol_t value) override;
|
result_t send(symbol_t value) override;
|
||||||
@@ -264,50 +271,19 @@ class FileDevice : public Device {
|
|||||||
result_t startArbitration(symbol_t masterAddress) override;
|
result_t startArbitration(symbol_t masterAddress) override;
|
||||||
|
|
||||||
// @copydoc
|
// @copydoc
|
||||||
bool isArbitrating() const override { return m_arbitrationMaster != SYN; }
|
virtual result_t notifyTransportStatus(bool opened);
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the transfer latency of this device.
|
|
||||||
* @return the transfer latency in milliseconds.
|
|
||||||
*/
|
|
||||||
virtual unsigned int getLatency() const { return m_latency; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return whether the device supports the ebusd enhanced protocol.
|
|
||||||
* @return whether the device supports the ebusd enhanced protocol.
|
|
||||||
*/
|
|
||||||
bool isEnhancedProto() const { return m_enhancedLevel != el_none; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get info about enhanced protocol support as string.
|
|
||||||
* @return a @a string describing level of enhanced protocol support, or the empty string.
|
|
||||||
*/
|
|
||||||
virtual string getEnhancedProtoInfo() const { return m_enhancedLevel ? "enhanced" : ""; }
|
|
||||||
|
|
||||||
// @copydoc
|
// @copydoc
|
||||||
bool supportsUpdateCheck() const override { return m_enhancedLevel && m_extraFatures & 0x01; }
|
bool supportsUpdateCheck() const override { return m_extraFatures & 0x01; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @return whether the device supports the ebusd enhanced protocol and supports querying extra infos.
|
|
||||||
*/
|
|
||||||
bool supportsEnhancedInfos() const { return m_enhancedLevel && m_extraFatures & 0x01; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for a running extra infos request, wait for it to complete,
|
* Check for a running extra infos request, wait for it to complete,
|
||||||
* and then send a new request for extra infos to enhanced device.
|
* and then send a new request for extra infos to enhanced device.
|
||||||
* @param infoId the ID of the info to request.
|
* @param infoId the ID of the info to request.
|
||||||
|
* @param wait true to wait for a running request to complete, false to send right away.
|
||||||
* @return @a RESULT_OK on success, or an error code otherwise.
|
* @return @a RESULT_OK on success, or an error code otherwise.
|
||||||
*/
|
*/
|
||||||
result_t requestEnhancedInfo(symbol_t infoId);
|
result_t requestEnhancedInfo(symbol_t infoId, bool wait = true);
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a sequence of bytes to the device.
|
|
||||||
* @param id the ID of the sequence.
|
|
||||||
* @param data the buffer with the data to send.
|
|
||||||
* @param len the length of the buffer.
|
|
||||||
* @return the @a result_t code.
|
|
||||||
*/
|
|
||||||
virtual result_t sendSequence(SequenceId id, const uint8_t* data = nullptr, size_t len = 0);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the enhanced device version.
|
* Get the enhanced device version.
|
||||||
@@ -321,12 +297,7 @@ class FileDevice : public Device {
|
|||||||
*/
|
*/
|
||||||
string getEnhancedInfos();
|
string getEnhancedInfos();
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
/**
|
|
||||||
* Check if the device is still available and close it if not.
|
|
||||||
*/
|
|
||||||
virtual void checkDevice() = 0; // abstract
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel a running arbitration.
|
* Cancel a running arbitration.
|
||||||
* @param arbitrationState the reference in which @a as_error is stored when cancelled.
|
* @param arbitrationState the reference in which @a as_error is stored when cancelled.
|
||||||
@@ -334,83 +305,22 @@ class FileDevice : public Device {
|
|||||||
*/
|
*/
|
||||||
bool cancelRunningArbitration(ArbitrationState* arbitrationState);
|
bool cancelRunningArbitration(ArbitrationState* arbitrationState);
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a single byte.
|
|
||||||
* @param value the byte value to write.
|
|
||||||
* @param startArbitration true to start arbitration.
|
|
||||||
* @return true on success, false on error.
|
|
||||||
*/
|
|
||||||
virtual bool write(symbol_t value, bool startArbitration = false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether a symbol is available for reading immediately (without waiting).
|
|
||||||
* @return true when a symbol is available for reading immediately.
|
|
||||||
*/
|
|
||||||
virtual bool available();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a single byte.
|
|
||||||
* @param value the reference in which the read byte value is stored.
|
|
||||||
* @param isAvailable the result of the immediately preceding call to @a available().
|
|
||||||
* @param arbitrationState the variable in which to store the current/received arbitration state (mandatory for enhanced proto).
|
|
||||||
* @param incomplete the variable in which to store when a partial transfer needs another poll.
|
|
||||||
* @return true on success, false on error.
|
|
||||||
*/
|
|
||||||
virtual bool read(symbol_t* value, bool isAvailable, ArbitrationState* arbitrationState = nullptr,
|
|
||||||
bool* incomplete = nullptr);
|
|
||||||
|
|
||||||
/** whether to regularly check the device availability. */
|
|
||||||
const bool m_checkDevice;
|
|
||||||
|
|
||||||
/** the bus transfer latency in milliseconds. */
|
|
||||||
const unsigned int m_latency;
|
|
||||||
|
|
||||||
/** whether the device supports the ebusd enhanced protocol. */
|
|
||||||
const EnhancedLevel m_enhancedLevel;
|
|
||||||
|
|
||||||
/** the opened file descriptor, or -1. */
|
|
||||||
int m_fd;
|
|
||||||
|
|
||||||
/** whether the reset of an enhanced device was already requested. */
|
|
||||||
bool m_resetRequested;
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
/**
|
||||||
* Handle the already buffered enhanced data.
|
* Handle the already buffered enhanced data.
|
||||||
* @param value the reference in which the read byte value is stored.
|
* @param value the reference in which the read byte value is stored.
|
||||||
* @param arbitrationState the variable in which to store the current/received arbitration state (mandatory for enhanced proto).
|
* @param arbitrationState the variable in which to store the current/received arbitration state.
|
||||||
* @return true if the value was set, false otherwise.
|
* @return the @a result_t code, especially RESULT_CONTINE if the value was set and more data is available immediately.
|
||||||
*/
|
*/
|
||||||
bool handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbitrationState);
|
result_t handleEnhancedBufferedData(const uint8_t* data, size_t len, symbol_t* value,
|
||||||
|
ArbitrationState* arbitrationState);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when reception of an info ID was completed.
|
* Called when reception of an info ID was completed.
|
||||||
*/
|
*/
|
||||||
void notifyInfoRetrieved();
|
void notifyInfoRetrieved();
|
||||||
|
|
||||||
/** the arbitration master address to send when in arbitration, or @a SYN. */
|
/** whether the reset of the device was already requested. */
|
||||||
symbol_t m_arbitrationMaster;
|
bool m_resetRequested;
|
||||||
|
|
||||||
/** >0 when in arbitration and the next received symbol needs to be checked against the sent master address,
|
|
||||||
* incremented with each received SYN when arbitration was not performed as expected and needs to be stopped. */
|
|
||||||
size_t m_arbitrationCheck;
|
|
||||||
|
|
||||||
/** the read buffer. */
|
|
||||||
symbol_t* m_buffer;
|
|
||||||
|
|
||||||
/** the read buffer size (multiple of 4). */
|
|
||||||
size_t m_bufSize;
|
|
||||||
|
|
||||||
/** the read buffer fill length. */
|
|
||||||
size_t m_bufLen;
|
|
||||||
|
|
||||||
/** the read buffer read position. */
|
|
||||||
size_t m_bufPos;
|
|
||||||
/** the send buffer. */
|
|
||||||
uint8_t* m_sendBuf;
|
|
||||||
|
|
||||||
/** the send buffer size. */
|
|
||||||
size_t m_sendBufSize;
|
|
||||||
|
|
||||||
/** the extra features supported by the device. */
|
/** the extra features supported by the device. */
|
||||||
symbol_t m_extraFatures;
|
symbol_t m_extraFatures;
|
||||||
@@ -440,96 +350,6 @@ class FileDevice : public Device {
|
|||||||
string m_enhInfoBusVoltage;
|
string m_enhInfoBusVoltage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The @a Device for directly connected serial interfaces (tty).
|
|
||||||
*/
|
|
||||||
class SerialDevice : public FileDevice {
|
|
||||||
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.
|
|
||||||
* @param extraLatency the extra bus transfer latency in milliseconds.
|
|
||||||
* @param enhancedLevel whether to use the ebusd enhanced protocol.
|
|
||||||
*/
|
|
||||||
SerialDevice(const char* name, bool checkDevice, unsigned int extraLatency,
|
|
||||||
EnhancedLevel enhancedLevel)
|
|
||||||
: FileDevice(name, checkDevice, extraLatency, enhancedLevel) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// @copydoc
|
|
||||||
string getEnhancedProtoInfo() const override {
|
|
||||||
return m_enhancedLevel == el_speed ? "enhanced high speed" : FileDevice::getEnhancedProtoInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
// @copydoc
|
|
||||||
result_t open() override;
|
|
||||||
|
|
||||||
// @copydoc
|
|
||||||
void close() override;
|
|
||||||
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// @copydoc
|
|
||||||
void checkDevice() override;
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
/** the previous settings of the device for restoring. */
|
|
||||||
termios m_oldSettings;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The @a Device for remote network interfaces.
|
|
||||||
*/
|
|
||||||
class NetworkDevice : public FileDevice {
|
|
||||||
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 hostOrIp the host name or IP address of the device.
|
|
||||||
* @param port the TCP or UDP port of the device.
|
|
||||||
* @param extraLatency the extra bus transfer latency in milliseconds.
|
|
||||||
* @param udp true for UDP, false to TCP.
|
|
||||||
* @param enhancedLevel whether to use the ebusd enhanced protocol.
|
|
||||||
*/
|
|
||||||
NetworkDevice(const char* name, const char* hostOrIp, uint16_t port, unsigned int extraLatency,
|
|
||||||
bool udp, EnhancedLevel enhancedLevel)
|
|
||||||
: FileDevice(name, true, NETWORK_LATENCY_MS+extraLatency, enhancedLevel),
|
|
||||||
m_hostOrIp(hostOrIp), m_port(port), m_udp(udp) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destructor.
|
|
||||||
*/
|
|
||||||
~NetworkDevice() override {
|
|
||||||
if (m_hostOrIp) {
|
|
||||||
free((void*)m_hostOrIp);
|
|
||||||
m_hostOrIp = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @copydoc
|
|
||||||
result_t open() override;
|
|
||||||
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// @copydoc
|
|
||||||
void checkDevice() override;
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
/** the host name or IP address of the device. */
|
|
||||||
const char* m_hostOrIp;
|
|
||||||
|
|
||||||
/** the TCP or UDP port of the device. */
|
|
||||||
const uint16_t m_port;
|
|
||||||
|
|
||||||
/** true for UDP, false to TCP. */
|
|
||||||
const bool m_udp;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ebusd
|
} // namespace ebusd
|
||||||
|
|
||||||
#endif // LIB_EBUS_DEVICE_H_
|
#endif // LIB_EBUS_DEVICE_H_
|
||||||
|
|||||||
+27
-23
@@ -57,21 +57,18 @@ bool ActiveBusRequest::notify(result_t result, const SlaveSymbolString& slave) {
|
|||||||
ProtocolHandler* ProtocolHandler::create(const ebus_protocol_config_t config,
|
ProtocolHandler* ProtocolHandler::create(const ebus_protocol_config_t config,
|
||||||
ProtocolListener* listener) {
|
ProtocolListener* listener) {
|
||||||
const char* name = config.device;
|
const char* name = config.device;
|
||||||
EnhancedLevel enhanced = el_none;
|
bool enhanced = false;
|
||||||
if (strncmp(name, "en", 2) == 0 && name[2] && name[3] == ':') {
|
uint8_t speed = 0;
|
||||||
switch (name[2]) {
|
if (name[0] == 'e' && name[1] && name[2] && name[3] == ':') {
|
||||||
case 's':
|
speed = name[2] == 's' ? 2 : name[2] == 'h' ? 1 : 0;
|
||||||
enhanced = el_speed;
|
enhanced = speed > 0 && name[1] == 'n';
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
enhanced = el_basic;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (enhanced) {
|
if (enhanced) {
|
||||||
name += 4;
|
name += 4;
|
||||||
|
} else {
|
||||||
|
speed = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FileDevice* device = nullptr;
|
Transport* transport;
|
||||||
if (strchr(name, '/') == nullptr && strchr(name, ':') != nullptr) {
|
if (strchr(name, '/') == nullptr && strchr(name, ':') != nullptr) {
|
||||||
char* in = strdup(name);
|
char* in = strdup(name);
|
||||||
bool udp = false;
|
bool udp = false;
|
||||||
@@ -95,10 +92,16 @@ ProtocolHandler* ProtocolHandler::create(const ebus_protocol_config_t config,
|
|||||||
*portpos = 0;
|
*portpos = 0;
|
||||||
char* hostOrIp = strdup(addrpos);
|
char* hostOrIp = strdup(addrpos);
|
||||||
free(in);
|
free(in);
|
||||||
device = new NetworkDevice(name, hostOrIp, port, config.extraLatency, udp, enhanced);
|
transport = new NetworkTransport(name, config.extraLatency, hostOrIp, port, udp);
|
||||||
} else {
|
} else {
|
||||||
// support enx:/dev/<device>, ens:/dev/<device>, enh:/dev/<device>, and /dev/<device>
|
// support ens:/dev/<device>, enh:/dev/<device>, and /dev/<device>
|
||||||
device = new SerialDevice(name, !config.noDeviceCheck, config.extraLatency, enhanced);
|
transport = new SerialTransport(name, config.extraLatency, !config.noDeviceCheck, speed);
|
||||||
|
}
|
||||||
|
CharDevice* device;
|
||||||
|
if (enhanced) {
|
||||||
|
device = new EnhancedCharDevice(transport);
|
||||||
|
} else {
|
||||||
|
device = new PlainCharDevice(transport);
|
||||||
}
|
}
|
||||||
return new DirectProtocolHandler(config, device, listener);
|
return new DirectProtocolHandler(config, device, listener);
|
||||||
}
|
}
|
||||||
@@ -124,32 +127,32 @@ void ProtocolHandler::formatInfo(ostringstream* ostream, bool verbose, bool noWa
|
|||||||
m_device->formatInfo(ostream, verbose, false);
|
m_device->formatInfo(ostream, verbose, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProtocolHandler::formatInfoJson(ostringstream* ostream) {
|
void ProtocolHandler::formatInfoJson(ostringstream* ostream) const {
|
||||||
m_device->formatInfoJson(ostream);
|
m_device->formatInfoJson(ostream);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProtocolHandler::notifyDeviceData(symbol_t symbol, bool received) {
|
void ProtocolHandler::notifyDeviceData(symbol_t* data, size_t len, bool received) {
|
||||||
if (received && m_dumpFile) {
|
if (received && m_dumpFile) {
|
||||||
m_dumpFile->write(&symbol, 1);
|
m_dumpFile->write(data, len);
|
||||||
}
|
}
|
||||||
if (!m_logRawFile && !m_logRawEnabled) {
|
if (!m_logRawFile && !m_logRawEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (m_logRawBytes) {
|
if (m_logRawBytes) {
|
||||||
if (m_logRawFile) {
|
if (m_logRawFile) {
|
||||||
m_logRawFile->write(&symbol, 1, received);
|
m_logRawFile->write(data, len, received);
|
||||||
} else if (m_logRawEnabled) {
|
} else if (m_logRawEnabled) {
|
||||||
if (received) {
|
for (size_t pos = 0; pos < len; pos++) {
|
||||||
logNotice(lf_bus, "<%02x", symbol);
|
logNotice(lf_bus, "%c%02x", received ? '<' : '>', data[pos]);
|
||||||
} else {
|
|
||||||
logNotice(lf_bus, ">%02x", symbol);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
for (size_t pos = 0; pos < len; pos++) {
|
||||||
|
symbol_t symbol = data[pos];
|
||||||
if (symbol != SYN) {
|
if (symbol != SYN) {
|
||||||
if (received && !m_logRawLastReceived && symbol == m_logRawLastSymbol) {
|
if (received && !m_logRawLastReceived && symbol == m_logRawLastSymbol) {
|
||||||
return; // skip received echo of previously sent symbol
|
continue; // skip received echo of previously sent symbol
|
||||||
}
|
}
|
||||||
if (m_logRawBuffer.tellp() == 0 || received != m_logRawLastReceived) {
|
if (m_logRawBuffer.tellp() == 0 || received != m_logRawLastReceived) {
|
||||||
m_logRawLastReceived = received;
|
m_logRawLastReceived = received;
|
||||||
@@ -175,6 +178,7 @@ void ProtocolHandler::notifyDeviceData(symbol_t symbol, bool received) {
|
|||||||
m_logRawBuffer.str("");
|
m_logRawBuffer.str("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ProtocolHandler::notifyDeviceStatus(bool error, const char* message) {
|
void ProtocolHandler::notifyDeviceStatus(bool error, const char* message) {
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ class ProtocolHandler : public WaitThread, DeviceListener {
|
|||||||
* Format device/protocol infos in JSON format.
|
* Format device/protocol infos in JSON format.
|
||||||
* @param output the @a ostringstream to append the infos to.
|
* @param output the @a ostringstream to append the infos to.
|
||||||
*/
|
*/
|
||||||
virtual void formatInfoJson(ostringstream* output);
|
virtual void formatInfoJson(ostringstream* output) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether to allow read access to the device only.
|
* @return whether to allow read access to the device only.
|
||||||
@@ -381,7 +381,7 @@ class ProtocolHandler : public WaitThread, DeviceListener {
|
|||||||
virtual bool supportsUpdateCheck() const { return m_device->supportsUpdateCheck(); }
|
virtual bool supportsUpdateCheck() const { return m_device->supportsUpdateCheck(); }
|
||||||
|
|
||||||
// @copydoc
|
// @copydoc
|
||||||
void notifyDeviceData(symbol_t symbol, bool received) override;
|
virtual void notifyDeviceData(symbol_t* symbols, size_t len, bool received);
|
||||||
|
|
||||||
// @copydoc
|
// @copydoc
|
||||||
void notifyDeviceStatus(bool error, const char* message) override;
|
void notifyDeviceStatus(bool error, const char* message) override;
|
||||||
|
|||||||
@@ -82,10 +82,18 @@ void DirectProtocolHandler::run() {
|
|||||||
lastTime += 2;
|
lastTime += 2;
|
||||||
logNotice(lf_bus, "bus started with own address %2.2x/%2.2x%s", m_ownMasterAddress, m_ownSlaveAddress,
|
logNotice(lf_bus, "bus started with own address %2.2x/%2.2x%s", m_ownMasterAddress, m_ownSlaveAddress,
|
||||||
m_config.answer?" in answer mode":"");
|
m_config.answer?" in answer mode":"");
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (m_device->isValid() && !m_reconnect) {
|
bool valid = m_device->isValid();
|
||||||
result_t result = handleSymbol();
|
if (valid && !m_reconnect) {
|
||||||
|
unsigned int recvTimeout = 0;
|
||||||
|
symbol_t sentSymbol = ESC;
|
||||||
|
struct timespec sentTime;
|
||||||
|
result_t result = handleSend(&recvTimeout, &sentSymbol, &sentTime);
|
||||||
|
bool sent = result == RESULT_CONTINUE;
|
||||||
|
do {
|
||||||
|
if (result >= RESULT_OK) {
|
||||||
|
result = handleReceive(recvTimeout, sent, sentSymbol, &sentTime);
|
||||||
|
}
|
||||||
time(&now);
|
time(&now);
|
||||||
if (result != RESULT_ERR_TIMEOUT && now >= lastTime) {
|
if (result != RESULT_ERR_TIMEOUT && now >= lastTime) {
|
||||||
symCount++;
|
symCount++;
|
||||||
@@ -101,8 +109,11 @@ void DirectProtocolHandler::run() {
|
|||||||
lastTime = now;
|
lastTime = now;
|
||||||
symCount = 0;
|
symCount = 0;
|
||||||
}
|
}
|
||||||
|
recvTimeout = 0; // for further buffered bytes
|
||||||
|
sent = false;
|
||||||
|
} while (result == RESULT_CONTINUE);
|
||||||
} else {
|
} else {
|
||||||
if (!m_device->isValid()) {
|
if (!valid) {
|
||||||
logNotice(lf_bus, "device invalid");
|
logNotice(lf_bus, "device invalid");
|
||||||
setState(bs_noSignal, RESULT_ERR_DEVICE);
|
setState(bs_noSignal, RESULT_ERR_DEVICE);
|
||||||
}
|
}
|
||||||
@@ -136,7 +147,8 @@ void DirectProtocolHandler::run() {
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
result_t DirectProtocolHandler::handleSymbol() {
|
result_t DirectProtocolHandler::handleSend(unsigned int* recvTimeout, symbol_t* sentSymbol,
|
||||||
|
struct timespec* sentTime) {
|
||||||
unsigned int timeout = SYN_TIMEOUT;
|
unsigned int timeout = SYN_TIMEOUT;
|
||||||
symbol_t sendSymbol = ESC;
|
symbol_t sendSymbol = ESC;
|
||||||
bool sending = false;
|
bool sending = false;
|
||||||
@@ -247,8 +259,6 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// send symbol if necessary
|
// send symbol if necessary
|
||||||
result_t result;
|
|
||||||
struct timespec sentTime, recvTime;
|
|
||||||
if (sending && !m_config.readOnly) {
|
if (sending && !m_config.readOnly) {
|
||||||
if (m_state != bs_sendSyn && (sendSymbol == ESC || sendSymbol == SYN)) {
|
if (m_state != bs_sendSyn && (sendSymbol == ESC || sendSymbol == SYN)) {
|
||||||
if (m_escape) {
|
if (m_escape) {
|
||||||
@@ -258,43 +268,51 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
sendSymbol = ESC;
|
sendSymbol = ESC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = m_device->send(sendSymbol);
|
result_t result = m_device->send(sendSymbol);
|
||||||
clockGettime(&sentTime);
|
clockGettime(sentTime);
|
||||||
if (result == RESULT_OK) {
|
if (result == RESULT_OK) {
|
||||||
if (m_state == bs_ready) {
|
if (m_state == bs_ready) {
|
||||||
timeout = m_config.busAcquireTimeout;
|
timeout = m_config.busAcquireTimeout;
|
||||||
} else {
|
} else {
|
||||||
timeout = SEND_TIMEOUT;
|
timeout = SEND_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
*sentSymbol = sendSymbol;
|
||||||
} else {
|
} else {
|
||||||
sending = false;
|
sending = false;
|
||||||
timeout = SYN_TIMEOUT;
|
timeout = SYN_TIMEOUT;
|
||||||
setState(bs_skip, result);
|
setState(bs_skip, result);
|
||||||
}
|
}
|
||||||
|
*recvTimeout = timeout;
|
||||||
|
return sending ? RESULT_CONTINUE : result;
|
||||||
} else {
|
} else {
|
||||||
clockGettime(&sentTime); // for measuring arbitration delay in enhanced protocol
|
clockGettime(sentTime); // for measuring arbitration delay in enhanced protocol
|
||||||
|
}
|
||||||
|
*recvTimeout = timeout;
|
||||||
|
return RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result_t DirectProtocolHandler::handleReceive(unsigned int timeout, bool sending, symbol_t sentSymbol,
|
||||||
|
struct timespec* sentTime) {
|
||||||
// receive next symbol (optionally check reception of sent symbol)
|
// receive next symbol (optionally check reception of sent symbol)
|
||||||
symbol_t recvSymbol;
|
symbol_t recvSymbol;
|
||||||
|
struct timespec recvTime;
|
||||||
ArbitrationState arbitrationState = as_none;
|
ArbitrationState arbitrationState = as_none;
|
||||||
result = m_device->recv(timeout, &recvSymbol, &arbitrationState);
|
result_t result = m_device->recv(timeout, &recvSymbol, &arbitrationState);
|
||||||
|
bool sentAutoSyn = false;
|
||||||
if (sending) {
|
if (sending) {
|
||||||
clockGettime(&recvTime);
|
clockGettime(&recvTime);
|
||||||
}
|
} else if (!m_config.readOnly && result == RESULT_ERR_TIMEOUT && m_generateSynInterval > 0
|
||||||
bool sentAutoSyn = false;
|
|
||||||
if (!sending && !m_config.readOnly && result == RESULT_ERR_TIMEOUT && m_generateSynInterval > 0
|
|
||||||
&& timeout >= m_generateSynInterval && (m_state == bs_noSignal || m_state == bs_skip)) {
|
&& timeout >= m_generateSynInterval && (m_state == bs_noSignal || m_state == bs_skip)) {
|
||||||
// check if acting as AUTO-SYN generator is required
|
// check if acting as AUTO-SYN generator is required
|
||||||
result = m_device->send(SYN);
|
result = m_device->send(SYN);
|
||||||
if (result != RESULT_OK) {
|
if (result != RESULT_OK) {
|
||||||
return setState(bs_skip, result);
|
return setState(bs_skip, result);
|
||||||
}
|
}
|
||||||
clockGettime(&sentTime);
|
clockGettime(sentTime);
|
||||||
recvSymbol = ESC;
|
recvSymbol = ESC;
|
||||||
result = m_device->recv(SEND_TIMEOUT, &recvSymbol, &arbitrationState);
|
result = m_device->recv(SEND_TIMEOUT, &recvSymbol, &arbitrationState);
|
||||||
clockGettime(&recvTime);
|
clockGettime(&recvTime);
|
||||||
if (result != RESULT_OK) {
|
if (result < RESULT_OK) {
|
||||||
logError(lf_bus, "unable to receive sent AUTO-SYN symbol: %s", getResultCode(result));
|
logError(lf_bus, "unable to receive sent AUTO-SYN symbol: %s", getResultCode(result));
|
||||||
return setState(bs_noSignal, result);
|
return setState(bs_noSignal, result);
|
||||||
}
|
}
|
||||||
@@ -302,7 +320,7 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
logError(lf_bus, "received %2.2x instead of AUTO-SYN symbol", recvSymbol);
|
logError(lf_bus, "received %2.2x instead of AUTO-SYN symbol", recvSymbol);
|
||||||
return setState(bs_noSignal, result);
|
return setState(bs_noSignal, result);
|
||||||
}
|
}
|
||||||
measureLatency(&sentTime, &recvTime);
|
measureLatency(sentTime, &recvTime);
|
||||||
if (m_generateSynInterval != SYN_INTERVAL) {
|
if (m_generateSynInterval != SYN_INTERVAL) {
|
||||||
// received own AUTO-SYN symbol back again: act as AUTO-SYN generator now
|
// received own AUTO-SYN symbol back again: act as AUTO-SYN generator now
|
||||||
m_generateSynInterval = SYN_INTERVAL;
|
m_generateSynInterval = SYN_INTERVAL;
|
||||||
@@ -337,7 +355,7 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
} else {
|
} else {
|
||||||
logDebug(lf_bus, "arbitration won");
|
logDebug(lf_bus, "arbitration won");
|
||||||
m_currentRequest = startRequest;
|
m_currentRequest = startRequest;
|
||||||
sendSymbol = m_currentRequest->getMaster()[0];
|
sentSymbol = m_currentRequest->getMaster()[0];
|
||||||
sending = true;
|
sending = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,11 +379,11 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (sentAutoSyn && !sending) {
|
if (sentAutoSyn && !sending) {
|
||||||
return RESULT_OK;
|
return result;
|
||||||
}
|
}
|
||||||
time_t now;
|
time_t now;
|
||||||
time(&now);
|
time(&now);
|
||||||
if (result != RESULT_OK) {
|
if (result < RESULT_OK) {
|
||||||
if ((m_generateSynInterval != SYN_INTERVAL && difftime(now, m_lastReceive) > 1)
|
if ((m_generateSynInterval != SYN_INTERVAL && difftime(now, m_lastReceive) > 1)
|
||||||
// at least one full second has passed since last received symbol
|
// at least one full second has passed since last received symbol
|
||||||
|| m_state == bs_noSignal) {
|
|| m_state == bs_noSignal) {
|
||||||
@@ -376,20 +394,26 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
|
|
||||||
m_lastReceive = now;
|
m_lastReceive = now;
|
||||||
if ((recvSymbol == SYN) && (m_state != bs_sendSyn)) {
|
if ((recvSymbol == SYN) && (m_state != bs_sendSyn)) {
|
||||||
if (!sending && m_remainLockCount > 0 && m_command.size() != 1) {
|
if (result == RESULT_CONTINUE) {
|
||||||
|
if (m_remainLockCount == 0) {
|
||||||
|
m_remainLockCount = 1; // avoid starting arbitration when more data is already buffered
|
||||||
|
}
|
||||||
|
} else if (!sending) {
|
||||||
|
if (m_remainLockCount > 0 && m_command.size() != 1) {
|
||||||
m_remainLockCount--;
|
m_remainLockCount--;
|
||||||
} else if (!sending && m_remainLockCount == 0 && m_command.size() == 1) {
|
} else if (m_remainLockCount == 0 && m_command.size() == 1) {
|
||||||
m_remainLockCount = 1; // wait for next AUTO-SYN after SYN / address / SYN (bus locked for own priority)
|
m_remainLockCount = 1; // wait for next AUTO-SYN after SYN / address / SYN (bus locked for own priority)
|
||||||
}
|
}
|
||||||
clockGettime(&m_lastSynReceiveTime);
|
}
|
||||||
return setState(bs_ready, m_state == bs_skip ? RESULT_OK : RESULT_ERR_SYN);
|
m_lastSynReceiveTime = recvTime;
|
||||||
|
return setState(bs_ready, m_state == bs_skip || m_remainLockCount > 0 ? result : RESULT_ERR_SYN);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sending && m_state != bs_ready) { // check received symbol for equality if not in arbitration
|
if (sending && m_state != bs_ready) { // check received symbol for equality if not in arbitration
|
||||||
if (recvSymbol != sendSymbol) {
|
if (recvSymbol != sentSymbol) {
|
||||||
return setState(bs_skip, RESULT_ERR_SYMBOL);
|
return setState(bs_skip, RESULT_ERR_SYMBOL);
|
||||||
}
|
}
|
||||||
measureLatency(&sentTime, &recvTime);
|
measureLatency(sentTime, &recvTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (m_state) {
|
switch (m_state) {
|
||||||
@@ -407,10 +431,10 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
if (m_escape) {
|
if (m_escape) {
|
||||||
// check escape/unescape state
|
// check escape/unescape state
|
||||||
if (sending) {
|
if (sending) {
|
||||||
if (sendSymbol == ESC) {
|
if (sentSymbol == ESC) {
|
||||||
return RESULT_OK;
|
return result;
|
||||||
}
|
}
|
||||||
sendSymbol = recvSymbol = m_escape;
|
sentSymbol = recvSymbol = m_escape;
|
||||||
} else {
|
} else {
|
||||||
if (recvSymbol > 0x01) {
|
if (recvSymbol > 0x01) {
|
||||||
return setState(bs_skip, RESULT_ERR_ESC);
|
return setState(bs_skip, RESULT_ERR_ESC);
|
||||||
@@ -420,23 +444,23 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
m_escape = 0;
|
m_escape = 0;
|
||||||
} else if (!sending && recvSymbol == ESC) {
|
} else if (!sending && recvSymbol == ESC) {
|
||||||
m_escape = ESC;
|
m_escape = ESC;
|
||||||
return RESULT_OK;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (m_state) {
|
switch (m_state) {
|
||||||
case bs_noSignal:
|
case bs_noSignal:
|
||||||
return setState(bs_skip, RESULT_OK);
|
return setState(bs_skip, result);
|
||||||
|
|
||||||
case bs_skip:
|
case bs_skip:
|
||||||
return RESULT_OK;
|
return result;
|
||||||
|
|
||||||
case bs_ready:
|
case bs_ready:
|
||||||
if (m_currentRequest != nullptr && sending) {
|
if (m_currentRequest != nullptr && sending) {
|
||||||
// check arbitration
|
// check arbitration
|
||||||
if (recvSymbol == sendSymbol) { // arbitration successful
|
if (recvSymbol == sentSymbol) { // arbitration successful
|
||||||
// measure arbitration delay
|
// measure arbitration delay
|
||||||
int64_t latencyLong = (sentTime.tv_sec*1000000000 + sentTime.tv_nsec
|
int64_t latencyLong = (sentTime->tv_sec*1000000000LL + sentTime->tv_nsec
|
||||||
- m_lastSynReceiveTime.tv_sec*1000000000 - m_lastSynReceiveTime.tv_nsec)/1000;
|
- m_lastSynReceiveTime.tv_sec*1000000000LL - m_lastSynReceiveTime.tv_nsec)/1000;
|
||||||
if (latencyLong >= 0 && latencyLong <= 10000) { // skip clock skew or out of reasonable range
|
if (latencyLong >= 0 && latencyLong <= 10000) { // skip clock skew or out of reasonable range
|
||||||
auto latency = static_cast<int>(latencyLong);
|
auto latency = static_cast<int>(latencyLong);
|
||||||
logDebug(lf_bus, "arbitration delay %d micros", latency);
|
logDebug(lf_bus, "arbitration delay %d micros", latency);
|
||||||
@@ -452,11 +476,11 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
}
|
}
|
||||||
m_nextSendPos = 1;
|
m_nextSendPos = 1;
|
||||||
m_repeat = false;
|
m_repeat = false;
|
||||||
return setState(bs_sendCmd, RESULT_OK);
|
return setState(bs_sendCmd, result);
|
||||||
}
|
}
|
||||||
// arbitration lost. if same priority class found, try again after next AUTO-SYN
|
// arbitration lost. if same priority class found, try again after next AUTO-SYN
|
||||||
m_remainLockCount = isMaster(recvSymbol) ? 2 : 1; // number of SYN to wait for before next send try
|
m_remainLockCount = isMaster(recvSymbol) ? 2 : 1; // number of SYN to wait for before next send try
|
||||||
if ((recvSymbol & 0x0f) != (sendSymbol & 0x0f) && m_lockCount > m_remainLockCount) {
|
if ((recvSymbol & 0x0f) != (sentSymbol & 0x0f) && m_lockCount > m_remainLockCount) {
|
||||||
// if different priority class found, try again after N AUTO-SYN symbols (at least next AUTO-SYN)
|
// if different priority class found, try again after N AUTO-SYN symbols (at least next AUTO-SYN)
|
||||||
m_remainLockCount = m_lockCount;
|
m_remainLockCount = m_lockCount;
|
||||||
}
|
}
|
||||||
@@ -464,7 +488,7 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
}
|
}
|
||||||
m_command.push_back(recvSymbol);
|
m_command.push_back(recvSymbol);
|
||||||
m_repeat = false;
|
m_repeat = false;
|
||||||
return setState(bs_recvCmd, RESULT_OK);
|
return setState(bs_recvCmd, result);
|
||||||
|
|
||||||
case bs_recvCmd:
|
case bs_recvCmd:
|
||||||
if ((m_command.size() == 0 && !isMaster(recvSymbol))
|
if ((m_command.size() == 0 && !isMaster(recvSymbol))
|
||||||
@@ -473,9 +497,9 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
}
|
}
|
||||||
m_command.push_back(recvSymbol);
|
m_command.push_back(recvSymbol);
|
||||||
if (m_command.isComplete()) { // all data received
|
if (m_command.isComplete()) { // all data received
|
||||||
return setState(bs_recvCmdCrc, RESULT_OK);
|
return setState(bs_recvCmdCrc, result);
|
||||||
}
|
}
|
||||||
return RESULT_OK;
|
return result;
|
||||||
|
|
||||||
case bs_recvCmdCrc:
|
case bs_recvCmdCrc:
|
||||||
m_crcValid = recvSymbol == m_crc;
|
m_crcValid = recvSymbol == m_crc;
|
||||||
@@ -483,7 +507,7 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
if (m_crcValid) {
|
if (m_crcValid) {
|
||||||
addSeenAddress(m_command[0]);
|
addSeenAddress(m_command[0]);
|
||||||
messageCompleted();
|
messageCompleted();
|
||||||
return setState(bs_skip, RESULT_OK);
|
return setState(bs_skip, result);
|
||||||
}
|
}
|
||||||
return setState(bs_skip, RESULT_ERR_CRC);
|
return setState(bs_skip, RESULT_ERR_CRC);
|
||||||
}
|
}
|
||||||
@@ -493,14 +517,14 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
if (m_crcValid) {
|
if (m_crcValid) {
|
||||||
addSeenAddress(m_command[0]);
|
addSeenAddress(m_command[0]);
|
||||||
m_currentAnswering = true;
|
m_currentAnswering = true;
|
||||||
return setState(bs_sendCmdAck, RESULT_OK);
|
return setState(bs_sendCmdAck, result);
|
||||||
}
|
}
|
||||||
return setState(bs_sendCmdAck, RESULT_ERR_CRC);
|
return setState(bs_sendCmdAck, RESULT_ERR_CRC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (m_crcValid) {
|
if (m_crcValid) {
|
||||||
addSeenAddress(m_command[0]);
|
addSeenAddress(m_command[0]);
|
||||||
return setState(bs_recvCmdAck, RESULT_OK);
|
return setState(bs_recvCmdAck, result);
|
||||||
}
|
}
|
||||||
if (m_repeat) {
|
if (m_repeat) {
|
||||||
return setState(bs_skip, RESULT_ERR_CRC);
|
return setState(bs_skip, RESULT_ERR_CRC);
|
||||||
@@ -515,15 +539,15 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
if (m_currentRequest != nullptr) {
|
if (m_currentRequest != nullptr) {
|
||||||
if (isMaster(m_currentRequest->getMaster()[1])) {
|
if (isMaster(m_currentRequest->getMaster()[1])) {
|
||||||
messageCompleted();
|
messageCompleted();
|
||||||
return setState(bs_sendSyn, RESULT_OK);
|
return setState(bs_sendSyn, result);
|
||||||
}
|
}
|
||||||
} else if (isMaster(m_command[1])) {
|
} else if (isMaster(m_command[1])) {
|
||||||
messageCompleted();
|
messageCompleted();
|
||||||
return setState(bs_skip, RESULT_OK);
|
return setState(bs_skip, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_repeat = false;
|
m_repeat = false;
|
||||||
return setState(bs_recvRes, RESULT_OK);
|
return setState(bs_recvRes, result);
|
||||||
}
|
}
|
||||||
if (recvSymbol == NAK) {
|
if (recvSymbol == NAK) {
|
||||||
if (!m_repeat) {
|
if (!m_repeat) {
|
||||||
@@ -543,17 +567,17 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
case bs_recvRes:
|
case bs_recvRes:
|
||||||
m_response.push_back(recvSymbol);
|
m_response.push_back(recvSymbol);
|
||||||
if (m_response.isComplete()) { // all data received
|
if (m_response.isComplete()) { // all data received
|
||||||
return setState(bs_recvResCrc, RESULT_OK);
|
return setState(bs_recvResCrc, result);
|
||||||
}
|
}
|
||||||
return RESULT_OK;
|
return result;
|
||||||
|
|
||||||
case bs_recvResCrc:
|
case bs_recvResCrc:
|
||||||
m_crcValid = recvSymbol == m_crc;
|
m_crcValid = recvSymbol == m_crc;
|
||||||
if (m_crcValid) {
|
if (m_crcValid) {
|
||||||
if (m_currentRequest != nullptr) {
|
if (m_currentRequest != nullptr) {
|
||||||
return setState(bs_sendResAck, RESULT_OK);
|
return setState(bs_sendResAck, result);
|
||||||
}
|
}
|
||||||
return setState(bs_recvResAck, RESULT_OK);
|
return setState(bs_recvResAck, result);
|
||||||
}
|
}
|
||||||
if (m_repeat) {
|
if (m_repeat) {
|
||||||
if (m_currentRequest != nullptr) {
|
if (m_currentRequest != nullptr) {
|
||||||
@@ -572,7 +596,7 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
return setState(bs_skip, RESULT_ERR_ACK);
|
return setState(bs_skip, RESULT_ERR_ACK);
|
||||||
}
|
}
|
||||||
messageCompleted();
|
messageCompleted();
|
||||||
return setState(bs_skip, RESULT_OK);
|
return setState(bs_skip, result);
|
||||||
}
|
}
|
||||||
if (recvSymbol == NAK) {
|
if (recvSymbol == NAK) {
|
||||||
if (!m_repeat) {
|
if (!m_repeat) {
|
||||||
@@ -594,17 +618,17 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
}
|
}
|
||||||
m_nextSendPos++;
|
m_nextSendPos++;
|
||||||
if (m_nextSendPos >= m_currentRequest->getMaster().size()) {
|
if (m_nextSendPos >= m_currentRequest->getMaster().size()) {
|
||||||
return setState(bs_sendCmdCrc, RESULT_OK);
|
return setState(bs_sendCmdCrc, result);
|
||||||
}
|
}
|
||||||
return RESULT_OK;
|
return result;
|
||||||
|
|
||||||
case bs_sendCmdCrc:
|
case bs_sendCmdCrc:
|
||||||
if (m_currentRequest->getMaster()[1] == BROADCAST) {
|
if (m_currentRequest->getMaster()[1] == BROADCAST) {
|
||||||
messageCompleted();
|
messageCompleted();
|
||||||
return setState(bs_sendSyn, RESULT_OK);
|
return setState(bs_sendSyn, result);
|
||||||
}
|
}
|
||||||
m_crcValid = true;
|
m_crcValid = true;
|
||||||
return setState(bs_recvCmdAck, RESULT_OK);
|
return setState(bs_recvCmdAck, result);
|
||||||
|
|
||||||
case bs_sendResAck:
|
case bs_sendResAck:
|
||||||
if (!sending || m_currentRequest == nullptr) {
|
if (!sending || m_currentRequest == nullptr) {
|
||||||
@@ -619,7 +643,7 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
return setState(bs_sendSyn, RESULT_ERR_ACK);
|
return setState(bs_sendSyn, RESULT_ERR_ACK);
|
||||||
}
|
}
|
||||||
messageCompleted();
|
messageCompleted();
|
||||||
return setState(bs_sendSyn, RESULT_OK);
|
return setState(bs_sendSyn, result);
|
||||||
|
|
||||||
case bs_sendCmdAck:
|
case bs_sendCmdAck:
|
||||||
if (!sending || !m_config.answer) {
|
if (!sending || !m_config.answer) {
|
||||||
@@ -636,18 +660,20 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
}
|
}
|
||||||
if (isMaster(m_command[1])) {
|
if (isMaster(m_command[1])) {
|
||||||
messageCompleted(); // TODO decode command and store value into database of internal variables
|
messageCompleted(); // TODO decode command and store value into database of internal variables
|
||||||
return setState(bs_skip, RESULT_OK);
|
return setState(bs_skip, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_nextSendPos = 0;
|
m_nextSendPos = 0;
|
||||||
m_repeat = false;
|
m_repeat = false;
|
||||||
// build response and store in m_response for sending back to requesting master
|
// build response and store in m_response for sending back to requesting master
|
||||||
m_response.clear();
|
m_response.clear();
|
||||||
result = m_listener->notifyProtocolAnswer(m_command, &m_response);
|
{
|
||||||
|
result_t result = m_listener->notifyProtocolAnswer(m_command, &m_response);
|
||||||
if (result != RESULT_OK) {
|
if (result != RESULT_OK) {
|
||||||
return setState(bs_skip, result);
|
return setState(bs_skip, result);
|
||||||
}
|
}
|
||||||
return setState(bs_sendRes, RESULT_OK);
|
}
|
||||||
|
return setState(bs_sendRes, result);
|
||||||
|
|
||||||
case bs_sendRes:
|
case bs_sendRes:
|
||||||
if (!sending || !m_config.answer) {
|
if (!sending || !m_config.answer) {
|
||||||
@@ -656,23 +682,23 @@ result_t DirectProtocolHandler::handleSymbol() {
|
|||||||
m_nextSendPos++;
|
m_nextSendPos++;
|
||||||
if (m_nextSendPos >= m_response.size()) {
|
if (m_nextSendPos >= m_response.size()) {
|
||||||
// slave data completely sent
|
// slave data completely sent
|
||||||
return setState(bs_sendResCrc, RESULT_OK);
|
return setState(bs_sendResCrc, result);
|
||||||
}
|
}
|
||||||
return RESULT_OK;
|
return result;
|
||||||
|
|
||||||
case bs_sendResCrc:
|
case bs_sendResCrc:
|
||||||
if (!sending || !m_config.answer) {
|
if (!sending || !m_config.answer) {
|
||||||
return setState(bs_skip, RESULT_ERR_INVALID_ARG);
|
return setState(bs_skip, RESULT_ERR_INVALID_ARG);
|
||||||
}
|
}
|
||||||
return setState(bs_recvResAck, RESULT_OK);
|
return setState(bs_recvResAck, result);
|
||||||
|
|
||||||
case bs_sendSyn:
|
case bs_sendSyn:
|
||||||
if (!sending) {
|
if (!sending) {
|
||||||
return setState(bs_ready, RESULT_ERR_INVALID_ARG);
|
return setState(bs_ready, RESULT_ERR_INVALID_ARG);
|
||||||
}
|
}
|
||||||
return setState(bs_ready, RESULT_OK);
|
return setState(bs_ready, result);
|
||||||
}
|
}
|
||||||
return RESULT_OK;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result_t DirectProtocolHandler::setState(BusState state, result_t result, bool firstRepetition) {
|
result_t DirectProtocolHandler::setState(BusState state, result_t result, bool firstRepetition) {
|
||||||
@@ -682,7 +708,7 @@ result_t DirectProtocolHandler::setState(BusState state, result_t result, bool f
|
|||||||
m_currentRequest->incrementBusLostRetries();
|
m_currentRequest->incrementBusLostRetries();
|
||||||
m_nextRequests.push(m_currentRequest); // repeat
|
m_nextRequests.push(m_currentRequest); // repeat
|
||||||
m_currentRequest = nullptr;
|
m_currentRequest = nullptr;
|
||||||
} else if (state == bs_sendSyn || (result != RESULT_OK && !firstRepetition)) {
|
} else if (state == bs_sendSyn || (result < RESULT_OK && !firstRepetition)) {
|
||||||
logDebug(lf_bus, "notify request: %s", getResultCode(result));
|
logDebug(lf_bus, "notify request: %s", getResultCode(result));
|
||||||
bool restart = m_currentRequest->notify(
|
bool restart = m_currentRequest->notify(
|
||||||
result == RESULT_ERR_SYN && (m_state == bs_recvCmdAck || m_state == bs_recvRes)
|
result == RESULT_ERR_SYN && (m_state == bs_recvCmdAck || m_state == bs_recvRes)
|
||||||
@@ -716,13 +742,13 @@ result_t DirectProtocolHandler::setState(BusState state, result_t result, bool f
|
|||||||
|
|
||||||
m_escape = 0;
|
m_escape = 0;
|
||||||
if (state == m_state) {
|
if (state == m_state) {
|
||||||
if (m_listener && result != RESULT_OK) {
|
if (m_listener && result < RESULT_OK && state != bs_noSignal) {
|
||||||
m_listener->notifyProtocolStatus(m_listenerState, result);
|
m_listener->notifyProtocolStatus(m_listenerState, result);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if ((result < RESULT_OK && !(result == RESULT_ERR_TIMEOUT && state == bs_skip && m_state == bs_ready))
|
if ((result < RESULT_OK && !(result == RESULT_ERR_TIMEOUT && state == bs_skip && m_state == bs_ready))
|
||||||
|| (result != RESULT_OK && state == bs_skip && m_state != bs_ready)) {
|
|| (result < RESULT_OK && state == bs_skip && m_state != bs_ready)) {
|
||||||
logDebug(lf_bus, "%s during %s, switching to %s", getResultCode(result), getStateCode(m_state),
|
logDebug(lf_bus, "%s during %s, switching to %s", getResultCode(result), getStateCode(m_state),
|
||||||
getStateCode(state));
|
getStateCode(state));
|
||||||
} else if (m_currentRequest != nullptr || state == bs_sendCmd || state == bs_sendCmdCrc || state == bs_sendCmdAck
|
} else if (m_currentRequest != nullptr || state == bs_sendCmd || state == bs_sendCmdCrc || state == bs_sendCmdAck
|
||||||
@@ -744,7 +770,7 @@ result_t DirectProtocolHandler::setState(BusState state, result_t result, bool f
|
|||||||
if (pstate == ps_idle && m_generateSynInterval == SYN_INTERVAL) {
|
if (pstate == ps_idle && m_generateSynInterval == SYN_INTERVAL) {
|
||||||
pstate = ps_idleSYN;
|
pstate = ps_idleSYN;
|
||||||
}
|
}
|
||||||
if (result != RESULT_OK || pstate != m_listenerState) {
|
if (result < RESULT_OK || pstate != m_listenerState) {
|
||||||
m_listener->notifyProtocolStatus(pstate, result);
|
m_listener->notifyProtocolStatus(pstate, result);
|
||||||
m_listenerState = pstate;
|
m_listenerState = pstate;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ class DirectProtocolHandler : public ProtocolHandler {
|
|||||||
* @param listener the @a ProtocolListener.
|
* @param listener the @a ProtocolListener.
|
||||||
*/
|
*/
|
||||||
DirectProtocolHandler(const ebus_protocol_config_t config,
|
DirectProtocolHandler(const ebus_protocol_config_t config,
|
||||||
Device* device, ProtocolListener* listener)
|
CharDevice* device, ProtocolListener* listener)
|
||||||
: ProtocolHandler(config, device, listener),
|
: ProtocolHandler(config, device, listener), m_device(device),
|
||||||
m_lockCount(config.lockCount <= 3 ? 3 : config.lockCount),
|
m_lockCount(config.lockCount <= 3 ? 3 : config.lockCount),
|
||||||
m_remainLockCount(config.lockCount == 0 ? 1 : 0),
|
m_remainLockCount(config.lockCount == 0 ? 1 : 0),
|
||||||
m_generateSynInterval(config.generateSyn ? 10*getMasterNumber(config.ownAddress)+SYN_TIMEOUT : 0),
|
m_generateSynInterval(config.generateSyn ? 10*getMasterNumber(config.ownAddress)+SYN_TIMEOUT : 0),
|
||||||
@@ -110,10 +110,24 @@ class DirectProtocolHandler : public ProtocolHandler {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Handle the next symbol on the bus.
|
* Handle sending the next symbol to the bus.
|
||||||
* @return RESULT_OK on success, or an error code.
|
* @param recvTimeout pointer to a variable in which to put the timeout for the receive.
|
||||||
|
* @param sentSymbol pointer to a variable in which to put the sent symbol.
|
||||||
|
* @param sentTime pointer to a variable in which to put the system time when the symbol was sent.
|
||||||
|
* @return RESULT_OK on success, RESULT_CONTINUE when a symbol was sent, or an error code.
|
||||||
*/
|
*/
|
||||||
result_t handleSymbol();
|
result_t handleSend(unsigned int* recvTimeout, symbol_t* sentSymbol, struct timespec* sentTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle receiving the next symbol from the bus.
|
||||||
|
* @param timeout the timeout for the receive.
|
||||||
|
* @param sending whether a symbol was sent before entry.
|
||||||
|
* @param sentSymbol the sent symbol to verify (if sending).
|
||||||
|
* @param sentTime pointer to a variable with the system time when the symbol was sent.
|
||||||
|
* @return RESULT_OK on success, RESULT_CONTINUE when further received symbols are buffered,
|
||||||
|
* or an error code.
|
||||||
|
*/
|
||||||
|
result_t handleReceive(unsigned int timeout, bool sending, symbol_t sentSymbol, struct timespec* sentTime);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a new @a BusState and add a log message if necessary.
|
* Set a new @a BusState and add a log message if necessary.
|
||||||
@@ -132,6 +146,9 @@ class DirectProtocolHandler : public ProtocolHandler {
|
|||||||
*/
|
*/
|
||||||
void messageCompleted();
|
void messageCompleted();
|
||||||
|
|
||||||
|
/** the @a CharDevice instance for accessing the bus. */
|
||||||
|
CharDevice* m_device;
|
||||||
|
|
||||||
/** the number of AUTO-SYN symbols before sending is allowed after lost arbitration. */
|
/** the number of AUTO-SYN symbols before sending is allowed after lost arbitration. */
|
||||||
unsigned int m_lockCount;
|
unsigned int m_lockCount;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,356 @@
|
|||||||
|
/*
|
||||||
|
* ebusd - daemon for communication with eBUS heating systems.
|
||||||
|
* Copyright (C) 2023 John Baier <ebusd@ebusd.eu>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "lib/ebus/transport.h"
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/file.h>
|
||||||
|
#ifdef HAVE_LINUX_SERIAL
|
||||||
|
# include <linux/serial.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_FREEBSD_UFTDI
|
||||||
|
# include <dev/usb/uftdiio.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_PPOLL
|
||||||
|
# include <poll.h>
|
||||||
|
#endif
|
||||||
|
#include "lib/ebus/data.h"
|
||||||
|
#include "lib/utils/tcpsocket.h"
|
||||||
|
|
||||||
|
namespace ebusd {
|
||||||
|
|
||||||
|
|
||||||
|
#define MTU 1540
|
||||||
|
|
||||||
|
#ifndef POLLRDHUP
|
||||||
|
#define POLLRDHUP 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef DEBUG_RAW_TRAFFIC
|
||||||
|
#define DEBUG_RAW_TRAFFIC_HEAD(format, args...) fprintf(stdout, "%ld raw: " format, clockGetMillis(), args)
|
||||||
|
#define DEBUG_RAW_TRAFFIC_ITEM(args...) fprintf(stdout, args)
|
||||||
|
#define DEBUG_RAW_TRAFFIC_FINAL() fprintf(stdout, "\n"); fflush(stdout)
|
||||||
|
#undef DEBUG_RAW_TRAFFIC
|
||||||
|
#define DEBUG_RAW_TRAFFIC(format, args...) fprintf(stdout, "%ld raw: " format "\n", clockGetMillis(), args); fflush(stdout)
|
||||||
|
#else
|
||||||
|
#define DEBUG_RAW_TRAFFIC_HEAD(format, args...)
|
||||||
|
#undef DEBUG_RAW_TRAFFIC_ITEM
|
||||||
|
#define DEBUG_RAW_TRAFFIC_FINAL()
|
||||||
|
#define DEBUG_RAW_TRAFFIC(format, args...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FileTransport::FileTransport(const char* name, unsigned int latency, bool checkDevice)
|
||||||
|
: Transport(name, HOST_LATENCY_MS+latency),
|
||||||
|
m_checkDevice(checkDevice),
|
||||||
|
m_fd(-1),
|
||||||
|
m_bufSize(((MAX_LEN+1+3)/4)*4), m_bufLen(0) {
|
||||||
|
m_buffer = reinterpret_cast<symbol_t*>(malloc(m_bufSize));
|
||||||
|
if (!m_buffer) {
|
||||||
|
m_bufSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileTransport::~FileTransport() {
|
||||||
|
close();
|
||||||
|
if (m_buffer) {
|
||||||
|
free(m_buffer);
|
||||||
|
m_buffer = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result_t FileTransport::open() {
|
||||||
|
close();
|
||||||
|
result_t result;
|
||||||
|
if (m_bufSize == 0) {
|
||||||
|
result = RESULT_ERR_DEVICE;
|
||||||
|
} else {
|
||||||
|
result = openInternal();
|
||||||
|
}
|
||||||
|
if (m_listener != nullptr) {
|
||||||
|
result = m_listener->notifyTransportStatus(result == RESULT_OK);
|
||||||
|
}
|
||||||
|
if (result != RESULT_OK) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileTransport::close() {
|
||||||
|
if (m_fd == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
::close(m_fd);
|
||||||
|
m_fd = -1;
|
||||||
|
m_bufLen = 0; // flush read buffer
|
||||||
|
if (m_listener != nullptr) {
|
||||||
|
m_listener->notifyTransportStatus(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileTransport::isValid() {
|
||||||
|
if (m_fd == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (m_checkDevice) {
|
||||||
|
checkDevice();
|
||||||
|
}
|
||||||
|
return m_fd != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
result_t FileTransport::write(const uint8_t* data, size_t len) {
|
||||||
|
if (!isValid()) {
|
||||||
|
return RESULT_ERR_DEVICE;
|
||||||
|
}
|
||||||
|
#ifdef DEBUG_RAW_TRAFFIC_ITEM
|
||||||
|
DEBUG_RAW_TRAFFIC_HEAD("%ld >", len);
|
||||||
|
for (size_t pos=0; pos < len; pos++) {
|
||||||
|
DEBUG_RAW_TRAFFIC_ITEM(" %2.2x", data[pos]);
|
||||||
|
}
|
||||||
|
DEBUG_RAW_TRAFFIC_FINAL();
|
||||||
|
#endif
|
||||||
|
return (::write(m_fd, data, len) == len) ? RESULT_OK : RESULT_ERR_DEVICE;
|
||||||
|
}
|
||||||
|
|
||||||
|
result_t FileTransport::read(unsigned int timeout, const uint8_t** data, size_t* len) {
|
||||||
|
if (!isValid()) {
|
||||||
|
return RESULT_ERR_DEVICE;
|
||||||
|
}
|
||||||
|
if (timeout == 0) {
|
||||||
|
if (m_bufLen > 0) {
|
||||||
|
*data = m_buffer;
|
||||||
|
*len = m_bufLen;
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
return RESULT_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
if (timeout > 0) {
|
||||||
|
timeout += m_latency;
|
||||||
|
int ret;
|
||||||
|
struct timespec tdiff;
|
||||||
|
|
||||||
|
// set select timeout
|
||||||
|
tdiff.tv_sec = timeout/1000;
|
||||||
|
tdiff.tv_nsec = (timeout%1000)*1000000;
|
||||||
|
|
||||||
|
#ifdef HAVE_PPOLL
|
||||||
|
nfds_t nfds = 1;
|
||||||
|
struct pollfd fds[nfds];
|
||||||
|
|
||||||
|
memset(fds, 0, sizeof(fds));
|
||||||
|
|
||||||
|
fds[0].fd = m_fd;
|
||||||
|
fds[0].events = POLLIN | POLLERR | POLLHUP | POLLRDHUP;
|
||||||
|
ret = ppoll(fds, nfds, &tdiff, nullptr);
|
||||||
|
if (ret >= 0 && fds[0].revents & (POLLERR | POLLHUP | POLLRDHUP)) {
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#ifdef HAVE_PSELECT
|
||||||
|
fd_set readfds, exceptfds;
|
||||||
|
|
||||||
|
FD_ZERO(&readfds);
|
||||||
|
FD_ZERO(&exceptfds);
|
||||||
|
FD_SET(m_fd, &readfds);
|
||||||
|
|
||||||
|
ret = pselect(m_fd + 1, &readfds, nullptr, &exceptfds, &tdiff, nullptr);
|
||||||
|
if (ret >= 1 && FD_ISSET(m_fd, &exceptfds)) {
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ret = 1; // ignore timeout if neither ppoll nor pselect are available
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
if (ret == -1) {
|
||||||
|
DEBUG_RAW_TRAFFIC("poll error %d", errno);
|
||||||
|
close();
|
||||||
|
return RESULT_ERR_DEVICE;
|
||||||
|
}
|
||||||
|
if (ret == 0) {
|
||||||
|
return RESULT_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// directly read byte from device
|
||||||
|
if (m_bufLen > 0 && m_bufLen > m_bufSize - m_bufSize / 4) {
|
||||||
|
// more than 3/4 of input buffer consumed is taken as signal that ebusd is too slow
|
||||||
|
m_bufLen = 0;
|
||||||
|
if (m_listener != nullptr) {
|
||||||
|
m_listener->notifyTransportMessage(true, "buffer overflow");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fill up the buffer
|
||||||
|
ssize_t size = ::read(m_fd, m_buffer + m_bufLen, m_bufSize - m_bufLen);
|
||||||
|
if (size <= 0) {
|
||||||
|
return RESULT_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
#ifdef DEBUG_RAW_TRAFFIC_ITEM
|
||||||
|
DEBUG_RAW_TRAFFIC_HEAD("%ld+%ld <", m_bufLen, size);
|
||||||
|
for (int pos=0; pos < size; pos++) {
|
||||||
|
DEBUG_RAW_TRAFFIC_ITEM(" %2.2x", m_buffer[(m_bufLen+pos)%m_bufSize]);
|
||||||
|
}
|
||||||
|
DEBUG_RAW_TRAFFIC_FINAL();
|
||||||
|
#endif
|
||||||
|
m_bufLen += size;
|
||||||
|
*data = m_buffer;
|
||||||
|
*len = m_bufLen;
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileTransport::readConsumed(size_t len) {
|
||||||
|
if (len >= m_bufLen) {
|
||||||
|
m_bufLen = 0;
|
||||||
|
} else if (len > 0) {
|
||||||
|
size_t tail = m_bufLen - len;
|
||||||
|
memmove(m_buffer, m_buffer + len, tail);
|
||||||
|
DEBUG_RAW_TRAFFIC("move %ld @%ld to 0", tail, len);
|
||||||
|
m_bufLen = tail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result_t SerialTransport::openInternal() {
|
||||||
|
struct termios newSettings;
|
||||||
|
|
||||||
|
// open file descriptor
|
||||||
|
m_fd = ::open(m_name, O_RDWR | O_NOCTTY | O_NDELAY);
|
||||||
|
|
||||||
|
if (m_fd < 0) {
|
||||||
|
return RESULT_ERR_NOTFOUND;
|
||||||
|
}
|
||||||
|
if (isatty(m_fd) == 0) {
|
||||||
|
close();
|
||||||
|
return RESULT_ERR_NOTFOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flock(m_fd, LOCK_EX|LOCK_NB) != 0) {
|
||||||
|
close();
|
||||||
|
return RESULT_ERR_DEVICE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_LINUX_SERIAL
|
||||||
|
struct serial_struct serial;
|
||||||
|
if (ioctl(m_fd, TIOCGSERIAL, &serial) == 0) {
|
||||||
|
serial.flags |= ASYNC_LOW_LATENCY;
|
||||||
|
ioctl(m_fd, TIOCSSERIAL, &serial);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_FREEBSD_UFTDI
|
||||||
|
int param = 0;
|
||||||
|
// flush tx/rx and set low latency on uftdi device
|
||||||
|
if (ioctl(m_fd, UFTDIIOC_GET_LATENCY, ¶m) == 0) {
|
||||||
|
ioctl(m_fd, UFTDIIOC_RESET_IO, ¶m);
|
||||||
|
param = 1;
|
||||||
|
ioctl(m_fd, UFTDIIOC_SET_LATENCY, ¶m);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// save current settings
|
||||||
|
tcgetattr(m_fd, &m_oldSettings);
|
||||||
|
|
||||||
|
// create new settings
|
||||||
|
memset(&newSettings, 0, sizeof(newSettings));
|
||||||
|
|
||||||
|
#ifdef HAVE_CFSETSPEED
|
||||||
|
cfsetspeed(&newSettings, m_speed ? (m_speed > 1 ? B115200 : B9600) : B2400);
|
||||||
|
#else
|
||||||
|
cfsetispeed(&newSettings, m_speed ? (m_speed > 1 ? B115200 : B9600) : B2400);
|
||||||
|
cfsetospeed(&newSettings, m_enhancedLevel ? (m_enhancedLevel >= el_speed ? B115200 : B9600) : B2400);
|
||||||
|
#endif
|
||||||
|
newSettings.c_cflag |= (CS8 | CLOCAL | CREAD);
|
||||||
|
newSettings.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // non-canonical mode
|
||||||
|
newSettings.c_iflag |= IGNPAR; // ignore parity errors
|
||||||
|
newSettings.c_oflag &= ~OPOST;
|
||||||
|
|
||||||
|
// non-canonical mode: read() blocks until at least one byte is available
|
||||||
|
newSettings.c_cc[VMIN] = 1;
|
||||||
|
newSettings.c_cc[VTIME] = 0;
|
||||||
|
|
||||||
|
// empty device buffer
|
||||||
|
tcflush(m_fd, TCIFLUSH);
|
||||||
|
|
||||||
|
// activate new settings of serial device
|
||||||
|
if (tcsetattr(m_fd, TCSANOW, &newSettings)) {
|
||||||
|
close();
|
||||||
|
return RESULT_ERR_DEVICE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set serial device into blocking mode
|
||||||
|
fcntl(m_fd, F_SETFL, fcntl(m_fd, F_GETFL) & ~O_NONBLOCK);
|
||||||
|
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialTransport::close() {
|
||||||
|
if (m_fd != -1) {
|
||||||
|
// empty device buffer
|
||||||
|
tcflush(m_fd, TCIOFLUSH);
|
||||||
|
|
||||||
|
// restore previous settings of the device
|
||||||
|
tcsetattr(m_fd, TCSANOW, &m_oldSettings);
|
||||||
|
}
|
||||||
|
FileTransport::close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialTransport::checkDevice() {
|
||||||
|
int cnt;
|
||||||
|
if (ioctl(m_fd, FIONREAD, &cnt) == -1) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result_t NetworkTransport::openInternal() {
|
||||||
|
m_fd = socketConnect(m_hostOrIp, m_port, m_udp, nullptr, 5, 2); // wait up to 5 seconds for established connection
|
||||||
|
if (m_fd < 0) {
|
||||||
|
return RESULT_ERR_GENERIC_IO;
|
||||||
|
}
|
||||||
|
if (!m_udp) {
|
||||||
|
usleep(25000); // wait 25ms for potential initial garbage
|
||||||
|
}
|
||||||
|
int cnt;
|
||||||
|
symbol_t buf[MTU];
|
||||||
|
int ioerr;
|
||||||
|
while ((ioerr=ioctl(m_fd, FIONREAD, &cnt)) >= 0 && cnt > 1) {
|
||||||
|
// skip buffered input
|
||||||
|
ssize_t read = ::read(m_fd, &buf, MTU);
|
||||||
|
if (read <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ioerr < 0) {
|
||||||
|
close();
|
||||||
|
return RESULT_ERR_GENERIC_IO;
|
||||||
|
}
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkTransport::checkDevice() {
|
||||||
|
int cnt;
|
||||||
|
if (ioctl(m_fd, FIONREAD, &cnt) < 0) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ebusd
|
||||||
Executable
+334
@@ -0,0 +1,334 @@
|
|||||||
|
/*
|
||||||
|
* ebusd - daemon for communication with eBUS heating systems.
|
||||||
|
* Copyright (C) 2023 John Baier <ebusd@ebusd.eu>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIB_EBUS_TRANSPORT_H_
|
||||||
|
#define LIB_EBUS_TRANSPORT_H_
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <string>
|
||||||
|
#include "lib/ebus/result.h"
|
||||||
|
#include "lib/ebus/symbol.h"
|
||||||
|
|
||||||
|
namespace ebusd {
|
||||||
|
|
||||||
|
/** @file lib/ebus/transport.h
|
||||||
|
* Classes for low level transport to/from the eBUS device.
|
||||||
|
*
|
||||||
|
* A @a Transport is either a @a SerialTransport directly connected
|
||||||
|
* to a local tty port or a remote @a NetworkTransport handled via a
|
||||||
|
* socket.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** the transfer latency of the network device [ms]. */
|
||||||
|
#define NETWORK_LATENCY_MS 30
|
||||||
|
|
||||||
|
/** the latency of the host [ms]. */
|
||||||
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||||
|
#define HOST_LATENCY_MS 20
|
||||||
|
#else
|
||||||
|
#define HOST_LATENCY_MS 10
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for listening to data received on/sent to a @a Transport.
|
||||||
|
*/
|
||||||
|
class TransportListener {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Destructor.
|
||||||
|
*/
|
||||||
|
virtual ~TransportListener() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to notify a status change from the @a Transport.
|
||||||
|
* @param opened true when the transport was successfully opened, false when it was closed or open failed.
|
||||||
|
* @return the result_t code (other than RESULT_OK if an extra open action was performed unsuccessfully).
|
||||||
|
*/
|
||||||
|
virtual result_t notifyTransportStatus(bool opened) = 0; // abstract
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to notify a message from the @a Transport.
|
||||||
|
* @param error true for an error message, false for an info message.
|
||||||
|
* @param message the message string.
|
||||||
|
*/
|
||||||
|
virtual void notifyTransportMessage(bool error, const char* message) = 0; // abstract
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base class for low level transport to/from the eBUS device.
|
||||||
|
*/
|
||||||
|
class Transport {
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Construct a new instance.
|
||||||
|
* @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network).
|
||||||
|
*/
|
||||||
|
Transport(const char* name, unsigned int latency)
|
||||||
|
: m_name(name), m_latency(latency), m_listener(nullptr) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Destructor.
|
||||||
|
*/
|
||||||
|
virtual ~Transport() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the device name.
|
||||||
|
* @return the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network).
|
||||||
|
*/
|
||||||
|
const char* getName() const { return m_name; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the transfer latency of this device.
|
||||||
|
* @return the transfer latency in milliseconds.
|
||||||
|
*/
|
||||||
|
unsigned int getLatency() const { return m_latency; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get info about the transport as string.
|
||||||
|
* @return a @a string describing the transport.
|
||||||
|
*/
|
||||||
|
virtual string getTransportInfo() const = 0; // abstract
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the @a TransportListener.
|
||||||
|
* @param listener the @a TransportListener.
|
||||||
|
*/
|
||||||
|
void setListener(TransportListener* listener) { m_listener = listener; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the transport.
|
||||||
|
* @return the @a result_t code.
|
||||||
|
*/
|
||||||
|
virtual result_t open() = 0; // abstract
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the device if opened.
|
||||||
|
*/
|
||||||
|
virtual void close() = 0; // abstract
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether the device is opened and available.
|
||||||
|
* @return whether the device is opened and available.
|
||||||
|
*/
|
||||||
|
virtual bool isValid() = 0; // abstract
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write arbitrary data to the device.
|
||||||
|
* @param data the data to send.
|
||||||
|
* @param len the length of data.
|
||||||
|
* @return the @a result_t code.
|
||||||
|
*/
|
||||||
|
virtual result_t write(const uint8_t* data, size_t len) = 0; // abstract
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read data from the device.
|
||||||
|
* @param timeout maximum time to wait for the byte in milliseconds, or 0 for returning only already buffered data.
|
||||||
|
* @param data pointer to a variable in which to put the received data.
|
||||||
|
* @param len pointer to a variable in which to put the number of available bytes.
|
||||||
|
* @return the @a result_t code.
|
||||||
|
*/
|
||||||
|
virtual result_t read(unsigned int timeout, const uint8_t** data, size_t* len) = 0; // abstract
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needs to be called after @a read() in order to mark all or parts of the available
|
||||||
|
* bytes as consumed.
|
||||||
|
* @param len the number of bytes consumed.
|
||||||
|
*/
|
||||||
|
virtual void readConsumed(size_t len) = 0; // abstract
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Internal method for opening the device. Called from @a open().
|
||||||
|
* @return the @a result_t code.
|
||||||
|
*/
|
||||||
|
virtual result_t openInternal() = 0; // abstract
|
||||||
|
|
||||||
|
/** the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). */
|
||||||
|
const char* m_name;
|
||||||
|
|
||||||
|
/** the bus transfer latency in milliseconds. */
|
||||||
|
const unsigned int m_latency;
|
||||||
|
|
||||||
|
/** the @a TransportListener, or nullptr. */
|
||||||
|
TransportListener* m_listener;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The common base class for transport using a file descriptor.
|
||||||
|
*/
|
||||||
|
class FileTransport : public Transport {
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Construct a new instance.
|
||||||
|
* @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network).
|
||||||
|
* @param latency the bus transfer latency in milliseconds.
|
||||||
|
* @param checkDevice whether to regularly check the device availability.
|
||||||
|
*/
|
||||||
|
FileTransport(const char* name, unsigned int latency, bool checkDevice);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Destructor.
|
||||||
|
*/
|
||||||
|
virtual ~FileTransport();
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
result_t open() override;
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
void close() override;
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
bool isValid() override;
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
result_t write(const uint8_t* data, size_t len) override;
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
result_t read(unsigned int timeout, const uint8_t** data, size_t* len) override;
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
void readConsumed(size_t len) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Check if the device is still available and close it if not.
|
||||||
|
*/
|
||||||
|
virtual void checkDevice() = 0; // abstract
|
||||||
|
|
||||||
|
/** whether to regularly check the device availability. */
|
||||||
|
const bool m_checkDevice;
|
||||||
|
|
||||||
|
/** the opened file descriptor, or -1. */
|
||||||
|
int m_fd;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** the receive buffer. */
|
||||||
|
symbol_t* m_buffer;
|
||||||
|
|
||||||
|
/** the receive buffer size (multiple of 4). */
|
||||||
|
size_t m_bufSize;
|
||||||
|
|
||||||
|
/** the receive buffer fill length. */
|
||||||
|
size_t m_bufLen;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The @a Transport for a directly connected serial interface (tty).
|
||||||
|
*/
|
||||||
|
class SerialTransport : public FileTransport {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Construct a new instance.
|
||||||
|
* @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network).
|
||||||
|
* @param extraLatency the extra bus transfer latency in milliseconds.
|
||||||
|
* @param checkDevice whether to regularly check the device availability.
|
||||||
|
* @param speed 0 for normal speed, 1 for 4x speed, or 2 for 48x speed.
|
||||||
|
*/
|
||||||
|
SerialTransport(const char* name, unsigned int extraLatency, bool checkDevice, uint8_t speed)
|
||||||
|
: FileTransport(name, extraLatency, checkDevice), m_speed(speed) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
string getTransportInfo() const override {
|
||||||
|
return m_speed ? (m_speed == 1 ? "serial speed" : "serial high speed") : "serial";
|
||||||
|
}
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
result_t openInternal() override;
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
void close() override;
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// @copydoc
|
||||||
|
void checkDevice() override;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** the previous settings of the device for restoring. */
|
||||||
|
termios m_oldSettings;
|
||||||
|
|
||||||
|
/** 0 for normal speed, 1 for 4x speed, or 2 for 48x speed. */
|
||||||
|
const int m_speed;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The @a Transport for a remote network interface.
|
||||||
|
*/
|
||||||
|
class NetworkTransport : public FileTransport {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Construct a new instance.
|
||||||
|
* @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network).
|
||||||
|
* @param extraLatency the extra bus transfer latency in milliseconds.
|
||||||
|
* @param address the socket address of the device.
|
||||||
|
* @param hostOrIp the host name or IP address of the device.
|
||||||
|
* @param port the TCP or UDP port of the device.
|
||||||
|
* @param udp true for UDP, false to TCP.
|
||||||
|
*/
|
||||||
|
NetworkTransport(const char* name, unsigned int extraLatency, const char* hostOrIp, uint16_t port,
|
||||||
|
bool udp)
|
||||||
|
: FileTransport(name, NETWORK_LATENCY_MS+extraLatency, true),
|
||||||
|
m_hostOrIp(hostOrIp), m_port(port), m_udp(udp) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor.
|
||||||
|
*/
|
||||||
|
~NetworkTransport() override {
|
||||||
|
if (m_hostOrIp) {
|
||||||
|
free((void*)m_hostOrIp);
|
||||||
|
m_hostOrIp = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
string getTransportInfo() const override {
|
||||||
|
return m_udp ? "UDP" : "TCP";
|
||||||
|
}
|
||||||
|
|
||||||
|
// @copydoc
|
||||||
|
result_t openInternal() override;
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// @copydoc
|
||||||
|
void checkDevice() override;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** the host name or IP address of the device. */
|
||||||
|
const char* m_hostOrIp;
|
||||||
|
|
||||||
|
/** the TCP or UDP port of the device. */
|
||||||
|
const uint16_t m_port;
|
||||||
|
|
||||||
|
/** true for UDP, false to TCP. */
|
||||||
|
const bool m_udp;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ebusd
|
||||||
|
|
||||||
|
#endif // LIB_EBUS_TRANSPORT_H_
|
||||||
Reference in New Issue
Block a user