Skip to content

Commit

Permalink
sns: external driver for onewire devices
Browse files Browse the repository at this point in the history
  • Loading branch information
mcspr committed Mar 19, 2024
1 parent 89fd41d commit f7e2977
Show file tree
Hide file tree
Showing 5 changed files with 511 additions and 0 deletions.
8 changes: 8 additions & 0 deletions code/espurna/config/sensors.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
// =============================================================================
Expand Down
360 changes: 360 additions & 0 deletions code/espurna/driver_onewire.cpp
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit f7e2977

Please sign in to comment.