Giter Site home page Giter Site logo

dotnet / iot Goto Github PK

View Code? Open in Web Editor NEW
2.1K 305.0 568.0 95.33 MB

This repo includes .NET Core implementations for various IoT boards, chips, displays and PCBs.

License: MIT License

Batchfile 0.05% Shell 1.19% PowerShell 1.81% C# 96.74% CMake 0.21%

iot's Introduction

Gitter Discord

.NET IoT Libraries

.NET can be used to build applications for IoT devices and scenarios. IoT applications typically interact with sensors, displays and input devices that require the use of GPIO pins, serial ports or similar hardware.

Important

This is the GitHub repo for the libraries. You might want to start with our official documentation.

This repository contains the System.Device.Gpio library and implementations for various boards like Raspberry Pi and Hummingboard.

The repository also contains Iot.Device.Bindings, a growing set of community-maintained device bindings for IoT components.

Note

This repository is still in experimental stage and all APIs are subject to changes.

Hardware requirements

While most of the bindings and examples in this project require and are designed to support specific hardware (such as LCD displays, temperature sensors, single-board computers, microcontrollers, etc.), the library itself tries to be as hardware-independent as possible. Some bindings are even written to showcase the use of IOT interfaces with hardware that is already present in normal desktop computers (such as keyboards or CPU temperature sensors). So to get started, you do not need expensive hardware. Or you can start out with cheap stuff, such as an Arduino Uno.

.NET Versions

Both libraries in this repository are cross-targeting .NET Standard 2.0, .NET Core 3.1, and .NET 6.0. They can be used from any project targeting .NET Core 2.0 or higher, and also from .NET Framework or mono. If you are looking at a Micro Controller Unit (MCU) support, check .NET nanoFramework.

The sample projects target the latest stable .NET Version. This applies to the sample projects with each device as well as the example projects on the /samples directory.

How to Install

From Visual Studio, you can just add a nuget by searching for System.Device.Gpio and Iot.Device.Bindings.

If you need, you can also install the latest daily pre-release build of the .NET System.Device.Gpio and Iot.Device.Bindings NuGet packages from the Azure artifacts feed.

NuGet.exe

nuget install System.Device.Gpio -PreRelease -Source https://pkgs.dev.azure.com/dotnet/IoT/_packaging/nightly_iot_builds/nuget/v3/index.json
nuget install Iot.Device.Bindings -PreRelease -Source https://pkgs.dev.azure.com/dotnet/IoT/_packaging/nightly_iot_builds/nuget/v3/index.json

Official Build Status

Build Status

.NET CLI

dotnet add package System.Device.Gpio --source https://pkgs.dev.azure.com/dotnet/IoT/_packaging/nightly_iot_builds/nuget/v3/index.json
dotnet add package Iot.Device.Bindings --source https://pkgs.dev.azure.com/dotnet/IoT/_packaging/nightly_iot_builds/nuget/v3/index.json

Contributing

For information on how to build this repository and to add new device bindings, please head out to Contributing.

Please contribute. We are primarily interested in the following:

  • Improving quality and capability of the drivers for supported boards.
  • Implementations for additional boards.
  • .NET device bindings for a wide variety of sensors, chips, displays and other components.
  • Request a device binding or protocol that you need for your project (file an issue).
  • Links to blog posts or tweets that showcase .NET Core being used for great IoT scenarios (file an issue).

Getting Started

After installing, please see the following areas to learn more:

All bindings (src/devices) contains a samples folder where you will find examples on how to use each of the devices, sensor, displays and other components.

Important: Please make sure you are using tag that correspond to your package version to browse and reuse the samples' code.

select branch

Once you have selected the right branch, you can browse the repository. The main branch contains code that is always the latest and may not been yet released to a package. So if you are using the 1.2 package, please select 1.2 tag before browsing the source code.

Tutorials

Community

This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. For more information, see the .NET Foundation Code of Conduct.

License

.NET (including the iot repo) is licensed under the MIT license.

iot's People

Contributors

anphel31 avatar buyaa-n avatar danmoseley avatar dependabot[bot] avatar dotnet-maestro[bot] avatar ellerbach avatar fehdem avatar frankenslag avatar garciaolais avatar humj0218 avatar jdbruner avatar jeremykuhne avatar jimmys20 avatar johntasler avatar joperezr avatar joshfree avatar krwq avatar markciliavincenti avatar nahueltaibo avatar pgrawehr avatar raffaeler avatar richlander avatar robintty avatar safern avatar sergeyrazmyslov avatar shaggygi avatar smdn avatar tibel avatar wsad4ryba avatar zhanggaoxing avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

iot's Issues

[Proposal] Create a new IotDev CLI based on System.CommandLine

Core Objective

If possible, I recommend we create a new console project called IotDev (short for IoT Device Development). The intention is to utilize the new System.CommandLine and deprecate DeviceApiTester at some point. IotDev can be stored at the same folder location and would enable to continue adding features to current DeviceApiTester while learning/adding to IotDev. This might be a good opportunity to "dog-food" and provide feedback to the System.CommandLine repo for bugs/features, as well.

Early notes about System.CommandLine

  • Wiki found here: https://github.com/dotnet/command-line-api/wiki
  • This, of course, is in the very early alpha stage. Seems to have potential, though.
  • I like the command > subcommand feature where you can have something like...
    iotdev i2c write [options]
    iotdev spi read -b 1 -c 4
    This appears to be much better how help info can be displayed while digging down to specific command level.
  • There is a nifty suggestions feature.
  • It also includes a [debug] directive for attaching.
  • There is a beginning app model codename DragonFruit to try out. Also noticed another called StarFruit (which might be similar to the current CLI package that uses attributes).

Random Thoughts

  • IMHO, IotDev seems like a better "branding" and flows better at the command line.
    DeviceApiTester script-run -f MyIotScript.csx
    versus...
    iotdev script run -f MyIotScript.csx

Future Work

There are a few things that will need to be completed. I'm sure there are other items I'm missing. Chime in.

  • Create and add an initial solution with bare projects as a starting point.

  • Determine the approach how each area should be tested. This would go in the IotDev.Tests project, of course. Also might be a good exercise to see how to mock interfaces.

  • Setup any CI/CD infrastructure components needed for repo. Not my expertise ๐Ÿ˜•

  • Setup a NuGet package.

    This will be helpful to support an IotDev Global Tool which makes it easy to install on development boards.

  • Determine structure of commands and best approach utilizing System.CommandLine.

  • Migrate commands already defined in DeviceApiTester

  • Create README with details on usage.

[Proposal] Provide a Software implementation of the SPI protocol.

Core Objective

As a device binding producer, I would like the ability to instantiate the binding with a GPIO SPI implementation instead of redundant code to bit-bang logic within each binding. This would be similar to how a UnixSpiDevice or Windows10SpiDevice is currently used to create a binding.

NOTE: There is a "very early" prototype located here.

Use the Mcp23xxx binding and a UnixSpiDevice for example:

// With UnixSpiDevice.
var settings = new SpiConnectionSettings(0, 0)
{
    ClockFrequency = 1000000,
    Mode = SpiMode.Mode0
};

var spiDevice = new UnixSpiDevice(settings);
var mcp23xxx = new Mcp23xxx(spiDevice);

Having a GpioSpiDevice offering, there could be something like the following:
NOTE: chipSelectActiveLow is active low in most cases, but some chips and hardware designs use active high.

public GpioSpiDevice(int sclk, int miso, int mosi, SpiConnectionSettings connectionSettings, bool chipSelectActiveLow = true)

Therefore, you could pass in the GpioSpiDevice like below:
NOTE: Notice different modes can also be used and low-level should accommodate (assuming the device supports that mode ๐Ÿ˜„). Frequency (clock delay) is not currently coded in prototype.

