Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding GrovePi support #246

Merged
merged 21 commits into from
Apr 25, 2019
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a973dfa
Adding BrickPi3 device
Ellerbach Feb 3, 2019
73fc8c3
fixing names for Linux builds
Ellerbach Feb 3, 2019
a1eb5ce
fixing name of SPIExceptions.cs in csproj file
Ellerbach Feb 3, 2019
e823c34
fiex typos in README, ,improved code based on GitHub reviews
Ellerbach Feb 4, 2019
ba8fdc4
remove specific exceptions to use generic ones, adjusted the code acc…
Ellerbach Feb 5, 2019
6c78a36
Merge remote-tracking branch 'upstream/master'
Ellerbach Feb 10, 2019
d960aca
Adding support for GoPiGo3
Ellerbach Feb 10, 2019
9e4a54d
Incorportated feedback following the PR
Ellerbach Feb 12, 2019
7cedd7f
Adjusting code based on last commit
Ellerbach Feb 14, 2019
270f45d
Fixed code based on latest feedbacks. Fixed typos in comments
Ellerbach Feb 17, 2019
98075b9
Merge remote-tracking branch 'upstream/master'
Ellerbach Feb 18, 2019
cb5842d
Adding GrovePi support
Ellerbach Feb 18, 2019
96d068e
Updating code based on PR comments
Ellerbach Feb 19, 2019
ffb3529
Merge remote-tracking branch 'upstream/master'
Ellerbach Feb 19, 2019
ce720ee
Merge remote-tracking branch 'upstream/master'
Ellerbach Feb 22, 2019
652783d
fixed code following PR feedbacks
Ellerbach Feb 27, 2019
60f802e
Fixed comment issue
Ellerbach Mar 1, 2019
43d809a
Adjusting exception for arguments as well as adding comments based on…
Ellerbach Mar 5, 2019
271f11e
Updating code based on latest PR comments
Ellerbach Apr 17, 2019
f6e5a35
fixed typo and feedback in last PR comments
Ellerbach Apr 18, 2019
880b04f
updating csproj files and fixing double namespace
Ellerbach Apr 18, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 240 additions & 0 deletions src/devices/GrovePi/GrovePi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Iot.Device.GrovePiDevice.Models;
using System;
using System.Buffers.Binary;
using System.Device.Gpio;
using System.Device.I2c;
using System.IO;
using System.Threading;

