diff options
author | Evgeny Zinoviev <me@ch1p.io> | 2021-05-07 02:18:07 +0300 |
---|---|---|
committer | Evgeny Zinoviev <me@ch1p.io> | 2021-05-07 02:18:07 +0300 |
commit | 7e743b73433475df086fcec81be7b10c1d695a42 (patch) | |
tree | 1737c5f9bdad2a40f740e9a655e510641331b9e2 /src/voltronic |
initial
Diffstat (limited to 'src/voltronic')
-rw-r--r-- | src/voltronic/crc.cc | 60 | ||||
-rw-r--r-- | src/voltronic/crc.h | 20 | ||||
-rw-r--r-- | src/voltronic/device.cc | 167 | ||||
-rw-r--r-- | src/voltronic/device.h | 176 | ||||
-rw-r--r-- | src/voltronic/exceptions.h | 27 | ||||
-rw-r--r-- | src/voltronic/pseudo_device.cc | 63 | ||||
-rw-r--r-- | src/voltronic/serial_device.cc | 160 | ||||
-rw-r--r-- | src/voltronic/time.cc | 38 | ||||
-rw-r--r-- | src/voltronic/time.h | 14 | ||||
-rw-r--r-- | src/voltronic/usb_device.cc | 60 |
10 files changed, 785 insertions, 0 deletions
diff --git a/src/voltronic/crc.cc b/src/voltronic/crc.cc new file mode 100644 index 0000000..485fbf5 --- /dev/null +++ b/src/voltronic/crc.cc @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include "crc.h" + +namespace voltronic { + +static const u16 table[16] = { + 0x0000, 0x1021, 0x2042, 0x3063, + 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, + 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF +}; + +static inline bool is_reserved(u8 b) { + return b == 0x28 || b == 0x0D || b == 0x0A; +} + +CRC crc_read(const u8* buf) { + CRC crc = 0; + + crc |= (u16) buf[0] << 8; + crc |= (u16) buf[1]; + + return crc; +} + +void crc_write(CRC crc, u8* buffer) { + if (buffer != nullptr) { + buffer[0] = (crc >> 8) & 0xFF; + buffer[1] = crc & 0xFF; + } +} + +CRC crc_calculate(const u8* buf, size_t bufSize) { + CRC crc = 0; + + if (bufSize > 0) { + u8 byte; + do { + byte = *buf; + + crc = table[(crc >> 12) ^ (byte >> 4)] ^ (crc << 4); + crc = table[(crc >> 12) ^ (byte & 0x0F)] ^ (crc << 4); + + buf += 1; + } while (--bufSize); + + byte = crc; + if (is_reserved(byte)) + crc += 1; + + byte = crc >> 8; + if (is_reserved(byte)) + crc += 1 << 8; + } + + return crc; +} + +}
\ No newline at end of file diff --git a/src/voltronic/crc.h b/src/voltronic/crc.h new file mode 100644 index 0000000..0f34f38 --- /dev/null +++ b/src/voltronic/crc.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef INVERTER_TOOLS_VOLTRONIC_CRC_H +#define INVERTER_TOOLS_VOLTRONIC_CRC_H + +#include <cstdint> +#include <cstdlib> +#include "../numeric_types.h" + +namespace voltronic { + +typedef u16 CRC; + +void crc_write(CRC crc, u8* buffer); +CRC crc_read(const u8* buf); +CRC crc_calculate(const u8* buf, size_t bufSize); + +} + +#endif //INVERTER_TOOLS_VOLTRONIC_CRC_H diff --git a/src/voltronic/device.cc b/src/voltronic/device.cc new file mode 100644 index 0000000..632ed27 --- /dev/null +++ b/src/voltronic/device.cc @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include <memory> +#include <iostream> +#include <limits> +#include <cstring> +#include <sstream> + +#include "crc.h" +#include "device.h" +#include "time.h" +#include "exceptions.h" +#include "hexdump/hexdump.h" +#include "../logging.h" + +namespace voltronic { + +Device::Device() : + flags_(FLAG_WRITE_CRC | FLAG_READ_CRC | FLAG_VERIFY_CRC), + timeout_(TIMEOUT) {} + +void Device::setFlags(int flags) { + flags_ = flags; +} + +int Device::getFlags() const { + return flags_; +} + +void Device::setVerbose(bool verbose) { + verbose_ = verbose; +} + +void Device::setTimeout(u64 timeout) { + timeout_ = timeout; + timeStarted_ = timestamp(); +} + +u64 Device::getElapsedTime() const { + return timestamp() - timeStarted_; +} + +u64 Device::getTimeLeft() const { + if (!timeout_) + return std::numeric_limits<uint64_t>::max(); + + return std::max((u64)0, timeout_ - getElapsedTime()); +} + +size_t Device::run(const u8* inbuf, size_t inbufSize, u8* outbuf, size_t outbufSize) { + send(inbuf, inbufSize); + + if (!getTimeLeft()) + throw TimeoutError("sending already took " + std::to_string(getElapsedTime()) + " ms"); + + return recv(outbuf, outbufSize); +} + +void Device::send(const u8* buf, size_t bufSize) { + size_t dataLen; + std::shared_ptr<u8> data; + + if ((flags_ & FLAG_WRITE_CRC) == FLAG_WRITE_CRC) { + const CRC crc = crc_calculate(buf, bufSize); + dataLen = bufSize + sizeof(u16) + 1; + data = std::unique_ptr<u8>(new u8[dataLen]); + crc_write(crc, &data.get()[bufSize]); + } else { + dataLen = bufSize + 1; + data = std::unique_ptr<u8>(new u8[dataLen]); + } + + u8* dataPtr = data.get(); + memcpy((void*)dataPtr, buf, bufSize); + + dataPtr[dataLen - 1] = '\r'; + + if (verbose_) { + myerr << "writing " << dataLen << (dataLen > 1 ? " bytes" : " byte"); + std::cerr << hexdump(dataPtr, dataLen); + } + + writeLoop(dataPtr, dataLen); +} + +void Device::writeLoop(const u8* data, size_t dataSize) { + int bytesLeft = static_cast<int>(dataSize); + + while (true) { + size_t bytesWritten = write(data, bytesLeft); + if (verbose_) + myerr << "bytesWritten=" << bytesWritten; + + bytesLeft -= static_cast<int>(bytesWritten); + if (bytesLeft <= 0) + break; + + if (!getTimeLeft()) + throw TimeoutError("data writing already took " + std::to_string(getElapsedTime()) + " ms"); + + data = &data[bytesWritten]; + } +} + +size_t Device::recv(u8* buf, size_t bufSize) { + size_t bytesRead = readLoop(buf, bufSize); + + if (verbose_) { + myerr << "got " << bytesRead << (bytesRead > 1 ? " bytes" : " byte"); + std::cerr << hexdump(buf, bytesRead); + } + + bool crcNeeded = (flags_ & FLAG_READ_CRC) == FLAG_READ_CRC; + size_t minSize = crcNeeded ? sizeof(u16) + 1 : 1; + + if (bytesRead < minSize) + throw InvalidDataError("response is too small"); + + const size_t dataSize = bytesRead - minSize; + + if (crcNeeded) { + const CRC crcActual = crc_read(&buf[dataSize]); + const CRC crcExpected = crc_calculate(buf, dataSize); + +// buf[dataSize] = 0; + + if ((flags_ & FLAG_VERIFY_CRC) == FLAG_VERIFY_CRC && crcActual == crcExpected) + return dataSize; + + std::ostringstream error; + error << std::hex; + error << "crc is invalid: expected 0x" << crcExpected << ", got 0x" << crcActual; + throw InvalidDataError(error.str()); + } + +// buf[dataSize] = 0; + return dataSize; +} + +size_t Device::readLoop(u8 *buf, size_t bufSize) { + size_t size = 0; + + while(true) { + size_t bytesRead = read(buf, bufSize); + if (verbose_) + myerr << "bytesRead=" << bytesRead; + + while (bytesRead) { + bytesRead--; + size++; + + if (*buf == '\r') + return size; + + buf++; + bufSize--; + } + + if (!getTimeLeft()) + throw TimeoutError("data reading already took " + std::to_string(getElapsedTime()) + " ms"); + + if (bufSize <= 0) + throw std::overflow_error("input buffer is not large enough"); + } +} + +}
\ No newline at end of file diff --git a/src/voltronic/device.h b/src/voltronic/device.h new file mode 100644 index 0000000..6584585 --- /dev/null +++ b/src/voltronic/device.h @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef INVERTER_TOOLS_VOLTRONIC_DEVICE_H +#define INVERTER_TOOLS_VOLTRONIC_DEVICE_H + +#include <string> +#include <memory> +#include <hidapi/hidapi.h> +#include <libserialport.h> + +#include "../numeric_types.h" + +namespace voltronic { + +enum { + FLAG_WRITE_CRC = 1, + FLAG_READ_CRC = 2, + FLAG_VERIFY_CRC = 4, +}; + + +/** + * Common device + */ + +class Device { +protected: + int flags_; + u64 timeout_; + u64 timeStarted_; + bool verbose_; + + void send(const u8* buf, size_t bufSize); + size_t recv(u8* buf, size_t bufSize); + + void writeLoop(const u8* data, size_t dataSize); + size_t readLoop(u8* buf, size_t bufSize); + + u64 getElapsedTime() const; + u64 getTimeLeft() const; + +public: + static const u64 TIMEOUT = 1000; + + Device(); + + virtual size_t read(u8* buf, size_t bufSize) = 0; + virtual size_t write(const u8* data, size_t dataSize) = 0; + + void setTimeout(u64 timeout); + size_t run(const u8* inbuf, size_t inbufSize, u8* outbuf, size_t outbufSize); + + void setFlags(int flags); + int getFlags() const; + + void setVerbose(bool verbose); +}; + + +/** + * USB device + */ + +class USBDevice : public Device { +private: + hid_device* device_; + +public: + static const u16 VENDOR_ID = 0x0665; + static const u16 PRODUCT_ID = 0x5161; + static const u16 HID_REPORT_SIZE = 8; + static u16 GET_HID_REPORT_SIZE(size_t size); + + USBDevice(u16 vendorId, u16 productId); + ~USBDevice(); + + size_t read(u8* buf, size_t bufSize) override; + size_t write(const u8* data, size_t dataSize) override; +}; + + +/** + * Serial device + */ + +typedef unsigned SerialBaudRate; + +enum class SerialDataBits { + Five = 5, + Six = 6, + Seven = 7, + Eight = 8, +}; + +enum class SerialStopBits { + One = 1, + OneAndHalf = 3, + Two = 2 +}; + +enum class SerialParity { + Invalid = SP_PARITY_INVALID, + None = SP_PARITY_NONE, + Odd = SP_PARITY_ODD, + Even = SP_PARITY_EVEN, + Mark = SP_PARITY_MARK, + Space = SP_PARITY_SPACE, +}; + +class SerialDevice : public Device { +private: + struct sp_port* port_; + SerialBaudRate baudRate_; + SerialDataBits dataBits_; + SerialStopBits stopBits_; + SerialParity parity_; + std::string name_; + + unsigned getTimeout(); + +public: + static const char* DEVICE_NAME; + static const SerialBaudRate BAUD_RATE = 2400; + static const SerialDataBits DATA_BITS = SerialDataBits::Eight; + static const SerialStopBits STOP_BITS = SerialStopBits::One; + static const SerialParity PARITY = SerialParity::None; + + explicit SerialDevice(std::string& name, + SerialBaudRate baudRate, + SerialDataBits dataBits, + SerialStopBits stopBits, + SerialParity parity); + ~SerialDevice(); + + [[nodiscard]] inline struct sp_port* getPort() const { + return port_; + } + + size_t read(u8* buf, size_t bufSize) override; + size_t write(const u8* data, size_t dataSize) override; +}; + +class SerialPortConfiguration { +private: + struct sp_port_config* config_; + SerialDevice& device_; + +public: + explicit SerialPortConfiguration(SerialDevice& device); + ~SerialPortConfiguration(); + + void setConfiguration(SerialBaudRate baudRate, SerialDataBits dataBits, SerialStopBits stopBits, SerialParity parity); +}; + +bool is_serial_baud_rate_valid(SerialBaudRate baudRate); +bool is_serial_data_bits_valid(SerialDataBits dataBits); +bool is_serial_stop_bits_valid(SerialStopBits stopBits); +bool is_serial_parity_valid(SerialParity parity); + + +/** + * Pseudo device + */ + +class PseudoDevice : public Device { +public: + PseudoDevice() = default; + ~PseudoDevice() = default; + + size_t read(u8* buf, size_t bufSize) override; + size_t write(const u8* data, size_t dataSize) override; +}; + +} + +#endif //INVERTER_TOOLS_VOLTRONIC_DEVICE_H diff --git a/src/voltronic/exceptions.h b/src/voltronic/exceptions.h new file mode 100644 index 0000000..6ae9c32 --- /dev/null +++ b/src/voltronic/exceptions.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef INVERTER_TOOLS_VOLTRONIC_EXCEPTIONS_H +#define INVERTER_TOOLS_VOLTRONIC_EXCEPTIONS_H + +#include <stdexcept> + +namespace voltronic { + +class DeviceError : public std::runtime_error { +public: + using std::runtime_error::runtime_error; +}; + +class TimeoutError : public std::runtime_error { +public: + using std::runtime_error::runtime_error; +}; + +class InvalidDataError : public std::runtime_error { +public: + using std::runtime_error::runtime_error; +}; + +} + +#endif //INVERTER_TOOLS_VOLTRONIC_EXCEPTIONS_H diff --git a/src/voltronic/pseudo_device.cc b/src/voltronic/pseudo_device.cc new file mode 100644 index 0000000..58cd95c --- /dev/null +++ b/src/voltronic/pseudo_device.cc @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include <stdexcept> +#include <sstream> +#include <cstring> + +#include "device.h" +#include "crc.h" +#include "hexdump/hexdump.h" +#include "../logging.h" + +namespace voltronic { + +// PI +//static const char* response = "^D00518"; + +// GS +static const char* response = "^D1060000,000,2300,500,0115,0018,002,500,000,000,000,000,078,019,000,000,0000,0000,0000,0000,0,0,0,1,2,2,0,0"; + +// PIRI +//static const char* response = "^D0882300,217,2300,500,217,5000,5000,480,500,570,420,576,540,2,30,060,0,1,1,6,0,0,0,1,2,00"; + +// DI +//static const char* response = "^D0682300,500,0,408,540,564,460,540,060,30,0,0,1,0,0,0,1,0,0,1,1,0,1,1"; + +// set response +//static const char* response = "^1"; + +// TODO: maybe move size and crc stuff to readLoop()? +size_t PseudoDevice::read(u8* buf, size_t bufSize) { + size_t pseudoResponseSize = strlen(response); + + size_t responseSize = pseudoResponseSize; + if (flags_ & FLAG_READ_CRC) + responseSize += 2; + + if (responseSize + 1 > bufSize) { + std::ostringstream error; + error << "buffer is not large enough (" << (responseSize + 1) << " > " << bufSize << ")"; + throw std::overflow_error(error.str()); + } + + memcpy(buf, response, responseSize); + + if (flags_ & FLAG_READ_CRC) { + CRC crc = crc_calculate(buf, pseudoResponseSize); + crc_write(crc, &buf[pseudoResponseSize]); + } + + buf[responseSize] = '\r'; + + return responseSize + 1; +} + +size_t PseudoDevice::write(const u8* data, size_t dataSize) { + if (verbose_) { + myerr << "dataSize=" << dataSize; + std::cerr << hexdump((void*)data, dataSize); + } + return dataSize; +} + +}
\ No newline at end of file diff --git a/src/voltronic/serial_device.cc b/src/voltronic/serial_device.cc new file mode 100644 index 0000000..d8b7c98 --- /dev/null +++ b/src/voltronic/serial_device.cc @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include <stdexcept> +#include <algorithm> +#include <libserialport.h> + +#include "device.h" +#include "exceptions.h" +#include "../logging.h" + +namespace voltronic { + +const char* SerialDevice::DEVICE_NAME = "/dev/ttyUSB0"; + +SerialDevice::SerialDevice(std::string& name, + SerialBaudRate baudRate, + SerialDataBits dataBits, + SerialStopBits stopBits, + SerialParity parity) + : port_(nullptr) + , name_(name) + , baudRate_(baudRate) + , dataBits_(dataBits) + , stopBits_(stopBits) + , parity_(parity) +{ + if (sp_get_port_by_name(name_.c_str(), &port_) != SP_OK) + throw DeviceError("failed to get port by name"); + + if (sp_open(port_, SP_MODE_READ_WRITE) != SP_OK) + throw DeviceError("failed to open device"); + + SerialPortConfiguration config(*this); + config.setConfiguration(baudRate_, dataBits_, stopBits_, parity_); + + sp_flush(port_, SP_BUF_BOTH); +} + +SerialDevice::~SerialDevice() { + if (port_ != nullptr) { + if (sp_close(port_) == SP_OK) + sp_free_port(port_); + } +} + +unsigned int SerialDevice::getTimeout() { + return !timeout_ + // to wait indefinitely if no timeout set + ? 0 + // if getTimeLeft() suddently returns 0, pass 1, + // otherwise libserialport will treat it like 'wait indefinitely' + : std::max(static_cast<unsigned>(getTimeLeft()), static_cast<unsigned>(1)); +} + +size_t SerialDevice::read(u8* buf, size_t bufSize) { + if (verbose_) + myerr << "reading..."; + return sp_blocking_read_next(port_, buf, bufSize, getTimeout()); +} + +size_t SerialDevice::write(const u8* data, size_t dataSize) { + return sp_blocking_write(port_, data, dataSize, getTimeout()); +} + + +/** + * Serial port configuration + */ + +SerialPortConfiguration::SerialPortConfiguration(SerialDevice& device) + : config_(nullptr), device_(device) +{ + if (sp_new_config(&config_) != SP_OK) + throw DeviceError("failed to allocate port configuration"); + + if (sp_get_config(device.getPort(), config_) != SP_OK) + throw DeviceError("failed to get current port configuration"); +} + +SerialPortConfiguration::~SerialPortConfiguration() { + if (config_ != nullptr) + sp_free_config(config_); +} + +void SerialPortConfiguration::setConfiguration(SerialBaudRate baudRate, + SerialDataBits dataBits, + SerialStopBits stopBits, + SerialParity parity) { + if (sp_set_config_baudrate(config_, static_cast<int>(baudRate)) != SP_OK) + throw DeviceError("failed to set baud rate"); + + if (sp_set_config_bits(config_, static_cast<int>(dataBits)) != SP_OK) + throw DeviceError("failed to set data bits"); + + if (sp_set_config_stopbits(config_, static_cast<int>(stopBits)) != SP_OK) + throw DeviceError("failed to set stop bits"); + + if (sp_set_config_parity(config_, static_cast<enum sp_parity>(parity)) != SP_OK) + throw DeviceError("failed to set parity"); + + if (sp_set_config(device_.getPort(), config_) != SP_OK) + throw DeviceError("failed to set port configuration"); +} + +bool is_serial_baud_rate_valid(SerialBaudRate baudRate) { + switch (baudRate) { + case 110: + case 300: + case 1200: + case 2400: + case 4800: + case 9600: + case 19200: + case 38400: + case 57600: + case 115200: + return true; + + default: break; + } + return false; +} + +bool is_serial_data_bits_valid(SerialDataBits dataBits) { + switch (dataBits) { + case SerialDataBits::Five: + case SerialDataBits::Six: + case SerialDataBits::Seven: + case SerialDataBits::Eight: + return true; + default: break; + } + return false; +} + +bool is_serial_stop_bits_valid(SerialStopBits stopBits) { + switch (stopBits) { + case SerialStopBits::One: + case SerialStopBits::OneAndHalf: + case SerialStopBits::Two: + return true; + default: break; + } + return false; +} + +bool is_serial_parity_valid(SerialParity parity) { + switch (parity) { + case SerialParity::None: + case SerialParity::Odd: + case SerialParity::Even: + case SerialParity::Mark: + case SerialParity::Space: + return true; + default: break; + } + return false; +} + +}
\ No newline at end of file diff --git a/src/voltronic/time.cc b/src/voltronic/time.cc new file mode 100644 index 0000000..20b45cc --- /dev/null +++ b/src/voltronic/time.cc @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include <ctime> +#include <cstdint> +#include <sys/time.h> +#include "time.h" + +namespace voltronic { + +u64 timestamp() { + u64 ms = 0; + +#if defined(CLOCK_MONOTONIC) + static bool monotonic_clock_error = false; + if (!monotonic_clock_error) { + struct timespec ts = {0}; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { + ms = static_cast<u64>(ts.tv_sec); + ms *= 1000; + ms += static_cast<u64>(ts.tv_nsec / 1000000); + return ms; + } else { + monotonic_clock_error = true; + } + } +#endif + + struct timeval tv = {0}; + if (gettimeofday(&tv, nullptr) == 0) { + ms = static_cast<u64>(tv.tv_sec); + ms *= 1000; + ms += static_cast<u64>(tv.tv_usec / 1000); + } + + return ms; +} + +}
\ No newline at end of file diff --git a/src/voltronic/time.h b/src/voltronic/time.h new file mode 100644 index 0000000..d456461 --- /dev/null +++ b/src/voltronic/time.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef INVERTER_TOOLS_VOLTRONIC_TIME_H +#define INVERTER_TOOLS_VOLTRONIC_TIME_H + +#include "../numeric_types.h" + +namespace voltronic { + +u64 timestamp(); + +} + +#endif //INVERTER_TOOLS_VOLTRONIC_TIME_H diff --git a/src/voltronic/usb_device.cc b/src/voltronic/usb_device.cc new file mode 100644 index 0000000..ffb94d0 --- /dev/null +++ b/src/voltronic/usb_device.cc @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include <stdexcept> +#include <cstring> +#include <iostream> + +#include "../logging.h" +#include "device.h" +#include "exceptions.h" +#include "hexdump/hexdump.h" + +namespace voltronic { + +USBDevice::USBDevice(u16 vendorId, u16 productId) { + if (hid_init() != 0) + throw DeviceError("hidapi initialization failure"); + + device_ = hid_open(vendorId, productId, nullptr); + if (!device_) + throw DeviceError("failed to create hidapi device"); +} + +USBDevice::~USBDevice() { + if (device_) + hid_close(device_); + + hid_exit(); +} + +size_t USBDevice::read(u8* buf, size_t bufSize) { + int timeout = !timeout_ ? -1 : static_cast<int32_t>(getTimeLeft()); + const int bytesRead = hid_read_timeout(device_, buf, GET_HID_REPORT_SIZE(bufSize), timeout); + if (bytesRead == -1) + throw DeviceError("hidapi_read_timeout() failed"); + return bytesRead; +} + +size_t USBDevice::write(const u8* data, size_t dataSize) { + const size_t writeSize = GET_HID_REPORT_SIZE(dataSize); + + if (verbose_) { + myerr << "dataSize=" << dataSize << ", writeSize=" << writeSize; + std::cerr << hexdump((void*)data, dataSize); + } + + u8 writeBuffer[HID_REPORT_SIZE+1]{0}; + memcpy(&writeBuffer[1], data, writeSize); + + const int bytesWritten = hid_write(device_, writeBuffer, HID_REPORT_SIZE + 1); + if (bytesWritten == -1) + throw DeviceError("hidapi_write() failed"); + + return GET_HID_REPORT_SIZE(bytesWritten); +} + +u16 USBDevice::GET_HID_REPORT_SIZE(size_t size) { + return size > HID_REPORT_SIZE ? HID_REPORT_SIZE : size; +} + +}
\ No newline at end of file |