// With GpioSpiDevice.
var settings = new SpiConnectionSettings(0, 25)
{
    ClockFrequency = 1000000,
    Mode = SpiMode.Mode3
};

var spiDevice = new GpioSpiDevice(18, 23, 24, settings);
var mcp23xxx = new Mcp23xxx(spiDevice);

GpioSpiDevice would be located with the other current SpiDevice drivers.

System
  Device
    Spi
      Drivers
        GpioSpiDevice.cs  // Or GpioSpiDriver depending on naming scheme
        UnixSpiDevice.Linux.cs
        ...

This might help with having abstract classes using the different types of interface devices. For example...

  • GpioSpiDevice, UnixSpiDevice and Windows10SpiDevice could be passed to an Mcp23Sxx device where 'S' represents SPI devices.
  • GpioI2cDevice (a future proposal), UnixI2cDevice and Windows10I2cDevice could be passed to an Mcp230xx device where '0' represents I2C devices.

A few thoughts to point out

  • busId doesn't really mean anything in this scenario. It could be ignored by creating an overloaded constructor new SpiConnectionSettings(chipSelectLine).

  • chipSelectActiveLow seems to be more for settings so it could be added to SpiConnectionSettings and defaulted to TRUE instead of passing into GpioSpiDevice constructor. So the following could be used:
    new SpiConnectionSettings(chipSelectLine, chipSelectActiveLow). This might confuse peeps when using for native interface and configured elsewhere. I'm on the fence with this.

  • There is a proposal for a GPIO Toggle helper where it would help support logic in other areas, as well.

  • It would be nice to have some bit helper methods to perform MSB/LSB as this varies with components.

  • It would be nice to have a helper for PinValue.Low = false or PinValue.High = true. There is a proposal related to this here: #121

  • One other thing to point out, is how much code can be reduced like in the Mcp3008 binding. The current code to read via GPIO SPI shown below:

private int ReadGpio(int channel, InputConfiguration inputConfiguration)
{
    while (true)
    {
        int result = 0;
        byte command = GetConfigurationBits(channel, inputConfiguration);

        _controller.Write(_cs, PinValue.High);
        _controller.Write(_clk, PinValue.Low);
        _controller.Write(_cs, PinValue.Low);

        for (int cnt = 0; cnt < 5; cnt++)
        {
            if ((command & 0b1000_0000) > 0)
            {
                _controller.Write(_mosi, PinValue.High);
            }
            else
            {
                _controller.Write(_mosi, PinValue.Low);
            }

            command <<= 1;
            _controller.Write(_clk, PinValue.High);
            _controller.Write(_clk, PinValue.Low);
        }

        for (int cnt = 0; cnt < 12; cnt++)
        {
            _controller.Write(_clk, PinValue.High);
            _controller.Write(_clk, PinValue.Low);
            result <<= 1;

            if (_controller.Read(_miso) == PinValue.High)
            {
                result |= 0b0000_0001;
            }
        }

        _controller.Write(_cs, PinValue.High);

        result >>= 1;
        return result;
    }
}

Basically, can use the same code as the ReadSpi method (replacing with the GpioSpiDevice, of course ๐Ÿ˜„):

private int ReadSpi(int channel, InputConfiguration inputConfiguration)
{
    byte configurationBits = GetConfigurationBits(channel, inputConfiguration);
    byte[] writeBuffer = new byte[] { configurationBits, 0, 0 };
    byte[] readBuffer = new byte[3];

    _spiDevice.TransferFullDuplex(writeBuffer, readBuffer);

    int result = (readBuffer[0] & 0b0000_0001) << 9;
    result |= (readBuffer[1] & 0b1111_1111) << 1;
    result |= (readBuffer[2] & 0b1000_0000) >> 7;
    result = result & 0b0011_1111_1111;
    return result;
}

As mentioned, this is just a prototype, but works. I'm still testing bits/modes and such.

Thoughts?

Create a standard IoT Device Binding template

Core Objective

To establish a rich set of quality .NET bindings making it straightforward to use .NET and IoT devices, there needs to be a rich offering of supporting tooling. And while it will be very difficult to manage bindings (code, docs, etc.) across the range of producers, we can lower the "how to get started bar" that will hopefully make bindings more consistent as the ecosystem grows. This objective is part of that initiative where it attempts to explain the goal along with various action items.

Current Binding Structure

Below shows the current device binding structure based on the Device Binding README.

NOTE: [IotDeviceBinding] represents the actual name of binding. For example, Mcp3008, Mcp23xxx, etc.

iot/
  src/
    devices/
      [IotDeviceBinding]/
        [IotDeviceBinding].csproj
        [IotDeviceBinding].cs
        README.md
        samples/
          [IotDeviceBinding].Sample.csproj
          [IotDeviceBinding].Sample.cs
          README.md
        tests/   <--  Tests are optional, but if present they should be layed out like this.
          [IotDeviceBinding].Tests.csproj
          [IotDeviceBinding].Tests.cs

Future Work

One approach to create a consistent structure with ease is to offer an IoT Device Binding template based on the .NET Templates.

