Browse Source

sns: external driver for onewire devices

test/dev
Maxim Prokhorov 2 months ago
parent
commit
f7e2977c45
5 changed files with 511 additions and 0 deletions
  1. +8
    -0
      code/espurna/config/sensors.h
  2. +360
    -0
      code/espurna/driver_onewire.cpp
  3. +136
    -0
      code/espurna/driver_onewire.h
  4. +3
    -0
      code/espurna/main.cpp
  5. +4
    -0
      code/espurna/main.h

+ 8
- 0
code/espurna/config/sensors.h View File

@ -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
// =============================================================================


+ 360
- 0
code/espurna/driver_onewire.cpp View File

@ -0,0 +1,360 @@
/*
OneWire MODULE
Uses PaulStoffregen/OneWire library
Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2019-2024 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
#include "espurna.h"
#if ONE_WIRE_SUPPORT
#include "driver_onewire.h"
#include <OneWire.h>
#include <forward_list>
#include <vector>
// One-Wire / 1-wire -> 1w -> w1
// (also Linux kernel naming)
namespace espurna {
namespace driver {
namespace onewire {
namespace internal {
namespace {
bool debug { false };
std::vector<PortPtr> 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<const uint8_t> 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<uint8_t> 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 [<ID>]"));
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<const uint8_t> data) {
return OneWire::crc16(data.data(), data.size());
}
bool check_crc16(Span<const uint8_t> data) {
auto span = decltype(data)(
static_cast<const uint8_t*>(data.data()),
data.size() - 2);
uint16_t crc = (data[6] << 8) | data[5];
return crc == crc16(span);
}
uint8_t crc8(Span<const uint8_t> data) {
return OneWire::crc8(data.data(), data.size());
}
bool check_crc8(Span<const uint8_t> data) {
auto span = decltype(data)(
static_cast<const uint8_t*>(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<OneWire>(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<const uint8_t> 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<const uint8_t>(data, length));
}
void Port::write(Address address, uint8_t value) {
const std::array<uint8_t, 1> data{{ value }};
write(address, make_span(data));
}
void Port::write(uint8_t value) {
internal::reset(_wire.get());
internal::skip(_wire.get());
const std::array<uint8_t, 1> data{{ value }};
internal::write_bytes(_wire.get(), make_span(data), parasite());
}
bool Port::request(Address address, Span<const uint8_t> input, Span<uint8_t> 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<uint8_t> output) {
const std::array<uint8_t, 1> 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

+ 136
- 0
code/espurna/driver_onewire.h View File

@ -0,0 +1,136 @@
/*
OneWire MODULE
Uses PaulStoffregen/OneWire library
Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2019-2024 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <array>
#include <vector>
#include <memory>
#include "libs/BasePin.h"
#include "types.h"
#include "system.h"
class OneWire;
namespace espurna {
namespace driver {
namespace onewire {
using Address = std::array<uint8_t, 8>;
struct Device {
Address address;
};
enum class Error {
Ok,
Unresponsive,
Config,
GpioUsed,
NotFound,
};
class Port {
public:
using Address = std::array<uint8_t, 8>;
using Devices = std::vector<Device>;
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<bool>(_wire);
}
Error attach(unsigned char pin, bool parasite);
void detach();
bool reset();
void write(Address address, Span<const uint8_t>);
void write(Address, const uint8_t*, size_t);
void write(Address, uint8_t value);
void write(uint8_t value);
template <size_t Size>
inline void write(Address address, const std::array<uint8_t, Size>& data) {
write(address, Span<const uint8_t>(data.data(), data.size()));
}
bool request(Address, Span<const uint8_t> input, Span<uint8_t> output);
bool request(Address address, uint8_t value, Span<uint8_t> output);
template <size_t Input, size_t Output>
bool request(Address address, const std::array<uint8_t, Input>& input, std::array<uint8_t, Output>& 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<const Device> {
return Span<const Device>(_devices.data(), _devices.size());
}
private:
Devices _search(OneWire&);
Devices search(OneWire&, unsigned char pin);
std::unique_ptr<OneWire> _wire;
unsigned char _pin { GPIO_NONE };
bool _parasite { false };
std::vector<Device> _devices;
};
using PortPtr = std::shared_ptr<Port>;
bool check_crc16(Span<const uint8_t>);
uint16_t crc16(Span<const uint8_t>);
bool check_crc8(Span<const uint8_t>);
uint8_t crc8(Span<const uint8_t>);
void dereference(PortPtr);
void reference(PortPtr);
StringView error(Error);
void setup();
} // namesapce onewire
} // namespace driver
} // namespace espurna
void oneWireSetup();

+ 3
- 0
code/espurna/main.cpp View File

@ -307,6 +307,9 @@ void setup() {
#if I2C_SUPPORT
i2cSetup();
#endif
#if ONE_WIRE_SUPPORT
oneWireSetup();
#endif
#if RFB_SUPPORT
rfbSetup();
#endif


+ 4
- 0
code/espurna/main.h View File

@ -68,6 +68,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "i2c.h"
#endif
#if ONE_WIRE_SUPPORT
#include "driver_onewire.h"
#endif
#if INFLUXDB_SUPPORT
#include "influxdb.h"
#endif


Loading…
Cancel
Save