-
Notifications
You must be signed in to change notification settings - Fork 590
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
Changes from all commits
a973dfa
73fc8c3
a1eb5ce
e823c34
ba8fdc4
6c78a36
d960aca
9e4a54d
7cedd7f
270f45d
98075b9
cb5842d
96d068e
ffb3529
ce720ee
652783d
60f802e
43d809a
271f11e
f6e5a35
880b04f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
/// </summary> | ||
public const byte DefaultI2cAddress = 0x04; | ||
|
||
/// <summary> | ||
/// The maximum ADC, 10 bit so 1023 on GrovePi | ||
/// </summary> | ||
public int MaxAdc => 1023; | ||
|
||
/// <summary> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can do There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
{ | ||
// 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]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
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); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netcoreapp2.1</TargetFramework> | ||
<EnableDefaultItems>false</EnableDefaultItems> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="System.Device.Gpio" Version="$(SystemDeviceGpioPackageVersion)" /> | ||
<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> |
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.GrovePiDevice.Models | ||
{ | ||
/// <summary> | ||
/// Type of DHT sensors | ||
/// </summary> | ||
public enum DhtType | ||
{ | ||
Dht11 = 0, | ||
Dht22 = 1, | ||
Dht21 = 2, | ||
Am2301 = 3 | ||
} | ||
} |
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, | ||
} | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.