From f4b139baf42e7480cb34a2db7fb190431914c950 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Sun, 17 Mar 2024 22:19:02 +0300 Subject: [PATCH] sns: external driver for onewire devices --- code/espurna/config/sensors.h | 8 + code/espurna/driver_onewire.cpp | 358 ++++++++++++++++++++++++++++++++ code/espurna/driver_onewire.h | 136 ++++++++++++ code/espurna/main.cpp | 3 + code/espurna/main.h | 4 + 5 files changed, 509 insertions(+) create mode 100644 code/espurna/driver_onewire.cpp create mode 100644 code/espurna/driver_onewire.h diff --git a/code/espurna/config/sensors.h b/code/espurna/config/sensors.h index f77621060d..b9f49480dc 100644 --- a/code/espurna/config/sensors.h +++ b/code/espurna/config/sensors.h @@ -1438,6 +1438,14 @@ #define I2C_PERFORM_SCAN 1 // Perform a bus scan on boot #endif +// ----------------------------------------------------------------------------- +// OneWire +// ----------------------------------------------------------------------------- + +#ifndef ONE_WIRE_SUPPORT +#define ONE_WIRE_SUPPORT 0 // disabled OneWire support by default +#endif + // ============================================================================= // Configuration helpers // ============================================================================= diff --git a/code/espurna/driver_onewire.cpp b/code/espurna/driver_onewire.cpp new file mode 100644 index 0000000000..32b739f055 --- /dev/null +++ b/code/espurna/driver_onewire.cpp @@ -0,0 +1,358 @@ +/* + +OneWire MODULE + +Uses PaulStoffregen/OneWire library + +Copyright (C) 2017-2019 by Xose Pérez +Copyright (C) 2019-2024 by Maxim Prokhorov + +*/ + +#include "espurna.h" + +#if ONE_WIRE_SUPPORT + +#include "driver_onewire.h" + +#include +#include + +// One-Wire / 1-wire -> 1w -> w1 +// (also Linux kernel naming) + +namespace espurna { +namespace driver { +namespace onewire { +namespace internal { +namespace { + +bool debug { false }; +std::vector references; + +bool reset(OneWire* wire) { + return wire->reset() != 0; +} + +void skip(OneWire* wire) { + wire->skip(); + if (debug) { + DEBUG_MSG_P(PSTR("[W1] ROM skip\n")); + } +} + +void select(OneWire* wire, Address address) { + wire->select(address.data()); + if (debug) { + DEBUG_MSG_P(PSTR("[W1] Selected %s\n"), hexEncode(address).c_str()); + } +} + +void write_bytes(OneWire* wire, Span data, bool power = false) { + wire->write_bytes(data.data(), data.size(), power); + if (debug) { + DEBUG_MSG_P(PSTR("[W1] %s-> %s \n"), + power ? "P " : "", + hexEncode(data).c_str()); + } +} + +void read_bytes(OneWire* wire, Span data) { + wire->read_bytes(data.data(), data.size()); + if (debug) { + DEBUG_MSG_P(PSTR("[W1] <- %s\n"), hexEncode(data).c_str()); + } +} + +} // namespace +} // namespace internal + +Port::Port() = default; + +Port::~Port() { + detach(); +} + +void dereference(PortPtr port) { + internal::references.erase( + std::remove( + internal::references.begin(), internal::references.end(), port)); +} + +void reference(PortPtr port) { + const auto it = std::find( + internal::references.begin(), internal::references.end(), port); + if (it != internal::references.end()) { + return; + } + + internal::references.push_back(port); +} + +#if DEBUG_SUPPORT +namespace debug { +namespace { + +void setup() { + STRING_VIEW_INLINE(Debug, "w1Debug"); + internal::debug = getSetting(Debug, false); +} + +} // namespace +} // namespace +#endif + +#if TERMINAL_SUPPORT +namespace terminal { +namespace { + +STRING_VIEW_INLINE(List, "W1"); + +void list(::terminal::CommandContext&& ctx) { + size_t index = 0; + for (auto& reference : internal::references) { + ctx.output.printf_P( + PSTR("w1/%zu\t{Pin=%hhu Parasite=#%c Devices=%zu}\n"), + index++, + reference->pin(), + reference->parasite() ? 'y' : 'n', + reference->devices()); + } +} + +STRING_VIEW_INLINE(Devices, "W1.DEVICES"); + +void devices(::terminal::CommandContext&& ctx) { + size_t id = 0; + if ((internal::references.size() > 1) && ctx.argv.size() != 2) { + terminalError(ctx, F("W1.DEVICES []")); + return; + } + + if (internal::references.size() > 1) { + if (!tryParseId(ctx.argv[1], internal::references.size(), id)) { + terminalError(ctx, F("Invalid port ID")); + return; + } + } + + auto reference = internal::references[id]; + + size_t index = 0; + for (auto& device : *reference) { + ctx.output.printf_P(PSTR("device%zu\t{Address=%s}\n"), + index++, hexEncode(device.address).c_str()); + } +} + +static constexpr ::terminal::Command Commands[] PROGMEM { + {List, list}, + {Devices, devices}, +}; + +void setup() { + espurna::terminal::add(Commands); +} + +} // namespace +} // namespace terminal +#endif + +uint16_t crc16(Span data) { + return OneWire::crc16(data.data(), data.size()); +} + +bool check_crc16(Span data) { + auto span = decltype(data)( + static_cast(data.data()), + data.size() - 2); + + uint16_t crc = (data[6] << 8) | data[5]; + return crc == crc16(span); +} + +uint8_t crc8(Span data) { + return OneWire::crc8(data.data(), data.size()); +} + +bool check_crc8(Span data) { + auto span = decltype(data)( + static_cast(data.data()), + data.size() - 1); + + return data.back() == crc8(span); +} + +Error Port::attach(unsigned char pin, bool parasite) { + if (pin == GPIO_NONE) { + return Error::Config; + } + + if (!gpioLock(pin)) { + return Error::GpioUsed; + } + + auto wire = std::make_unique(pin); + + auto devices = search(*wire, pin); + if (!devices.size()) { + gpioUnlock(pin); + return Error::NotFound; + } + + _wire = std::move(wire); + _pin = pin; + _parasite = parasite; + + _devices = std::move(devices); + + hardwareGpioIgnore(pin); + + return Error::Ok; +} + +void Port::detach() { + if (_wire) { + gpioUnlock(_pin); + _wire.reset(nullptr); + } + + _devices.clear(); + _pin = GPIO_NONE; + _parasite = false; +} + +Port::Devices Port::_search(OneWire& wire) { + Address address; + + wire.reset(); + wire.reset_search(); + + Devices out; + + while (wire.search(address.data())) { + if (wire.crc8(address.data(), address.size() - 1) != address.back()) { + continue; + } + + Device device; + device.address = address; + out.emplace_back(std::move(device)); + } + + return out; +} + +Port::Devices Port::search(OneWire& wire, unsigned char pin) { + Devices out; + + out = _search(wire); + bool pulled_up{ false }; + + // If no devices found check again pulling up the line + if (!out.size()) { + pinMode(pin, INPUT_PULLUP); + pulled_up = true; + out = _search(wire); + } + + // ...and do not forget to go back to the way it was before + if (pulled_up) { + pinMode(pin, INPUT); + } + + return out; +} + +bool Port::reset() { + return _wire->reset() == 0; +} + +void Port::write(Address address, Span data) { + internal::reset(_wire.get()); + internal::select(_wire.get(), address); + internal::write_bytes(_wire.get(), data, parasite()); +} + +void Port::write(Address address, const uint8_t* data, size_t length) { + write(address, Span(data, length)); +} + +void Port::write(Address address, uint8_t value) { + const std::array data{{ value }}; + write(address, make_span(data)); +} + +void Port::write(uint8_t value) { + internal::reset(_wire.get()); + internal::skip(_wire.get()); + + const std::array data{{ value }}; + internal::write_bytes(_wire.get(), make_span(data), parasite()); +} + +bool Port::request(Address address, Span input, Span output) { + //if (!// + // return false; + //} + internal::reset(_wire.get()); + + internal::select(_wire.get(), address); + internal::write_bytes(_wire.get(), input); + internal::read_bytes(_wire.get(), output); + + return internal::reset(_wire.get()); +} + +bool Port::request(Address address, uint8_t value, Span output) { + const std::array input{ value }; + return request(address, make_span(input), output); +} + +StringView error(Error error) { + StringView out; + + switch (error) { + case Error::Ok: + out = STRING_VIEW("OK"); + break; + + case Error::NotFound: + out = STRING_VIEW("Not found"); + break; + + case Error::Unresponsive: + out = STRING_VIEW("Device does not respond"); + break; + + case Error::GpioUsed: + out = STRING_VIEW("GPIO Already Used"); + break; + + case Error::Config: + out = STRING_VIEW("Invalid Configuration"); + break; + + } + + return out; +} + +void setup() { +#if DEBUG_SUPPORT + debug::setup(); +#endif +#if TERMINAL_SUPPORT + terminal::setup(); +#endif +} + +} // namespace onewire +} // namespace driver +} // namespace espurna + +void oneWireSetup() { + espurna::driver::onewire::setup(); +} + +#endif diff --git a/code/espurna/driver_onewire.h b/code/espurna/driver_onewire.h new file mode 100644 index 0000000000..5e0d2acd6c --- /dev/null +++ b/code/espurna/driver_onewire.h @@ -0,0 +1,136 @@ +/* + +OneWire MODULE + +Uses PaulStoffregen/OneWire library + +Copyright (C) 2017-2019 by Xose Pérez +Copyright (C) 2019-2024 by Maxim Prokhorov + +*/ + +#pragma once + +#include +#include + +#include +#include +#include + +#include "libs/BasePin.h" +#include "types.h" +#include "system.h" + +class OneWire; + +namespace espurna { +namespace driver { +namespace onewire { + +using Address = std::array; + +struct Device { + Address address; +}; + +enum class Error { + Ok, + Unresponsive, + Config, + GpioUsed, + NotFound, +}; + +class Port { +public: + using Address = std::array; + using Devices = std::vector; + + Port(); + ~Port(); + + Port(const Port&) = delete; + Port& operator=(const Port&) = delete; + + Port(Port&&) = default; + Port& operator=(Port&&) = default; + + explicit operator bool() const { + return static_cast(_wire); + } + + Error attach(unsigned char pin, bool parasite); + void detach(); + + bool reset(); + + void write(Address address, Span); + void write(Address, const uint8_t*, size_t); + + void write(Address, uint8_t value); + void write(uint8_t value); + + template + inline void write(Address address, const std::array& data) { + write(address, Span(data.data(), data.size())); + } + + bool request(Address, Span input, Span output); + bool request(Address address, uint8_t value, Span output); + + template + bool request(Address address, const std::array& input, std::array& output) { + return request(address, make_span(input), make_span(output)); + } + + unsigned char pin() const noexcept { + return _pin; + } + + bool parasite() const noexcept { + return _parasite; + } + + auto begin() const noexcept -> Devices::const_iterator { + return _devices.cbegin(); + } + + auto end() const noexcept -> Devices::const_iterator { + return _devices.cend(); + } + + auto devices() const noexcept -> Span { + return Span(_devices.data(), _devices.size()); + } + +private: + Devices _search(OneWire&); + Devices search(OneWire&, unsigned char pin); + + std::unique_ptr _wire; + unsigned char _pin { GPIO_NONE }; + bool _parasite { false }; + + std::vector _devices; +}; + +using PortPtr = std::shared_ptr; + +bool check_crc16(Span); +uint16_t crc16(Span); + +bool check_crc8(Span); +uint8_t crc8(Span); + +void dereference(PortPtr); +void reference(PortPtr); + +StringView error(Error); +void setup(); + +} // namesapce onewire +} // namespace driver +} // namespace espurna + +void oneWireSetup(); diff --git a/code/espurna/main.cpp b/code/espurna/main.cpp index 76066a838c..95bbe5d126 100644 --- a/code/espurna/main.cpp +++ b/code/espurna/main.cpp @@ -307,6 +307,9 @@ void setup() { #if I2C_SUPPORT i2cSetup(); #endif + #if ONE_WIRE_SUPPORT + oneWireSetup(); + #endif #if RFB_SUPPORT rfbSetup(); #endif diff --git a/code/espurna/main.h b/code/espurna/main.h index 71f6dd0c63..336b6dbd28 100644 --- a/code/espurna/main.h +++ b/code/espurna/main.h @@ -68,6 +68,10 @@ along with this program. If not, see . #include "i2c.h" #endif +#if ONE_WIRE_SUPPORT +#include "driver_onewire.h" +#endif + #if INFLUXDB_SUPPORT #include "influxdb.h" #endif