dotnet / iot

This repo includes .NET Core implementations for various IoT boards, chips, displays and PCBs.
MIT License
2.16k stars 581 forks source link

Support one-wire protocol #197

Closed ericstj closed 3 years ago

ericstj commented 5 years ago

During the IOT hackathon I was playing with some DS18B20 temperature sensors. These use the one-wire protocol which is supported on the RaspberyPi by remapping a GPIO pin to act as a one-wire bus. For more information see https://www.raspberrypi.org/documentation/configuration/device-tree.md https://www.kernel.org/doc/Documentation/gpio/drivers-on-gpio.txt https://www.kernel.org/doc/html/latest/driver-api/w1.html https://www.kernel.org/doc/Documentation/w1/

The DS18B20 is a pretty popular and inexpensive digital thermometer so it seems worthwhile supporting this.

I haven't yet found official Windows support for one wire, there are a few hacks that use the serial port or run a software timer in user mode but I can't imagine they are reliable. I think Windows would need a kernel driver for this like Linux has (though I could be missing something).

For now I'm going to proceed trying to implement the controller for OneWire on linux along with device binding for the DS18B20.

maloo commented 5 years ago

Hi, please keep me up to date. I'm just about to use a MAX31820 which is very similar. I use this as an ambient temp meter on my latest PCB design.

joperezr commented 5 years ago

note that #172 is already tracking the work to add the binding for DS18B20 in case you end up working in both we should close both issues

ericstj commented 5 years ago

It would seem that linux automatically installs kernel device drivers for the DS18B20 and similar devices: https://www.kernel.org/doc/Documentation/w1/slaves/w1_therm

From the docs https://www.kernel.org/doc/Documentation/w1/w1.generic

When a device is found on the bus, w1 core tries to load the driver for its family
and check if it is loaded. If so, the family driver is attached to the slave.
If there is no driver for the family, default one is assigned, which allows to perform
almost any kind of operations. Each logical operation is a transaction
in nature, which can contain several (two or one) low-level operations.

Still researching to understand what's the right way to surface all this in the GPIO library and device bindings.

maloo commented 5 years ago

Yes, currently I just read the w1_slave file. But now I want to move to .Net Core IOT and I was planning something like SerialPort.GetPortNames(), but instead return devices by family. Like OneWireController.GetTermDevices() and then maybe an Open/Close/Dispose if needed. And OneWire.TermDevice dev = OneWireController.GetTermDevices().First(); var temp = dev.ReadTemperature()

maloo commented 5 years ago

Then there are other controllers, like I2C to 1-wire ICs. So there have to be different type of controller implementations.

maloo commented 5 years ago

Looks like the kernel is handling the known bridges already.

ericstj commented 5 years ago

I believe the current design of the GPIO library is to be a lower-level component which doesn't "know" about specific device types.

I can imagine an API for OneWire controller might look like enumerating the bus'es, enumerating the devices on the bus in a generic way, and exposing any generic commands that are available for devices without a family driver.

A device binding would look like a type that could be constructed with either a handle, or a generic device representation. That type would provide the higher-level functionality, like a ReadTemperature() method. I can imagine extension methods provided by that device library that could make

So a rough idea would be like:

var buses = OneWireController.EnumerateBuses();   // returns IEnumerable<OneWireBus>.  The buses provide generic properties / methods for interacting with bus.
bus.EnumerateDevices(); // enumerates devices on a specific bus, similar to controller enumeration below
bus.Name;
bus.MaxDevices;
bus.Attempts;
bus.Timeout;
// etc.

var devices = OneWireController.EnumerateDevices();  // returns IEnumerable<OneWireDevice>.  The devices provide generic properties 

device.Path;  // device path to device
device.Name;
device.Id;
device.Open()
device.Read(...)
device.Write(...)

tempDevice = new TempDevice(device)

This is just some rough thinking based on discussions with @joperezr. Still need to do some more work and research to come up with API proposal for this.

maloo commented 5 years ago

I hope to get some spare time this weekend to start looking into the iot repo. Do you want me to do a PR using the Api you proposed? I need to do this anyway now to access my temp sensor. Or do you have a branch with some progress?

maloo commented 5 years ago

Do you expect to force a new bus device enumeration when EnumerateDevices is called? Or just list what is available in /sys? If the latter, maybe GetDevices() is a better name.

