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:
John
2023-11-26 15:32:03 +01:00
parent a7d6eedf30
commit 82693fb51f
11 changed files with 1240 additions and 1074 deletions
+118 -298
View File
@@ -19,15 +19,9 @@
#ifndef 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 "lib/ebus/result.h"
#include "lib/ebus/transport.h"
#include "lib/ebus/symbol.h"
namespace ebusd {
@@ -35,25 +29,12 @@ namespace ebusd {
/** @file lib/ebus/device.h
* Classes providing access to the eBUS.
*
* A @a Device is either a @a SerialDevice directly connected to a local tty
* port or a remote @a NetworkDevice handled via a TCP socket. It allows to
* send and receive bytes to/from the eBUS while optionally dumping the data
* to a file and/or forwarding it to a logging function.
* A @a Device allows to send and receive data to/from a local or remote eBUS
* device while optionally dumping the data 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. */
enum ArbitrationState {
as_none, //!< no arbitration in process
@@ -65,11 +46,6 @@ enum ArbitrationState {
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.
*/
@@ -81,11 +57,12 @@ class DeviceListener {
virtual ~DeviceListener() {}
/**
* Listener method that is called when a symbol was received from/sent to eBUS.
* @param symbol the received/sent symbol.
* Listener method that is called when symbols were received from/sent to eBUS.
* @param data the received/sent data.
* @param len the length of received/sent data.
* @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.
@@ -99,25 +76,32 @@ class DeviceListener {
/**
* The base class for accessing an eBUS.
*/
class Device {
class Device : public TransportListener {
protected:
/**
* 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:
/**
* Destructor.
*/
virtual ~Device() { }
virtual ~Device() {
if (m_transport) {
delete m_transport;
m_transport = nullptr;
}
}
/**
* 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; }
const char* getName() const { return m_transport->getName(); }
/**
* Set the @a DeviceListener.
@@ -131,43 +115,75 @@ class Device {
* @param verbose whether to add verbose infos.
* @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.
* @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.
* @return the @a result_t code.
*/
virtual result_t open() = 0;
/**
* 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;
virtual result_t open() { return m_transport->open(); }
/**
* 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.
* @param value the byte value to write.
* @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.
@@ -177,7 +193,7 @@ class Device {
* @a as_won, the received byte is the master address that was successfully arbitrated with.
* @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
@@ -185,74 +201,65 @@ class Device {
* @param masterAddress the master address, or @a SYN to cancel a previous arbitration request.
* @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 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:
/** the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). */
const char* m_name;
/** the arbitration master address to send when in arbitration, or @a SYN. */
symbol_t m_arbitrationMaster;
/** the @a DeviceListener, or nullptr. */
DeviceListener* m_listener;
/** >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 possible enhanced protocol levels. */
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);
class PlainCharDevice : public CharDevice {
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
void formatInfo(ostringstream* output, bool verbose, bool prefix) override;
// @copydoc
void formatInfoJson(ostringstream* output) override;
// @copydoc
result_t open() override;
// @copydoc
result_t afterOpen() override;
// @copydoc
void close() override;
// @copydoc
bool isValid() override;
void formatInfoJson(ostringstream* output) const override;
// @copydoc
result_t send(symbol_t value) override;
@@ -264,50 +271,19 @@ class FileDevice : public Device {
result_t startArbitration(symbol_t masterAddress) override;
// @copydoc
bool isArbitrating() const override { return m_arbitrationMaster != SYN; }
/**
* 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" : ""; }
virtual result_t notifyTransportStatus(bool opened);
// @copydoc
bool supportsUpdateCheck() const override { return m_enhancedLevel && 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; }
bool supportsUpdateCheck() const override { return m_extraFatures & 0x01; }
/**
* Check for a running extra infos request, wait for it to complete,
* and then send a new request for extra infos to enhanced device.
* @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.
*/
result_t requestEnhancedInfo(symbol_t infoId);
/**
* 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);
result_t requestEnhancedInfo(symbol_t infoId, bool wait = true);
/**
* Get the enhanced device version.
@@ -321,12 +297,7 @@ class FileDevice : public Device {
*/
string getEnhancedInfos();
protected:
/**
* Check if the device is still available and close it if not.
*/
virtual void checkDevice() = 0; // abstract
private:
/**
* Cancel a running arbitration.
* @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);
/**
* 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.
* @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).
* @return true if the value was set, false otherwise.
* @param arbitrationState the variable in which to store the current/received arbitration state.
* @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.
*/
void notifyInfoRetrieved();
/** the arbitration master address to send when in arbitration, or @a SYN. */
symbol_t m_arbitrationMaster;
/** >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;
/** whether the reset of the device was already requested. */
bool m_resetRequested;
/** the extra features supported by the device. */
symbol_t m_extraFatures;
@@ -440,96 +350,6 @@ class FileDevice : public Device {
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
#endif // LIB_EBUS_DEVICE_H_