namespace Iot.Device.GrovePiDevice
{
/// <summary>
/// Create a GrovePi class
/// </summary>
public class GrovePi : IDisposable
{
private I2cDevice _i2cDevice;
private readonly bool _autoDispose;
private const byte MaxRetries = 4;

/// <summary>
/// The default GrovePi I2C address is 0x04
/// Other addresses can be use, see GrovePi documentation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: I wonder if it would be worth it to add here the alternative I2c addresses as well. It might be nice so that users don't have to look into datasheet in case they want to use an alternate address. Anyways, you don't have to fix it, just thinking out loud.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a way to change the address programmatically thru some scripts. To implement that, it would need to add SPI support and basically do what the script is doing. I Don't see much interest. Usually what people do is changing it before they start using it. If they want to have multiple GrovePi, they need anyway to change one by one with the script.

/// </summary>
public const byte DefaultI2cAddress = 0x04;

/// <summary>
/// The maximum ADC, 10 bit so 1023 on GrovePi
/// </summary>
public int MaxAdc => 1023;

/// <summary>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: no space

/// Contains the GrovePi key information
/// </summary>
public Info GrovePiInfo { get; internal set; }

/// <summary>
/// GrovePi constructor
/// </summary>
/// <param name="i2cDevice">The I2C device. Device address is 0x04</param>
/// <param name="autoDispose">True to dispose the I2C device when disposing GrovePi</param>
public GrovePi(I2cDevice i2cDevice, bool autoDispose = true)
{
_i2cDevice = i2cDevice ?? throw new ArgumentNullException(nameof(i2cDevice));
_autoDispose = autoDispose;
GrovePiInfo = new Info() { SoftwareVersion = GetFirmwareVerion() };
}

public void Dispose()
{
if (_autoDispose)
{
_i2cDevice?.Dispose();
_i2cDevice = null;
}
}

/// <summary>
/// Get the firmware version
/// </summary>
/// <returns>GroovePi firmware version</returns>
public Version GetFirmwareVerion()
{
WriteCommand(GrovePiCommand.Version, 0, 0, 0);
byte[] inArray = ReadCommand(GrovePiCommand.Version, 0);
return new Version(inArray[1], inArray[2], inArray[3]);
}

/// <summary>
/// Write a GrovePi command
/// </summary>
/// <param name="command">The GrovePi command</param>
/// <param name="pin">The pin to write the command</param>
/// <param name="param1">First parameter</param>
/// <param name="param2">Second parameter</param>
public void WriteCommand(GrovePiCommand command, GrovePort pin, byte param1, byte param2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider making these lower-level APIs internal instead? seems like you have a wrapper API for all commands, so perhaps we should hide Write and Read from the public surface area just to make API easier to use.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I have implemented some sensors only, I would live this part public so anyone who want to implement their own sensor can really use all low level as well. Now Read and Write will make it in 90% of the cases. Just living the door open for the 10%

{
Span<byte> outArray = stackalloc byte[4] { (byte)command, (byte)(pin), param1, param2 };
byte tries = 0;
IOException innerEx = null;
// When writing/reading to the I2C port, GrovePi doesn't respond on time in some cases
// So we wait a little bit before retrying
// In most cases, the I2C read/write can go thru without waiting
while (tries < MaxRetries)
{
try
{
_i2cDevice.Write(outArray);
return;
}
catch (IOException ex)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can do catch (IOException ex) when (tries < MaxRetries)
and then while (true) - it will just throw on the last try - fine to leave as is too

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I4ll leave it like this so far. But I keep the pattern in mind for other cases where it may Apply better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we write something here describing why we're retrying? Ideally we don't swallow IOException, of course, but if there is a valid reason we should clearly state why.

{
// Give it another try
innerEx = ex;
tries++;
Thread.Sleep(10);
}
}

throw new IOException($"{nameof(WriteCommand)}: Failed to write command {command}", innerEx);
}

/// <summary>
/// Read data from GrovePi
/// </summary>
/// <param name="command">The GrovePi command</param>
/// <param name="pin">The pin to read</param>
/// <returns></returns>
public byte[] ReadCommand(GrovePiCommand command, GrovePort pin)
{
int numberBytesToRead = 0;
switch (command)
{
case GrovePiCommand.DigitalRead:
numberBytesToRead = 1;
break;
case GrovePiCommand.AnalogRead:
case GrovePiCommand.UltrasonicRead:
case GrovePiCommand.LetBarGet:
numberBytesToRead = 3;
break;
case GrovePiCommand.Version:
numberBytesToRead = 4;
break;
case GrovePiCommand.DhtTemp:
numberBytesToRead = 9;
break;
// No other commands are for read
default:
return null;
}
byte[] outArray = new byte[numberBytesToRead];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should create an issue to remove the array allocation- it doesn't have to block this PR. We can still have a wrapping method that allocates an array for convenience, but there should also be a TryReadCommand that takes an input buffer.

byte tries = 0;
IOException innerEx = null;
// When writing/reading the I2C port, GrovePi doesn't respond on time in some cases
// So we wait a little bit before retrying
// In most cases, the I2C read/write can go thru without waiting
while (tries < MaxRetries)
{
try
{
_i2cDevice.Read(outArray);
return outArray;
}
catch (IOException ex)
{
// Give it another try
innerEx = ex;
tries++;
Thread.Sleep(10);
}
}

throw new IOException($"{nameof(ReadCommand)}: Failed to write command {command}", innerEx);
}

/// <summary>
/// Read a digital pin, equivalent of digitalRead on Arduino
/// </summary>
/// <param name="pin">The GroovePi pin to read</param>
/// <returns>Returns the level either High or Low</returns>
public PinValue DigitalRead(GrovePort pin)
{
WriteCommand(GrovePiCommand.DigitalRead, pin, 0, 0);
byte tries = 0;
IOException innerEx = null;
// When writing/reading to the I2C port, GrovePi doesn't respond on time in some cases
// So we wait a little bit before retrying
// In most cases, the I2C read/write can go thru without waiting
while (tries < MaxRetries)
{
try
{
return (PinValue)_i2cDevice.ReadByte();
}
catch (IOException ex)
{
// Give it another try
innerEx = ex;
tries++;
Thread.Sleep(10);
}
}

throw new IOException($"{nameof(DigitalRead)}: Failed to read byte with command {GrovePiCommand.DigitalRead}", innerEx);
}

/// <summary>
/// Write a digital pin, equivalent of digitalWrite on Arduino
/// </summary>
/// <param name="pin">The GroovePi pin to read</param>
/// <param name="pinLevel">High to put the pin high, Low to put the pin low</param>
public void DigitalWrite(GrovePort pin, PinValue pinLevel)
{
WriteCommand(GrovePiCommand.DigitalWrite, pin, (byte)pinLevel, 0);
}

/// <summary>
/// Setup the pin mode, equivalent of pinMod on Arduino
/// </summary>
/// <param name="pin">The GroovePi pin to setup</param>
/// <param name="mode">THe mode to setup Intput or Output</param>
public void PinMode(GrovePort pin, PinMode mode)
{
WriteCommand(GrovePiCommand.PinMode, pin, (byte)mode, 0);
}

/// <summary>
/// Read an analog value on a pin, equivalent of analogRead on Arduino
/// </summary>
/// <param name="pin">The GroovePi pin to read</param>
/// <returns></returns>
public int AnalogRead(GrovePort pin)
{
WriteCommand(GrovePiCommand.AnalogRead, pin, 0, 0);
try
{
var inArray = ReadCommand(GrovePiCommand.AnalogRead, pin);
return BinaryPrimitives.ReadInt16BigEndian(inArray.AsSpan(1, 2));
}
catch (IOException)
{
return -1;
}
}

/// <summary>
/// Write an analog pin (PWM pin), equivalent of analogWrite on Arduino
/// </summary>
/// <param name="pin">The GroovePi pin to write</param>
/// <param name="value">The value to write between 0 and 255</param>
public void AnalogWrite(GrovePort pin, byte value)
{
WriteCommand(GrovePiCommand.AnalogWrite, pin, value, 0);
}
}
}
22 changes: 22 additions & 0 deletions src/devices/GrovePi/GrovePiDevice.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<EnableDefaultItems>false</EnableDefaultItems>
<RootNamespace>Iot.Device.GroovePiDevice</RootNamespace>
<LangVersion>7.3</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Device.Gpio" Version="0.1.0-prerelease.19078.2" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not right. Can you please instead use version $(SystemDeviceGpioPackageVersion) ? That is what we are doing in the rest of the repo

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will adjust, the PR is so old that it was not the case originally. Thanks for checking! Will adjust as well

<PackageReference Include="System.Memory" Version="4.5.2" />
<None Include="README.md" />
</ItemGroup>

<ItemGroup>
<Compile Include="GrovePi.cs" />
<Compile Include="Models\*.cs" />
<Compile Include="Sensors\*.cs" />
</ItemGroup>

</Project>
Binary file added src/devices/GrovePi/GrovePort.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions src/devices/GrovePi/Models/DhtType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace Iot.Device.GroovePiDevice.Models
{
/// <summary>
/// Type of DHT sensors
/// </summary>
public enum DhtType
{
Dht11 = 0,
Dht22 = 1,
Dht21 = 2,
Am2301 = 3
}
}
114 changes: 114 additions & 0 deletions src/devices/GrovePi/Models/GrovePiCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace Iot.Device.GrovePiDevice.Models
{
/// <summary>
/// GroovePi commands to read, write, setup pins and access special sensors
/// Note that those commands are supported in most of the recent firmware with version higher than 1.2.1
/// </summary>
public enum GrovePiCommand
{
/// <summary>
/// Digital read a pin, equivalent of digitalRead on Arduino
/// </summary>
DigitalRead = 1,
/// <summary>
/// Digital write a pin, equivalent of digitalWrite on Arduino
/// </summary>
DigitalWrite = 2,
/// <summary>
/// Analog read a pin, equivalent of analogRead on Arduino
/// </summary>
AnalogRead = 3,
/// <summary>
/// Analog write a pin, equivalent of analogRead on Arduino
/// </summary>
AnalogWrite = 4,
/// <summary>
/// Set the Pin moden, equivalent of pinMode on Arduino
/// </summary>
PinMode = 5,
/// <summary>
/// Ultrasonic sensor
/// </summary>
UltrasonicRead = 7,
/// <summary>
/// Get the version number
/// </summary>
Version = 8,
/// <summary>
/// DHT22 sensor
/// </summary>
DhtTemp = 40,
/// <summary>
/// Initialize the Led bar
/// </summary>
LedBarInitialization = 50,
/// <summary>
/// Set orientiation
/// </summary>
LedBarOrientation = 51,
/// <summary>
/// Set level
/// </summary>
LedBarLevel = 52,
/// <summary>
/// Set an individual led
/// </summary>
LedBarSetOneLed = 53,
/// <summary>
/// Toggle an individual led
/// </summary>
LedBarToggleOneLed = 54,
/// <summary>
/// Set all leds
/// </summary>
LedBarSet = 55,
/// <summary>
/// Get the led status
/// </summary>
LetBarGet = 56,
/// <summary>
/// Initialize na 4 digit display
/// </summary>
FourDigitInit = 70,
/// <summary>
/// Set brightness, not visible until next cmd
/// </summary>
FourDigitBrightness = 71,
/// <summary>
/// Set numeric value without leading zeros
/// </summary>
FourDigitValue = 72,
/// <summary>
/// Set numeric value with leading zeros
/// </summary>
FourDigitValueZeros = 73,
/// <summary>
/// Set individual digit
/// </summary>
FourDigitIndividualDigit = 74,
/// <summary>
/// Set individual leds of a segment
/// </summary>
FourDigitIndividualLeds = 75,
/// <summary>
/// Set left and right values with colon
/// </summary>
FourDigitScore = 76,
/// <summary>
/// Analog read for n seconds
/// </summary>
FourDigitAnalogRead = 77,
/// <summary>
/// Entire display on
/// </summary>
FourDigitAllOn = 78,
/// <summary>
/// Entire display off
/// </summary>
FourDigitAllOff = 79,
}
}
Loading