maloo commented 5 years ago

Do you think Open should block other processes from accessing the device? Or what is the intended purpose of it? Close needed too? IDisposable?

maloo commented 5 years ago

@ericstj I had some spare time tonight so I implemented a proof of concept of your API proposal. You can do what you want with it. But for now it fulfills most of your spec and works for scanning buses, enumerating buses/devices and querying thermometer devices on my Raspberry Pi 3 B+. I have only tried with a couple of MAX31820 sensors (family 28). Comments are welcome. Not sure how you handle WIP/RFC PRs, but it looks like tagging it with WIP triggers some kind of flags so it is not merged.

Rinsen commented 4 years ago

I have written a library that is not at all solving this exact issue but enables OneWire support on Raspberry Pis on both Windows and Linux via a DS2482 chip giving a bridge from i2c to OneWire bus and might help you if an add on chip is okay.

https://github.com/Rinsen/OneWire

krwq commented 4 years ago

@Rinsen that would definitely make the problem smaller for many people and provide a workaround in many situations. Support for DS2482 would be useful.

One thing to think about is to provide some kind of common interface between all devices using 1-WIRE so that this can be re-used in different contexts - perhaps something similar to what I2cDevice/SpiDevice has or perhaps just implement a Stream

Rinsen commented 4 years ago

@krwq That's a good idea, I will take a look at what I have and if that could be generalized to not depend directly on the ds2482 implementation.

I think it should be possible since the methods are just generic One Wire methods more or less.

Rinsen commented 4 years ago

@krwq what is the reasoning behind having the shared "interface" for I2cDevice and SpiDevice as an abstract base class and not an interface definition?

I have no that strong opinions but would create it as an interface if I would design it without looking into the previous types.

krwq commented 4 years ago

When we developed the first version of this library interfaces were somewhat considered anti-patterns for framework because they don't version well - any modification to the interface - including adding stuff is a breaking change. For abstract classes you can still add non-abstract methods. Since recently interfaces support default method implementation which make them closer to abstract class but also allows us to use them in the framework but at this point we haven't had scenario which lack of using interfaces was causing any design issues and abstract classes are somewhat convenient as you can add static methods like "Create" which pick the right implementation - with interfaces it's more indirect.

Rinsen commented 4 years ago

Thanks for that explanation, really makes sense :)

I have done some experiments but also looked around at some code in this repo and other places to get some inspiration and this supports my needs quite good :)

The naming is as usual a bit hard for me, I don't really like the name OneWireDevice for the host device since that's the sensor for me and then OneWireBus might be an option as in https://github.com/dotnet/iot/blob/master/src/devices/OneWire/OneWireBus.cs

But when reading the documentation about One Wire the host device is called Master and I kind of like that as the abstraction name since that is what we are controlling here.

I think the change made my library a bit more clean in the old C-port! :) https://github.com/Rinsen/OneWire/commit/7c205de5ffa403d76ad0b33ad49621b881ef0b2c

public abstract class OneWireMaster : IEnumerable<byte[]>, IDisposable
    {

        /// <summary>
        /// Reset the 1-Wire bus and return the presence of any device
        /// </summary>
        /// <returns>true : device present, false : no device present</returns>
        public abstract bool Reset();

        /// <summary>
        /// Send 1 bit of data to the 1-Wire bus 
        /// </summary>
        /// <param name="bit_value"></param>
        public abstract void WriteBit(bool bit_value);

        /// <summary>
        /// Send 8 bits of data to the 1-Wire bus
        /// </summary>
        /// <param name="byte_value">byte to send</param>
        public abstract void WriteByte(byte byte_value);

        /// <summary>
        /// Read 1 bit of data from the 1-Wire bus 
        /// </summary>
        /// <returns>true : bit read is 1, false : bit read is 0</returns>
        public abstract bool ReadBit();

        /// <summary>
        /// Read 1 bit of data from the 1-Wire bus 
        /// </summary>
        /// <returns></returns>
        public abstract byte ReadByte();

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            // Nothing to do in base class.
        }

        public abstract IEnumerator<byte[]> GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
Ellerbach commented 3 years ago

[Triage] Feeling that this has been addressed with the existing OneWire bus. Feel free to reopen in case it's not!