There is a PR #114 that provides a template as an exercise. If this PR is accepted, there are a few action items that need to be completed. I'm sure I am not thinking of everything so please chime in to help this effort.

  • Setup a NuGet package for the template.

    Currently, the user is required to download the project to local machine and point to a folder when executing the dotnet new command. It would be more efficient if there is an official package.

  • Verify and improve the template.json file.

    This currently includes the basics. What else is missing?

  • (PR #136) Update overall binding markdown explaining the template and how to get started.

  • (PR #136) Deprecate the main binding README markdown.

    This is now included within the template structure and is no longer needed. It is a better approach as it will be automatically provided instead of forcing the user to make a copy of the markdown into their project.

  • Review and finalize the main binding README markdown.

    The binding markdown should be brief and specific about the binding's device(s). This markdown should only include a description, related datasheets, compatible components, notes on the API basics & gotchas, and references. See Mcp23xxx README as a good starting example. NOTE: This markdown should not include anything related to respective sample work as there is another README below that should house that information.

    This markdown is located as shown below:

  iot/
  src/
    devices/
      IotDeviceBinding/
        IotDeviceBinding.csproj
        IotDeviceBinding.cs
        [README.md]  // This represents the main Binding Markdown file.
  • Review, determine and finalize the structure for binding samples markdown.

    There should be a description to help users decide if the sample provides what they are looking for.

    While it is not prohibited, this markdown should not include the basics like creating interface connections (e.g. new I2cConnectionSettings(), etc. ). This will minimize redundant steps throughout the binding samples. There will eventually be a System.Device.* API Overview doc provided that explains all the introductory stuff.

    Fritzing diagrams should be added here and show connections as clearly as possible. This helps limit the content on pin-to-pin writing. Users will most likely be referencing datasheets/pinouts, so they can find info there (usually on page 1 in PDFs).

    This markdown is located as shown below:

iot/
  src/
    devices/
      IotDeviceBinding/
        samples/
          IotDeviceBinding.Sample.csproj
          IotDeviceBinding.Sample.cs
          [README.md]   // This represents the Binding Samples Markdown file
  • Review, determine and finalize content that should be in code files ([IotDeviceBinding].cs, [IotDevinceBinding].Sample.cs, [IotDeviceBinding]Tests.cs, etc.).

    This should include common code a user would need to start adding code to interact with device. Below are a few examples:

    • using statements
    • Helper methods to get device binding objects and interfaces. For example,
    Mcp23xxx mcp23xxx = GetMcp23xxxWithSpi();
  • Investigate if possible to use the template within Visual Studio.

    • Would be nice to right-click a folder in Solution Explorer > Add New IoT Device Binding ๐Ÿ˜„
    • This item could be removed and added to new issue as it is not required to complete this initial proposal.
    • A separate VSIX might be needed.
    • Looks like MS is working on a solution here: dotnet/templating#1209 (comment)

Provide APIs from WIndows IoT Extensions as .NET Core

To enable .NET Core to be platform, which run on any device, it is necessary to provide support for diverse IoT scenarios.

For example, we already have a set of APIs related to BLE, various sensors and GPIO. They all are implemented as a part of Windows IoT Extensions and unfortunately available for UWP only.
How about implementing them as native .NET Core API and integrating them as standard ?

#See also: dotnet/standard#358

Add scripting commands to DeviceApiTester

This is a placeholder for discussions related to scripting commands using the DeviceApiTester. There is an initial PR #101 as a starting point.

  1. What other commands/attributes could be added that would be helpful related to scripting?
  2. Would support for Visual Basic and F# language be useful? Is it possible?
  3. Should this be added to DeviceApiTester README?

Support for common IoT libraries

This is not for a specific feature, but more to gain synergy on possibly getting other core APIs created or central location for related "How-Tos" that would help while coding APIs/apps more specific to IoT/hardware.

For example, Binary Coded Decimal (BCD) conversions are common in many devices for displays and dates. While this conversion is simple, it might seem redundant to include helper methods in each binding API where needed. A new specific API for this might be overkill, so maybe it would be better to have topic created in a markdown file as a quick reference.

It would be nice to offer support down to the bit level. Here is a previous thread on CoreFxLab repo that appears to be a good start for this.

Thoughts? /cc @terrajobst @stephentoub

Should GPIO Pin enums and events be renamed with Gpio prefix

It seems like the following enums and events would be named with Gpio prefix. Even though they are in the namespace of System.Device.Gpio, it would make consistent with others (e.g. GpioController, GpioDriver, etc.)

Current Renamed
PinChangeEventHandler GpioPinChangeEventHandler
PinEventTypes GpioPinEventTypes
PinMode GpioPinMode
PinNumberingScheme GpioPinNumberingScheme
PinValue GpioPinValue
PinValueChangedEventArgs GpioPinValueChangedEventArgs

There is also WaitForEventResult, but not sure what would be appropriate for this one.

I2C API should support Restart/Repeat condition

It appears you currently can only read and write using I2C API where it provides a Start/Stop at beginning and end of command. Many I2C components will work by writing the address with a Write command and then follow with a ReadByte() or Read() for multiples. However, there are components that require a Reset condition between writing and reading.

@bigdnf mentioned this in #111

Windows.Devices.I2c appears to have related support with a WriteRead():
https://docs.microsoft.com/en-us/uwp/api/windows.devices.i2c.i2cdevice.writeread#Windows_Devices_I2c_I2cDevice_WriteRead_System_Byte___System_Byte___

It appears the UnixI2cDevice has some started work here. Have not found anything public to use similar to SPI TransferFullDuplex.

Couple Linux references

Add a contribution doc

For Preview 1, we should write up a doc that will show contributors how to add bindings for new boards and new sensors.

[Proposal] Add ReadBool and WriteBool to GpioController

This proposal is an addition to the GpioController class that include helper methods for working with the different pin values and their respective boolean value.

Rationale and Usage

There are many times you need to determine the bit value and must perform conversions of setting/clearing the bit. Pins sometimes represent bits within a value. The proposal would help from having to write redundant code in apps and device bindings. For example, reading pin values while clocking data to/from a device.

Example

The following writes the different bits to a pin.

BitArray bitArray = new BitArray(new byte[] { 0x1A });

foreach (bool bitValue in bitArray)
{
  if (bitValue)
  {
    _controller.Write(1, PinValue.High);
  }
  else
  {
    _controller.Write(1, PinValue.Low);
  }
  
  // Other logic...
}

The proposed changes would allow the code to be written as:

BitArray bitArray = new BitArray(new byte[] { 0x1A });

foreach (bool bitValue in bitArray)
{
  _controller.WriteBool(1, bitValue);

  // Other logic...
}

Proposed Changes

The proposed changes are to add the following helper methods within GpioController class.

/// <summary>
/// Reads the current boolean value of a pin.
/// </summary>
/// <param name="pinNumber">The pin number in the controller's numbering scheme.</param>
/// <returns>The boolean value of the pin.</returns>
public bool ReadBool(int pinNumber)
{
    return PinValueToBool(_controller.Read(pinNumber));
}

/// <summary>
/// Writes a boolean value to a pin.
/// </summary>
/// <param name="pinNumber">The pin number in the controller's numbering scheme.</param>
/// <param name="value">The boolean value to be written.</param>
public void WriteBool(int pinNumber, bool value)
{
    _controller.Write(pinNumber, BoolToPinValue(value));
}

/// <summary>
/// Converts a pin value to a boolean value.
/// </summary>
/// <returns>The boolean value of the pin value.</returns>
public static bool PinValueToBool(PinValue value)
{
    return value == PinValue.High;
}

/// <summary>
/// Converts a boolean value to a pin value.
/// </summary>
/// <returns>The pin value of the boolean value.</returns>
public static PinValue BoolToPinValue(bool value)
{
    return value ? PinValue.High : PinValue.Low;
}

Open Questions

  1. I'm not sure the proposed method names are the best. I used WriteBool/ReadBool as you can't overload Read to return PinValue or bool. What would be the best names?

  2. Should there be an option as a bool? where null represents closed pin or high-impedance state? I don't think so as that makes things a little more complex. Low (false) and High (true) should be good enough.

Dependency Injection for Implementations

Please consider a lightweight IoC/DependencyInjection for driver implementations, ie DeviceDriverCollection.Add(options =>{ ... }); similar to the IServiceCollection so that a developer may register driver implementation base on custom logic and allow injection of the implementation into the constructor of the driver consumer(s)

Should driver abstracts be moved to respective Drivers folder/namespace

It seems like a better fit for the abstract Drivers be moved to the Drivers folder along with updating their namespace.

GPIO

System
  Device
    Gpio
      Drivers
        GpioDriver.cs  // with namespace System.Device.Gpio.Drivers

I2C

or I2cDriver.cs based on #118

System
  Device
    I2c
      Drivers
        I2cDevice.cs  // with namespace System.Device.I2c.Drivers

PWM

System
  Device
    Pwm
      Drivers
        PwmDevice.cs  // with namespace System.Device.Pwm.Drivers

SPI

or SpiDriver.cs based on #119

System
  Device
    Spi
      Drivers
        SpiDevice.cs  // with namespace System.Device.Spi.Drivers

Create System.Device.Gpio Overview doc

It would be beneficial to create a System.Device.Gpio Overview markdown to help newbies get started along with more technical details on infrastructure. It could include the following topics:

NOTE: The intent of this overview is to focus on the following:

  1. Intro examples of creating a protocol connection (e.g. I2C, SPI) and instantiating the respective device (e.g. I2cDevice, SpiDevice). This would be more focused on how it relates to interface topics (e.g. buses, addressing, etc.); not so much on particular binding.
  2. Understanding how the OS and/or specific device is determined.
  3. Understanding of external bindings.
  4. Other topics?

โš ๏ธ It might be more appropriate to document certain topics in Microsoft Docs to keep consistent with the overall .NET ecosystem.

Change polling mechanism to use poll instead of epoll

per @stephentoub in #45 (review)

This is called the "unix" driver, but many unixes don't have epoll, e.g. macOS, FreeBSD, etc. They have an alternate mechanism called kqueues.
I'm also unclear why we're using epoll rather than just poll (which does exist everywhere). epoll is intended for scalability, where you need to wait for / listen to events on lots of file descriptors at once (hundreds, thousands, etc.); it doesn't really provide any benefits, just complexity, when listening for events on one or even a couple. Is there a reason we can't just use poll? Does epoll actually do something special for the file descriptors in question?

Should it be recommended that device binding samples utilize a CLI

It might be helpful to have guidance recommending/using the new System.CommandLine (or similar) API while developing device binding samples. For example... enable, disable, write message to LCD, etc.

This would make it easier to use commands to perform certain features of a sample or breadboard hardware configuration. Currently, you need to comment/uncomment what you wish to execute and deploy again to dev board.

Mcp23xxx disable // Send command to turn master controller pin low to reset the external Mcp23xxx.
Mcp23xxx read-registers // Read/display status of all internal registers.

I have not studied too much, but appears you can include a directive to the middleware. So you could possibly override things like the type of interface (SpiDevice) to use.

Mcp23xxx [unix-i2c-device] read-register // Use the UnixI2cDevice
Mcp23xxx [gpio-spi-device] read-registers // Use the GpioSpiDevice

You could also just have an option to parse out to determine what to use, as well. Needs more thinking through.

Mcp23xxx read-register -i unix-i2c-device // Use the UnixI2cDevice
Mcp23xxx read-registers -i gpio-spi-device // Use the GpioSpiDevice

Just a suggestion.

Move System.Device.Gpio to use the new Api Surface

This issue is going to track the work of moving the implementation of System.Device.Gpio to the Api surface that we are intending to ship. It also includes cleaning up the code and productizing it.
The steps to complete this are:

  • Port the enums and Controller code into the new Api shape. (Done in #20)
  • Port the Unix Portable/Generic Driver (Done in #23 and #45)
  • Port the Unix Raspberry Pi Optimized Driver
    - [ ] Port the Unix Hummingboard Optimized Driver Not required for V1 any longer.
  • Port Spi classes (Done in #53)
  • Port I2C classes (Done in #51)

[Proposal] Add System.Device.Gpio.Clock()

Referencing #107 as it spawned some thinking on other out-of-box GPIO support.

In addition to Toggle(), it might be worth investigating a Clock() function. For example,

var controller = new GpioController();
int clkPin = 8;
controller.OpenPin(clkPin, PinMode.Output);  // Setup clock pin.
controller.Write(clkPin, PinValue.High); // Set to initial high state.

// Do other logic to to get external hardware ready to clock.

controller.Clock(clkPin, 5); // Starting in high (logic 1) state, begin clocking 5 times.

// clkPin is now idle in initial state (high state).

In many cases, you will most likely need to clock once and read. However, there are also times you need to clock registers and such to rotate bits to a certain location to begin read bits.

  1. Should there be a time interval argument to toggle state (understanding this isn't going to be accurate)?
  2. Should this be an Async method?
  3. Other thoughts?

[Proposal] Provide controller provider interfaces

This proposal is to create new controller interfaces for the various types of I/O within System.Device.API. This is similar (but not exactly) to Windows.Devices.Gpio.Provider.

Rationale and Usage

In most cases, the choice will be to control things with I/O available on main controller boards using the GpioController. However, device bindings could be structured in a way that allow them to provide their own controller that utilizes a particular interface (e.g. I2C/SPI) behind the scenes.

Examples

Use SPI to update related registers when performing a controller Read call.

Mcp23xxx mcp23xxx = new Mcp23S17(spiDevice);
GpioController mcp23xxxGpioController = mcp23xxx.GetDefaultGpioController();
PinValue value = mcp23xxxGpioController.Read(14);

Use the proposed GpioPort to update multiple pins at the same time on a binding.

// Using controller from above.
GpioPort port = new GpioPort(mcp23xxxGpioController, 1, 4, 7);
port.WriteByte(0x06);

Binding that implements multiple providers. This example uses I2C to perform the controller specifics.

public class MyArduinoDoohickey : IAdcControllerProvider, IGpioControllerProvider, IPwmControllerProvider

MyArduinoDoohickey doohickey = new MyArduinoDoohickey(i2cDevice);

// PWM stuff.
PwmController pwmController = doohickey.GetDefaultPwmController();
pwmController.OpenChannel(1, 4); // pwmChip, pwmChannel
pwmController.StartWriting(1, 4, 200, 0.3); // pwmChip, pwmChannel, frequencyInHertz, dutyCyclePercentage)

// ADC stuff (when AdcController is added to API).
AdcController adcController = doohickey.GetDefaultAdcController();
adcController.OpenChannel(5);
double adcValue = adcController.ReadValue(5);
            
// GPIO stuff.
GpioController gpioController = mcp23xxx.GetDefaultGpioController();
gpioController.OpenPin(1, PinMode.Input);
PinValue gpioValue = gpioController.Read(1);

Proposed Change

This proposal should include the following interfaces to get the specific type of controller.
This would allow device bindings to begin implementing with controller functionality.

GPIO

public interface IGpioControllerProvider : IDisposable
{
    GpioController GetDefaultGpioController();    // Gets first (or only one) in list.
    
    // Most cases will only have 1, but could possibly have multiples of same type.
    // Not sure if there would be a way to get a specific if needed?
    IList<GpioController> GetGpioControllers();  // Gets all GPIO controllers.
}

PWM

public interface IPwmControllerProvider : IDisposable
{
    PwmController GetDefaultPwmController();    // Gets first (or only one) in list.
    
    // Most cases will only have 1, but could possibly have multiples of same type.
    // Not sure if there would be a way to get a specific if needed?
    IList<PwmController> GetPwmControllers();  // Gets all PWM controllers.
}

ADC (when AdcController is added to API)

public interface IAdcControllerProvider : IDisposable
{
    AdcController GetDefaultAdcController();    // Gets first (or only one) in list.
    
    // Most cases will only have 1, but could possibly have multiples of same type.
    // Not sure if there would be a way to get a specific if needed?
    IList<AdcController> GetAdcControllers();  // Gets all ADC controllers.
}

Productize the device bindings and samples from corefxlab

@edgardozoppi created a bunch of samples this summer under https://github.com/dotnet/corefxlab/tree/master/samples/System.Devices.Gpio.Samples.

The device bindings should be moved to the Iot.Device namespace under https://github.com/dotnet/iot/tree/master/src/devices

The Application code samples using the device bindings should be moved under https://github.com/dotnet/iot/tree/master/samples.

Fritzing Diagrams should be generated liberally.

Component Project Naming Conventions

This is related to #55, but created separate issue to understand more specifics.

Topic Adding more libraries that control specific sensors or devices

  1. What is the appropriate naming convention for component projects? Camel-casing sometimes messes with me when it comes to hardware and acronyms. ๐Ÿ˜„
  • e.g. MCP3008 or Mcp3008, etc.
  • e.g. TCA9548A, Tca9548A, Tca9548a, etc.
  1. Should we create projects with top-level namespace specific to component name or should it be under a particular format?
  • e.g. MCP3008. Namespace = Mcp3008

I personally like the component name as top-level as it is simple, but I assume there are other (potential) APIs in the wild that users have named the same. If they did and already have NuGet packages, what impact would that have when we release the repo's related API package? For example, Microsoft.IoT.AdcMcp3008 is not the same name, but had potential to be released as Mcp3008.

[Proposal] Add System.Device.Gpio.Toggle()

Toggling a Gpio pin is a very, very common operation. To accomplish that, with what's available today, one has to either:

  1. Keep a variable to hold the pin state and then update the pin and the variable.
  2. Perform a read pin, toggle state and perform a write pin.

Most of modern MCUs provide a low level way of doing this with one or maybe a couple of instructions. Even if it requires implementing 2. in native code it would still be far more efficient that using 1. or 2. in managed code.

Considering the above, I'm proposing that a System.Device.Gpio.Toggle(int pinNumber) method should exist.

PS: We already have this on nanoFramework Windows.Devices.Gpio API, see here.

Support for MCUs

Do you have plans to support for MCU like ESP32. Like nanoFramework does?

WriteRead using I2C and MAX7311

Iโ€™m trying to communicate with MAX7311 using UnixI2cDevice on Raspians. Generally I2C with other devices works but in this case I need to write some data and then immediately read response from MAX7311 device.
When I try to do something like this:

var buffer = new Span<byte>();
var device = new System.Device.I2c.Drivers.UnixI2cDevice(new I2cConnectionSettings(BusId, address));
device.Write(new byte[] { 0x00 });
device.Read(buffer);

I get exception โ€œError when attempting to perform the I2c data transferโ€
When I use some custom implementation of I2C from this repo

https://github.com/chkr1011/Wirehome.Core/blob/master/Wirehome.Core/Hardware/I2C/I2CBusService.cs

and use WriteRead method I can communicate with success.

var device = new LinuxI2CBusAdapter(Logger);
device.Enable();
var readBuffer = new byte[2];
device.WriteRead(address, new byte[] { 0x00 }, readBuffer);

It would be nice if this WriteRead could be supported โ€“ or Iโ€™m doing something wrong?

[Proposal] Add ConnectionType enum to System.Devices.* API

Would it be worth adding a new ConnectionType enum (or similar) as a way to determine how devices are connected & communicate. This is mainly a helper for bindings as it will most likely be redundant code in each project.

When a binding is instantiated, we will usually have multiple options (SPI, I2C, GPIO) depending on device capabilities. For example the MCP23XXX has both I2C and SPI interface options and because we can bit-bang those protocols... this makes 4 options. As shown in early MCP3008 binding, we currently have a private CommunicationProtocol enum that is set based on the constructor called. This is used to determine what internal method to call during reads/writes.

namespace System.Device
{
    public enum ConnectionType
    {
        I2c,  // Represents anything that inherits from I2cDevice
        Spi,  // Represents anything that inherits from SpiDevice
    }
}

There could be other choices, but have not reviewed enough to know what makes sense. What about Pwm, PwmGpio, Serial, etc.?

I used ConnectionType as it seemed to fit better with the ConnectionSettings scheme.

Sample: Display IP address on device startup

Many people want a way of determining a device IP address for an otherwise headless device. This info would need to be shown at device startup. By definition, there isn't a way to find out the IP address to start the program manually in this scenario.

We should build a sample that does this.

This would be the perfect LCD panel to use: https://www.adafruit.com/product/3527

[Question] What is the need for OS driver files for other OS

I noticed the following I2C/SPI driver files with PlatformNotSupportedException.

  • UnixI2cDevice.Windows.cs
  • Windows10I2cDevice.Linux.cs
  • UnixSpiDevice.Windows.cs
  • Windows10SpiDevice.Linux.cs

I get various ambiguous warnings/errors in Visual Studio for project. What is the need for these and where are they used? Just curious ๐Ÿค”

If it is only a safety net to not allow a driver on the wrong OS, you could do something like below with the new PR (#110),..

// For UnixSpiDevice.Linux.cs class.
public UnixSpiDevice(SpiConnectionSettings settings)
{
    if (!Interop.OperatingSystem.IsLinux())
    {
        throw new PlatformNotSupportedException($"The {GetType().Name} class is not available on this platform.");
    }

    _settings = settings;
    DevicePath = Default_Device_Path;
}

Create a Microchip MCP23XXX binding

Create a binding API for the Microchip MCP23XXX family.

//cc @joperezr @joshfree Please assign this to me. I have a prototype, but trying to work out the kinks and such. This has been a good exercise as it has bought up a few issues/questions. I will post more details and action items as progress is made. Thx

Possible Iterations

  • (#91) Create initial Mcp23xxx project with tests and samples.
  • (#130) Separate with Mcp23xxx abstract, create Mcp230xx (I2C), create Mcp23Sxx (SPI).
  • (#130) Add additional pins for reset and interrupt lines.
  • (#130) Add ReadBit/WriteBit helper methods.

Virtual devices & signalr

Please consider creating a set of virtual devices that simulate the physically state/operations of the hardware, and can be used without the need of the developer to have access to actual hardware.

E.g.. the device driver for a virtual SPI interface could make use of Signalr to send (almost realtime) state updates and/or operations to a web, windows, mobile ux.
using a similar concept to device twins.

IMHO this would help developers and improve testibility and reduce the developer inner loop when creating driver/controllers.

Enabling specifying pin values as external configuration

I am hoping to deliver samples in this repo as Docker images. The basic idea is that there are scenarios where it isn't convenient to update code to use the pins you want or switch to the pins in place within existing binaries. Docker is one scenario, but I expect others.

Here's one way this could work.

In code:

GPIO.OpenPin(16,"pin1")

via config:

docker run -e dotnetconfig=pin1:17 rich/cool-iot-demo

Naturally, we'd expect pin1 to be 17 instead of 16 when run with that environment variable set

[Proposal] SpiController and ISpiController

Similar to the GpioController, there needs to be a mechanism to determine the best SpiDevice to use.

Updated 6/24/2019

Proposed Changes

1. Add New SpiController

The creation of devices with a new static helper method that attempts to pick the best SPI device for OS app is executing on. This static method will be added to a new sealed partial class respective to OS.

// SpiController.Linux.cs
public static SpiDevice Create(SpiConnectionSettings settings) => new UnixSpiDevice(settings);
// SpiController.Windows.cs
public static SpiDevice Create(SpiConnectionSettings settings) => new Windows10SpiDevice(settings);

Update Generic Unix Device Driver to use libgpiod when available instead of sysfs calls

https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/tree/README

libgpiod

libgpiod - C library and tools for interacting with the linux GPIO
character device (gpiod stands for GPIO device)

Since linux 4.8 the GPIO sysfs interface is deprecated. User space should use
the character device instead. This library encapsulates the ioctl calls and
data structures behind a straightforward API.

RATIONALE

The new character device interface guarantees all allocated resources are
freed after closing the device file descriptor and adds several new features
that are not present in the obsolete sysfs interface (like event polling,
setting/reading multiple values at once or open-source and open-drain GPIOs).

Unfortunately interacting with the linux device file can no longer be done
using only standard command-line tools. This is the reason for creating a
library encapsulating the cumbersome, ioctl-based kernel-userspace interaction
in a set of convenient functions and opaque data structures.

https://blog.adafruit.com/2018/11/26/sysfs-is-dead-long-live-libgpiod-libgpiod-for-linux-circuitpython/

With more linux boards coming out with GPIO (thereโ€™s probably 4 more released since we wrote this post) โ€“ having a consistent, reliable, complete GPIO interface is pretty important to avoid icky unmaintainable code. So weโ€™re pretty psyched about libgpiod. From our experiments, its much much faster than sysfs. Its not as fast as mem twiddling, but thatโ€™s not too surprising, thereโ€™s still kernel messages and error checking done. A Pi 3 got us 400Khz pin output toggles in a loop in C, 100KHz in Python examples, and thatโ€™s pretty good for bitbanging. (For SPI or I2C, use the hardware peripherals, they can go multi-MHz and can be shared between processes!)

and has the benefit of being forward compatible with any other Linux board that runs a 4.8+ kernel.

[Proposal] Provide a new GpioPort class

Core Objective

There are scenarios when a port (with multiple pins) needs to be read from and written to. It would be nice to have a new GpioPort class where you can pass in pin numbers that represent the respective pin/bit for values.

NOTE: The code examples are loosely defined only to point out scenarios.

Thoughts/Examples

  • GpioPort should have a GpioController passed to constructor along with pins while instantiating object. This would work for both dev boards and related device bindings.
public class GpioPort(GpioController controller, params int[] bitIndexes)
GpioController controller = new GpioController();
GpioPort port = new GpioPort(controller, 1, 2, 3, 4);
// or...
int[] bitIndexes = new int[] { 1, 2, 3, 4 };
GpioPort port = new GpioPort(controller, bitIndexes);
  • Write the value 0x65 to an 8-bit port.
GpioPort port = new GpioPort(controller, 1, 2, 3, 4, 5, 6, 7, 8);
port.WriteByte(0x65);

/*
Pin values at this point.
Bit 1; Pin 1: PinValue.High
Bit 2; Pin 2: PinValue.Low
Bit 3; Pin 3: PinValue.High
Bit 4; Pin 4: PinValue.Low
Bit 5; Pin 5: PinValue.Low
Bit 6; Pin 6: PinValue.High
Bit 7; Pin 7: PinValue.High
Bit 8; Pin 8: PinValue.Low
*/
  • What would be the appropriate methods? It seems like there should be a variety as there will be different port sizes based on controller or device binding.
public bool ReadBit(int bitIndex)
public bool[] ReadBits() // Reads bool values for all assigned pins.
public bool[] ReadBits(params int[] bitIndexes) // Read only pins being requested.
public byte ReadByte()
public ushort ReadUInt16()
public uint ReadUInt32()
public ulong ReadUInt64()
public void WriteBit(int bitIndex, bool value)
public void WriteByte(byte value)
public void WriteUInt16(ushort value)
public void WriteUInt32(uint value)
public void WriteUInt64(ulong value)
public void SetAll(PinValue value);  // Can write all Low or High.
  • Pins can be in any order, but represent the respective bit starting with least significant bit.
// 23 = bit 1; 11 = 8.
GpioPort port = new GpioPort(controller, 23, 2, 9, 26, 1, 19, 10, 11);
  • Most ports are usually in multiples of 8 (8, 16, 32, etc.). By allowing the number of pins a user wants to define in a port, you could have cases where they don't equal multiples of 8. This would be okay as the value could still work with respective pins. For example, you could have 3 pins that control a 1-of-8 Decoder chip. Below shows another example using a 5-bit port.
GpioPort port = new GpioPort(controller, 1, 2, 9, 18, 3);
port.WriteByte(0x1A);

/*
Pin values at this point.
Bit 1; Pin  1: PinValue.Low
Bit 2; Pin  2: PinValue.High
Bit 3; Pin  9: PinValue.Low
Bit 4; Pin 18: PinValue.High
Bit 5; Pin  3: PinValue.High
*/
  • What should happen if value is greater than expected port? Throw ArgumentOutOfRangeException or ignore and only update wired pins?
GpioPort port = new GpioPort(controller, 1, 2, 9, 18, 3);
port.WriteByte(0x72);  // 5 bits max value is 0x1F.
  • Writing values to the entire port will not be synchronous since individual bit indexes have to determine bit/pin, closing/opening pin if needed, and writing pin value to each pin used. This should be okay as port pins are usually setup and then updated to external hardware using clock/latch lines.

  • What happens when user passes same pin more than once? Just ignore?

// Pin 2 used twice.
GpioPort port = new GpioPort(controller, 1, 2, 5, 2);
// port only has 3 pins at this point.
  • When and how should pin direction be setup? It seems like it wouldn't be until after the first Read/Write call.
GpioPort port = new GpioPort(controller, 1, 2, 3, 4);
// No direction/values have been setup at this point.
port.WriteByte(0x06);
// Pins are now outputs with respective value.
  • Reading or writing pins should always update the direction of pin. If there are specific pins that need to be inputs or outputs, then those should be left as individual controller pins or separate GpioPort.

  • Since it is based on a controller, you could have a helper method to get from controller. This would help verify the specified pins are provided by controller. Either way could verify.

// Also using params int[] bitIndexes.
GpioPort port = controller.CreateGpioPort(1, 2, 5, 8); 

Issues

  • You would not be able to combine I/O between controllers for ports. Meaning, you could not use a few pins from a dev board and a few from a device binding to create a port. This approach is "all or nothing" from specific device. This should be acceptable in most cases.

  • Would need to make sure the controller and port are disposed correctly.

    GpioPort port;
    using (GpioController controller = new GpioController())
    {
    	port = new GpioPort(controller, 1, 2, 3, 4);
    }
    // The controller is disposed here which means the port should be disposed as well, but we still keep a reference to it.
    port.WriteByte(0x06);
  • How would you determine the pin mode when reading/writing? In some cases, the I/O could be a pull-up input, so you could set as just an input mode. Same for outputs (if drain/collector options are available).
    Other thoughts/concerns?

Add Tests to CI

We need to add a unit test project and make sure that we execute them as part of CI for validation. Initially, this will only be inner loop runs that will only have unit tests, but eventually we will also want to add outerloop runs that test against actual physical devices.

Setup project so that we can cross compile for Linux and Windows

The SDK by default only allows cross-compiling for different TFMs but won't allow by default to cross-compile for different platforms. This means we will have to do some custom infrastructure to ensure that we produce two different implementation dlls, one for Windows and one for Linux.

[Proposal] Move device projects to respective vendor folder

/cc @joperezr @richlander

I'm currently reviewing and trying to clean up some of the code for the Mcp3008 project and think it would be a good idea to come up with some better structuring regarding IoT devices folder. This might be a good topic to add in doc related to #55.

If we begin to have a "rich set" of many component bindings, it could get messy quick. I propose we at least have a vendor (or similar) folder that separate components. Yes, some manufacturers produce the same component sometimes, but most are differentiated and the links/specs usually come from the creator of said components.

For example, the MCP3008 is a Microchip component. Therefore, it would be good to place its related projects (API, tests, etc.) under the dotnet\iot\devices\vendor\Microchip (or similar) folder.

I've seen a few places where peeps reference components like Adafruit [component] for example. This is not necessarily correct as Adrafruit is a distributor, but provides code examples. There might be times they (Adafruit, Sparkfun, etc.) create their own component (maybe a combination of other components). In this case, there would be a specific folder for their components. Using #12 as an example, we could have a dotnet\iot\devices\vendor\Adafruit\PiOLED folder.

To elaborate a little further... Adrafruit offers a breakout board for the TCA9548A. This component is produced by Texas Instrument. The breakout board is basically an easy way for hobbyists to connect to controller boards. So in this case, we could have dotnet\iot\devices\vendor\TexasInstruments\TCA9548A (or similar) folder. This API should be able to work for Adrafruit's breakout board and where others use said component in other hardware boards.

One problem with labeling to specific vendor folders is acquisitions do occur from time to time. However, transitions are usually long and most of the time they keep the branding. Engineers are usually smart enough to know the difference and how to find specs. If the vendors' name is not included in project namespace, it shouldn't be a breaking change in code... just a new folder location in repo.

Does this make sense? What are thoughts around this? I know I'm not thinking of all scenarios. Just trying to collect some ideas to make a better ecosystem related to this repo while it's still young.

Thx

[Proposal] Move classes and rename to be consistent throughout API

This proposal is a collection of different modifications and grouped here as there are many breaking changes being proposed. The intent is to review, decide and update (if needed) namespaces, class names and file locations in a consistent structure between the different types of interfaces within System.Device.Gpio API.

This seems to fit with @terrajobst comments during this design review, as well.

Rationale and Usage

Since System.Device.Gpio is in early previews, it seems like the opportune time to review and decide how to structure the core components. This will reduce breaking changes in the future.

Examples

  • GPIO and PWM have abstract classes based on Drivers and are located under the *.Drivers folder. This includes RaspberryPi3Driver, UnixDriver.Linux.cs, etc.. Gpio is not in the file names where I2C/Pwm/SPI are included in the file names.
  • I2C and SPI are based on Devices. However, there are implementations under the *.Drivers folders.

Proposed Changes

The proposed changes are to rename and move files as shown in the folder structure below. To simplify, this attempts to point out only the changes for the files being modified.

GPIO Folder

  • TODO
  1. Drivers and related classes renamed to include "Gpio".
  2. GpioDriver.cs moved to Drivers folder.
Device
  Gpio
    Drivers
      GpioDriver.cs
      HummingBoardGpioDriver.cs
      HummingBoardGpioDriver.Linux.cs
      HummingBoardGpioDriver.Windows.cs
      RaspberryPi3GpioDriver.cs
      RaspberryPi3GpioDriver.Linux.cs
      RaspberryPi3GpioDriver.Windows.cs
      UnixGpioDriver.Linux.cs
      UnixGpioDriver.Windows.cs
      UnixGpioDriverPin.Linux.cs  // Also simplified by removing Device
      Windows10GpioDriver.Linux.cs
      Windows10GpioDriver.Windows.cs
      Windows10GpioDriverPin.Windows.cs
    GpioPinChangeEventHandler.cs
    GpioPinEventTypes.cs
    GpioPinMode.cs
    GpioPinNumberingScheme.cs
    GpioPinValue.cs
    GpioPinValueChangedEventArgs.cs

I2C Folder

  1. Folder renamed from "Drivers" to "Devices".
  2. Namespaces be renamed from "System.Device.I2c.Drivers" to "System.Device.I2c"
  3. I2cDevice.cs moved to Devices folder.

See #118 and #534 for related details

Device
  I2c
    Devices
      I2cDevice.cs
      I2cDevice.Linux.cs
      I2cDevice.Windows.cs
      [other devices here that were not moved or renamed]
    I2cConnectionSettings.cs (didn't change; showing for location purposes)

PWM Folder

  1. PwmDriver.cs moved to Drivers folder.

See #204 for related details

Device
  Pwm
    Drivers
      PwmDriver.cs

SPI Folder

  1. Folder renamed from "Drivers" to "Devices".
  2. Namespaces be renamed from "System.Device.Spi.Drivers" to "System.Device.Spi"
  3. SpiDevice.cs moved to Devices folder.

See #119 and #534 for related details

Device
  Spi
    Devices
      SpiDevice.cs
      SpiDevice.Linux.cs
      SpiDevice.Windows.cs
      [other devices here that were not moved or renamed]
    SpiConnectionSettings.cs (didn't change; showing for location purposes)

Open Questions

  1. It seems a little redundant to have the interface in the file name. For example, GpioPinMode. Is this too redundant knowing it is under the Gpio folder/namespace? It seems like differentiating would be okay, especially when mixing different pin types in code files (e.g. Bindings with a mixture of digital, analog and PWM pins). This would help when adding "usings" or typing entire namespace out.

[Proposal] I2cController and II2cController

Similar to the GpioController, there needs to be a mechanism to determine the best I2cDevice to use.

Updated 6/18/2019

I2cDevice allows for an easy way to create an I2C connection between a master controller and one device binding. However, there are scenarios where a new type of controller can manage related devices more efficiently.

Rationale and Usage

System.Device.Gpio Consistency - Other APIs provide controllers that work with low-level drivers to perform the functional operations. For example, GpioController informs related driver to write logic-levels to pins and PwmController informs related driver to write variable frequency to pins. The I2cController will inform which I2C device to read/write data.

Multiple Device Management - Other controllers also can open and close multiple pins that will be controlled. Sometimes hardware configurations require a master to communicate with multiple devices on the same I2C bus. This is a current limitation with I2cDevice as it requires the bus ID & device address when instantiating and the read/write methods communicate with only the one device.

Default OS Implementation - Most current device binding samples specifically define the I2cDevice related to executing OS (e.g. UnixI2cDevice or Windows10I2cDevice). This forces users to update sample code when running on different hardware and OS. This proposal will offer a method to generate the default I2cDevice for OS similar to how a GpioController provides a default GpioDriver.

Controller Interface - In addition to providing a default I2cController, this proposal also includes an II2cController as there are use-cases where bindings could be a master I2C controller that can control other I2C devices.

Proposed Changes

1. Add New II2cController and I2cController

Add a new II2cController interface that includes the same read/write methods as an I2cDevice with the addition of specified bus ID and address parameters. This allows the controller to know which device to use for communications. There are also methods to open/close devices.

public interface II2cController : IDisposable
{
    void OpenDevice(I2cConnectionSettings settings, bool shouldDispose = true);
    void OpenDevice(I2cDevice device, bool shouldDispose = true);
    void CloseDevice(I2cConnectionSettings settings);
    void CloseDevice(I2cDevice device);
    void ClearDevices();
    bool TryGetDevice(int busId, int address, out I2cDevice device);
    void Read(int busId, int address, Span<byte> buffer);
    byte ReadByte(int busId, int address);
    void Write(int busId, int address, ReadOnlySpan<byte> buffer);
    void WriteByte(int busId, int address, byte value);
    void WriteRead(int busId, int address, ReadOnlySpan<byte> writeBuffer, Span<byte> readBuffer);
}

The I2cController class will implement the new II2cController interface and be the default for most apps and device bindings. The controller is instantiated with zero I2C devices to control. More devices can be opened/closed similar to pins on other controllers.

// Class
public class I2cController : II2cController

// Constructor
public I2cController()

The creation of devices with a new static helper method that attempts to pick the best I2C device for OS app is executing on. This static method will be added to a new sealed partial class respective to OS.

// I2cController.Linux.cs
public static I2cDevice Create(I2cConnectionSettings settings) => new UnixI2cDevice(settings);
// I2cController.Windows.cs
public static I2cDevice Create(I2cConnectionSettings settings) => new Windows10I2cDevice(settings);

Example 1.1

Instantiate a controller and open a device.

int busId = 1;
int address = 0x21;
var settings = new I2cConnectionSettings(busId, address);
var i2cController = new I2cController();
i2cController.OpenDevice(settings);

Example 1.2

Open a new device to controller that already contains device with specified address.

int busId = 1;
int address = 0x21;
var settings = new I2cConnectionSettings(busId, address);
var i2cController = new I2cController();
i2cController.OpenDevice(settings);
i2cController.OpenDevice(settings); // Throws exception because device with address already exists.

Example 1.3

Open two devices with same address but on different buses.

int busId0 = 0;
int busId1 = 1;
int address = 0x21;
var settings0 = new I2cConnectionSettings(busId0, address);
var settings1 = new I2cConnectionSettings(busId1, address);
var i2cController = new I2cController();
i2cController.OpenDevice(settings0);
i2cController.OpenDevice(settings1);

Example 1.4

Open a new device that implements I2cDevice to controller.

NOTE: This I2C software implementation doesn't currently exist but is the same concept as the SPI software implementation. See [Proposal] Provide a Software implementation of the SPI protocol for related details.

int busId = 1;
int address = 0x21;
var settings = new I2cConnectionSettings(busId, address);
int sdaPin = 17;
int sclPin = 18;
var gpioController = new GpioController();

var i2cDevice = new GpioI2cDevice(
    settings,
    sdaPin,
    sclPin,
    gpioController);

var i2cController = new I2cController();
i2cController.OpenDevice(i2cDevice);

Example 1.5

Close a device from controller that doesn't contain device with specified address.

int busId = 1;
int address = 0x21;
var settings = new I2cConnectionSettings(busId, address);
var i2cController = new I2cController();

i2cController.OpenDevice(settings);
i2cController.CloseDevice(new I2cConnectionSettings(busId, 0x45));  // Throws exception because no device with specified address exists.

Example 1.6

Reading from a specific device in controller.

int busId = 1;
int address = 0x21;
var settings = new I2cConnectionSettings(busId, address);
var i2cController = new I2cController();
i2cController.OpenDevice(settings);

byte value = i2cController.ReadByte(busId, address);

// Or...

Span<byte> buffer = stackalloc byte[3];
i2cController.Read(busId, address, buffer);

Example 1.7

Write to a specific device in controller.

int busId = 1;
int address = 0x21;
var settings = new I2cConnectionSettings(busId, address);
var i2cController = new I2cController();
i2cController.OpenDevice(settings);

byte value = 0x23;
i2cController.WriteByte(busId, address, value);

// Or...

ReadOnlySpan<byte> buffer = new byte[] { 0x12, 0x23, 0x45 };
i2cController.Write(busId, address, buffer);

2. Disposing of I2cControllers and I2cDevices

  • WIP: Need to review topic and create examples here. There are some initial dispose notes below (delete after adding to this section).

NOTE: This dispose pattern needs to fit other controllers to not confuse others, as well.

A new public bool ShouldDispose property is added to I2cDevice class for different disposing scenarios. This property will allow controllers/bindings to know if it should dispose device(s) if/when the controller/binding is being disposed. By default, this property should be set to true as this is the intention in most cases.

public abstract class I2cDevice : IDisposable
{
    public bool ShouldDispose { get; set; } => true;

    // other current code...
}

Example 2.1

Use controller with default device (ShouldDispose = true).

Example 2.2

Use controller with provided device (ShouldDispose = false).

3. Device Binding Guidance

This section points out a few areas bindings should use that use I2cDevice.

Binding Constructors

Bindings that use I2C should be the owners of opening the I2cDevice on the controller. Therefore, the bindings should provide at least 2 constructor overloads that offer the option of using I2cConnectionSettings or I2cDevice. This will make it easier when instantiating the bindings with other apps and APIs. It is assumed there will be no other I2C device with the same bus ID and address as it will throw an exception when the binding adds the device to the controller.

Example 3.1

Instantiate a device binding with specified settings without a defined controller.

NOTE: The default controller would be instantiated similar to a master IGpioController when not defined. Since the controller is not provided and only settings, there is no way to add a specified I2cDevice. Therefore, the intent is to also create a default I2cDevice when it is added to controller.

int busId = 1;
int address = 0x21;
var settings = new I2cConnectionSettings(busId, address);
var mcp23018 = new Mcp23018(settings);  // Default controller and default device is opened within.

Example 3.2

Instantiate a device binding with specified device without a defined controller.

NOTE: The default controller would be instantiated similar to a master IGpioController when not defined. The specified device will then be opened.

int busId = 1;
int address = 0x21;
var settings = new I2cConnectionSettings(busId, address);
MyI2cDevice device = new MyI2cDevice(settings);  // New device that implements I2cDevice.
var mcp23018 = new Mcp23018(myI2cDevice);  // Default controller and specified device is opened within.

Example 3.3

Instantiate a device binding with specified settings (or device) and specified controller.

int busId = 1;
int address = 0x20;
var i2cController = new WhizBangI2cController();

// By settings.
var settings = new I2cConnectionSettings(busId, address);
var mcp23018 = new Mcp23018(settings, i2cController);

// Or...

// By device.
var device = I2cController.Create(new I2cConnectionSettings(busId, address));
i2cController.OpenDevice(device, i2cController);

Example 3.4

Instantiate a controller for a binding that has multiple devices with fixed addresses.

See Adding GHI Fez Cream driver for related details.

NOTE: This hardware is a RPi HAT with hard-wired pins on I2C bus 1.

var ghiFezCream = new GhiFezCream(); // Adds all 4 on-board devices to binding.

// Or...

// Adds all 4 on-board devices to binding using default devices on specified controller.
var i2cController = new I2cController();
var ghiFezCream = new GhiFezCream(i2cController);

// Or...

// Adds all 4 on-board devices to binding using specified devices and specified controller.
int busId = 1;

var ghiFezCream = new GhiFezCream(
    new GpioI2cDevice(new I2cConnectionSettings(busId, GhiFezCream.Pca9535AddressIc1)),
    new GpioI2cDevice(new I2cConnectionSettings(busId, GhiFezCream.Pca9535AddressIc2)),
    new GpioI2cDevice(new I2cConnectionSettings(busId, GhiFezCream.Ads7830IpwtAddress)),
    new GpioI2cDevice(new I2cConnectionSettings(busId, GhiFezCream.Pca9685Address)),
    i2cController);

public class GhiFezCream
{
    // Constructor shows it adding fixed devices.
    public GhiFezCream(II2cController? i2cController = null)
    {
        int busId = 1;
        i2cController = i2cController ?? new I2cController();

        i2cController.OpenDevice(new I2cConnectionSettings(busId, GhiFezCream.Pca9685Address)),
        i2cController.OpenDevice(new I2cConnectionSettings(busId, GhiFezCream.Pca9535AddressIc2)),
        i2cController.OpenDevice(new I2cConnectionSettings(busId, GhiFezCream.Ads7830IpwtAddress)),
        i2cController.OpenDevice(new I2cConnectionSettings(busId, GhiFezCream.Pca9685Address)),
    }
    
    // Or you could overload with specified devices.
    public GhiFezCream(
        I2cDevice pca9535Ic1,
        I2cDevice pca9535Ic2 ,
        I2cDevice ads7830Ipwt ,
        I2cDevice pca9685 ,
        II2cController? i2cController = null)
    {
        i2cController = i2cController ?? new I2cController();
        i2cController.OpenDevice(pca9535Ic1);
        i2cController.OpenDevice(pca9535Ic2);
        i2cController.OpenDevice(ads7830Ipwt);
        i2cController.OpenDevice(pca9685);
    }

    public static int Pca9535AddressIc1 => 0x23;
    public static int Pca9535AddressIc2 => 0x25;
    public static int Ads7830IpwtAddress => 0x48;
    public static int Pca9685Address => 0x70;
}

Example 3.5

Instantiate a controller, opens another I2C device on bus, and then opens a binding.

int busId = 1;
int address = 0x21;
var settings = new I2cConnectionSettings(busId, address);
var i2cController = new I2cController();
i2cController.OpenDevice(settings);  // This opens a default device using specified settings.

var bindingSettings = new I2cConnectionSettings(busId, 0x20);
var mcp23018 = new Mcp23018(bindingSettings, i2cController);  // Specified controller opens default device within on same bus as other device.

Disposing of Bindings

Bindings should implement IDisposable where it disposes the related devices.

public class MyBinding : IDisposable
{
    public void Dispose()
    {
        // This will remove the device from controller and only dispose if ShouldDispose is true. 
        _i2cController.CloseDevice(_i2cSettings);
    }
}

Update I2C Device Namespace

โš ๏ธ This topic is related to a future PR that will be a breaking change.

There have been discussions how we should structure/name some of the folders and classes throughout API. I originally thought we should rename I2C devices to use 'Driver' instead to be consistent with other types. For example, I2cDevice would be renamed I2cDriver.

My opinions have changed as the new I2C controller fits quite nicely with the current device naming. Although, there is a breaking change needed with the folder structure and related namespace. This could be implemented before the release of v1 in a future PR. So this proposal is in-line to the current device class naming. It only states we need to change the namespace later (hopefully soon after).

// From...
namespace System.Device.I2c.Drivers
// To...
namespace System.Device.I2c.Devices

See [Proposal] Move classes and rename to be consistent throughout API for related details.

Related Issues

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.