-
Notifications
You must be signed in to change notification settings - Fork 591
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
Conversation
Ellerbach
commented
Feb 18, 2019
- Adding supportfor GrovePi from Dexter Industries
- Adding support for all core GrovePi functions
- Adding support for native GrovePi sensors like Ultrasound, Led Bar, DHT
- Adding support for generic Analogic Input, Digital Input and Output, PWM Output
- Adding support for high level sensors like buzzer, led, relay
- Adding documentation
- Adding sample with usage of multiple sensors, all present in the Grove starter kit
…ordingly, added SPI bus Id and chip select line
src/devices/GrovePi/GrovePi.cs
Outdated
/// The default GrovePi I2C address is 0x04 | ||
/// Other addresses can be use, see GrovePi documentation | ||
/// </summary> | ||
public static byte GrovePiSefaultI2cAddress => 0x04; |
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.
I2cAddress
should be good enough (also make it const)
src/devices/GrovePi/GrovePi.cs
Outdated
public static byte GrovePiSefaultI2cAddress => 0x04; | ||
|
||
/// <summary> | ||
/// The maximum ADC, 12 bit so 2013 on GrovePi |
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.
confused by the comment, since value is 1023
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.
typo, it's 1023
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.
isn't that 10bit though?
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.
yes, it is. copy/paste from GoPiGo with mistakes. correcting.
src/devices/GrovePi/GrovePi.cs
Outdated
/// <summary> | ||
/// Contains the GrovePi key information | ||
/// </summary> | ||
public GrovePiInfo GrovePiInfo { get; internal set; } |
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.
Info
is sufficient since this is on GrovePi class
src/devices/GrovePi/GrovePi.cs
Outdated
|
||
public void Dispose() | ||
{ | ||
if(_autoDispose) |
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.
space before (
src/devices/GrovePi/GrovePi.cs
Outdated
{ | ||
if(_autoDispose) | ||
{ | ||
_i2cDevice.Dispose(); |
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.
_i2cDevice?.Dispose();
_i2cDevice = null;
src/devices/GrovePi/GrovePi.cs
Outdated
public Version GetFirmwareVerion() | ||
{ | ||
WriteCommand(GrovePiCommands.Version, 0, 0, 0); | ||
Thread.Sleep(10); |
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.
Why is sleep and 3 zeros needed needed?
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.
The write command is taking 4 bytes. The version does not require the last 3 bytes. So they have to be set to 0 and sent.
For sleep, I can try to remove it and run more tests as I I implemented a second retry. In tests versions, asking too many I2C commands would raise exceptions.
src/devices/GrovePi/GrovePi.cs
Outdated
WriteCommand(GrovePiCommands.Version, 0, 0, 0); | ||
Thread.Sleep(10); | ||
var inArray = ReadCommand(GrovePiCommands.Version, 0); | ||
return new Version(inArray[1], inArray[2], inArray[2]); |
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.
inArray[1], inArray[2], inArray[3]
?
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.
the first inArray is always 0. And it is not part of the version. Only the last 3 bytes are
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.
@Ellerbach What I meant is that you got [2]
twice
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.
hooooooooooooooo
src/devices/GrovePi/GrovePi.cs
Outdated
/// <param name="param2">Second parameter</param> | ||
public void WriteCommand(GrovePiCommands commands, GrovePort pin, byte param1, byte param2) | ||
{ | ||
byte[] outArray = new byte[4] { (byte)commands, (byte)(pin), param1, param2 }; |
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.
Span<byte> outArray = stackalloc byte[4]
src/devices/GrovePi/GrovePi.cs
Outdated
byte[] outArray = new byte[4] { (byte)commands, (byte)(pin), param1, param2 }; | ||
try | ||
{ | ||
_i2cDevice.Write(outArray.AsSpan()); |
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.
AsSpan is not needed since byte[]
is also a Span
src/devices/GrovePi/GrovePi.cs
Outdated
{ | ||
_i2cDevice.Write(outArray.AsSpan()); | ||
} | ||
catch (Exception ex) when (ex is IOException) |
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.
catch (IOException ex)
src/devices/GrovePi/GrovePi.cs
Outdated
default: | ||
return null; | ||
} | ||
Span<byte> inArray = new Span<byte>(new byte[numberBytesToRead]); |
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.
This should be:
byte[] outArray = new byte[numberBytesToRead];
_i2cDevice.Read(outArray);
return outArray;
src/devices/GrovePi/GrovePi.cs
Outdated
{ | ||
return (PinLevel)_i2cDevice.ReadByte(); | ||
} | ||
catch (Exception ex) when (ex is IOException) |
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.
exception directly in catch
src/devices/GrovePi/GrovePi.cs
Outdated
public PinLevel DigitalRead(GrovePort pin) | ||
{ | ||
WriteCommand(GrovePiCommands.DigitalRead, pin, 0, 0); | ||
Thread.Sleep(10); |
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.
why is the sleep needed? if datasheet requires it - should it be part of WriteCommand instead?
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.
Yes, it's needed to have the operation done correctly on the GrovePi side. Now, As implemented a retry method in the write command, I can try without and run tests like this.
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.
perhaps add this delay before doing the retry
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.
Yes, exactly what I was trying to test. I did a stress test wihout any wait. And after 15 minutes it failed. Will run a new one with the wait before the retry
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.
I ran more tests and still failing after 30 minutes with older firmware (1.2.2). I'll implement multiple retries with the wait pattern. Let's see.
/// GroovePi commands to read, write, setup pins and access special sensors | ||
/// Note that those commands are supports in most of the recent firmware with version higher than 1.2.1 | ||
/// </summary> | ||
public enum GrovePiCommands |
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.
should this be private?
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.
No as if you want to implement your own sensors, you may need it
@@ -0,0 +1,64 @@ | |||
using System; |
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.
copyright header
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.
same comment for other files where it is missing
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.
pfff, totally forgot them… Will add
/// <summary> | ||
/// Defines the pin level, either high, either low | ||
/// </summary> | ||
public enum PinLevel |
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.
why not use PinValue?
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.
Right, I was hesitating. I can rename it to PinValue
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.
I meant that we already have it in Gpio namespace
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.
yes, I got it. I used the Gpio ones.
/// <summary> | ||
/// Defines the pin mode, either input, either output | ||
/// </summary> | ||
public enum PinMode |
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.
why not use PinMode
from Gpio?
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.
Yes, same for PinLevel (PinValue). They are actually the same values in the enum. So all will work the same way
throw new ArgumentException($"Error: grove Port not supported"); | ||
_grovePi = grovePi; | ||
Port = port; | ||
_grovePi.PinMode(Port, PinMode.Input); |
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.
I feel like GrovePi could probably implement IGpioController - might be worth taking a look
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.
It actually can but the IGpioController is only for Digital Pins and not for analogic pins. And most people who are using Grove sensors which are not implemented will look at the Arduino code. And in the Arduino code, they'll find analogRead, digitalWrite, etc. And not the ones from the IGpioController. So in this case, I think it will bring more confusion than Clarity. If I would implement the IGpioController, I would anyway keep the DigitalWrite and DigitalRead functions.
GrovePi is here to bring analogic sensors to the RaspberyPi world and makes things easy. So I'll keep the philosophie of GrovePi.
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.
@Ellerbach I'd suggest to create an issue that we should have support for analog pins in Gpio
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.
I will do. also the IGpioInterface is not present in the nuget 0.1.0-prerelease.19078.2. Would be great to have a new nuget release that would include it.
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.
Issue for analogic support: #255
_i2cDevice.Write(outArray); | ||
return; | ||
} | ||
catch (IOException ex) |
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.
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
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.
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 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.
src/devices/GrovePi/GrovePi.cs
Outdated
{ | ||
WriteCommand(GrovePiCommands.DigitalRead, pin, 0, 0); | ||
byte tries = 0; | ||
IOException innerEx = new IOException(); |
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.
Why not IOException innerEx = null
or if compiler allows just declaration? I don't think this value is ever used
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.
yes, it does accept null. The simple declaration was not enough. So I jsut created a new exception which is not needed as null is working.
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.
LGTM, Thanks @Ellerbach - we can fix up anything remaining here as more people have a chance to play with GrovePi
FYI: I just got the GrovePi+ starter kit. I'm going through the RGB LCD and revamping it a bit. I've found a datasheet for what I believe the IC is copying. It should be a clone of the NXP PCF2119x, which was introduced back in 1997. (AIP31068L is the clone part number, but I cannot find the datasheet for it.) |
/// <param name="port">The grove Port, need to be in the list of SupportedPorts</param> | ||
public DigitalOutput(GrovePi grovePi, GrovePort port) | ||
{ | ||
if (!SupportedPorts.Contains(port)) |
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.
The name of the argument should be included in the exception and you're not actually using the interpolated string.
throw new ArgumentException($"Error: grove port {port} not supported.", nameof(port));
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.
if we are changing it then I'd get rid of the Error:
prefix since that is implied by throwing an exception
@JeremyKuhne - any chance you could take another look? |
I'll review shortly |
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.
Appreciate your effort here! I have the GrovePi+ starter kit myself and I'm excited to use/build on this.
There are a number of nits and a few larger items called out. Notably ISensor is something we probably shouldn't do. Usage of the model would also be simpler if we can separate the analog pin and digital pin identifiers.
/// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
nit: no space
src/devices/GrovePi/GrovePi.cs
Outdated
} | ||
} | ||
|
||
throw new IOException($"{nameof(WriteCommand)} error writting command", innerEx); |
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.
I can see throwing another error here, but only if we're providing more context. We're not in this case, and there is a spelling problem too. A better string would be $"Failed to write command {commands}"
.
src/devices/GrovePi/GrovePi.cs
Outdated
{ | ||
Span<byte> outArray = stackalloc byte[4] { (byte)commands, (byte)(pin), param1, param2 }; | ||
byte tries = 0; | ||
IOException innerEx = new IOException(); |
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.
You shouldn't allocate an exception, just set it to null.
/// </summary> | ||
public enum GrovePiCommands | ||
{ | ||
// Digital read a pin, equivalent of digitalRead on Arduino |
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.
These should be xml comments. /// <summary>
/// GroovePi commands to read, write, setup pins and access special sensors | ||
/// Note that those commands are supports in most of the recent firmware with version higher than 1.2.1 | ||
/// </summary> | ||
public enum GrovePiCommands |
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.
This should be singular as it is not a flags enum. GrovePiCommand
.
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.
See the design guidelines.
src/devices/GrovePi/Sensors/Relay.cs
Outdated
set | ||
{ | ||
_value = value; | ||
_grovePi.DigitalWrite(Port, ((_value == PinValue.High) && !IsInverted) ? PinValue.High : PinValue.Low); |
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.
If we're exposing the raw value we should also set the raw value. This probably isn't even needed- you can infer the PinValue
if you need to from IsOn/IsEnabled
and IsInverted
. I presume needing to know this would be very rare and having just one get/set would make it easier to use this type correctly.
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.
I've added an IsOn property. Will keep this Value for consistency with the rest of the sensors.
src/devices/GrovePi/Sensors/Relay.cs
Outdated
/// <summary> | ||
/// Get "On" when relay if on and "Off" when relay is off | ||
/// </summary> | ||
public override string ToString() => (_value == PinValue.High) ? "On" : "Off"; |
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.
This isn't right as it doesn't take inverted into account.
/// <summary> | ||
/// Type of DHT sensors | ||
/// </summary> | ||
public enum DhtType |
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: This should be in a separate file.
src/devices/GrovePi/GrovePi.cs
Outdated
public Version GetFirmwareVerion() | ||
{ | ||
WriteCommand(GrovePiCommands.Version, 0, 0, 0); | ||
var inArray = ReadCommand(GrovePiCommands.Version, 0); |
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.
Per our coding styles, don't use var
here.
src/devices/GrovePi/GrovePi.cs
Outdated
/// <param name="autoDispose">True to dispose the I2C device when disposing GrovePi</param> | ||
public GrovePi(I2cDevice i2cDevice, bool autoDispose = true) | ||
{ | ||
_i2cDevice = i2cDevice ?? throw new ArgumentException("I2C device can't be null", nameof(i2cDevice)); |
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- the description isn't needed. The argument name and the exception tell you everything you need to know.
🐱🐉 I'd like to see this merged, I'm using python with a weird dockerfile to use these sensors at the moment, but my ideal solution would be to use dotnet. |
I need to find the time to work on this PR. In the mean time, you can use the PR by checking out the branch of this PR and work on it. Looking at the feedbacks, not everything will change. You may have later to adjust some of the elements but not everything. And your feedback will be very valuable. |
@qwertoyo, other option is that you can take this PR, look at feedbacks from Jeremy and work on the PR ,as well. As I wrote in the previous comment, you can take the PR as a branch and work on it. |
@@ -10,6 +10,7 @@ | |||
using System.Device.I2c; | |||
using System.Device.I2c.Drivers; | |||
using System.Device.Gpio; | |||
using Iot.Device.GroovePiDevice.Models; |
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.
Grove
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.
arg, will fix this one.
@Ellerbach except for one missed comment from previous and renaming |
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="System.Device.Gpio" Version="0.1.0-prerelease.19078.2" /> |
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.
This is not right. Can you please instead use version $(SystemDeviceGpioPackageVersion) ? That is what we are doing in the rest of the repo
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.
will adjust, the PR is so old that it was not the case originally. Thanks for checking! Will adjust as well
// See the LICENSE file in the project root for more information. | ||
|
||
using Iot.Device.GrovePiDevice.Models; | ||
using Iot.Device.GrovePiDevice.Models; |
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.
Build is failing because you have this using namespace twice.
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.
yes, correcting it
@joperezr want to review? I think this has been open long enough and is in reasonably good state - we can fix any remaining feedback separately |
|
||
/// <summary> | ||
/// The default GrovePi I2C address is 0x04 | ||
/// Other addresses can be use, see GrovePi documentation |
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.
/// <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 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.
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.
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%
Left small comments but this looks good otherwise. Main thing for me is that I would try to simplify the surface area with GroovePi, maybe the best way would be to simply have a WriteCommand and ReadCommand, and don't have those DigitalReads and instead simply force people to manually call the WriteCommand and specify what is it that they want to read. |
I think this is good to go now. Thanks for all the great work here @Ellerbach! |