未验证 提交 1c036548 编写于 作者: L Laurent Ellerbach 提交者: GitHub

Add Raspberry Pi build HAT (#1752)

上级 0cb69fa8
此差异已折叠。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(DefaultBindingTfms)</TargetFrameworks>
<EnableDefaultItems>false</EnableDefaultItems>
<RootNamespace>Iot.Device.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="System.IO.Ports" Version="$(SystemIOPortsPackageVersion)" />
<Compile Include="Brick.cs" />
<Compile Include="Resource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Include="Models\*.cs" />
<Compile Include="Motors\*.cs" />
<Compile Include="Sensors\*.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="data\firmware.bin">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Include="data\signature.bin">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Include="data\version">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Common\CommonHelpers.csproj" />
</ItemGroup>
</Project>

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31919.166
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildHat", "BuildHat.csproj", "{0F13CF3E-6455-45CF-AAF0-2E702F8A95DB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildHat.samples", "samples\BuildHat.samples.csproj", "{2577191A-4928-4BF7-9734-F2651181A169}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildHatTests", "tests\BuildHatTests\BuildHatTests.csproj", "{CD468E74-AF86-48D8-96AA-1E267084AB2F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonHelpers", "..\Common\CommonHelpers.csproj", "{F0563C1E-F8B4-47A7-9C1F-A700FF9F5C65}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0F13CF3E-6455-45CF-AAF0-2E702F8A95DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F13CF3E-6455-45CF-AAF0-2E702F8A95DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F13CF3E-6455-45CF-AAF0-2E702F8A95DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F13CF3E-6455-45CF-AAF0-2E702F8A95DB}.Release|Any CPU.Build.0 = Release|Any CPU
{2577191A-4928-4BF7-9734-F2651181A169}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2577191A-4928-4BF7-9734-F2651181A169}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2577191A-4928-4BF7-9734-F2651181A169}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2577191A-4928-4BF7-9734-F2651181A169}.Release|Any CPU.Build.0 = Release|Any CPU
{CD468E74-AF86-48D8-96AA-1E267084AB2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD468E74-AF86-48D8-96AA-1E267084AB2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD468E74-AF86-48D8-96AA-1E267084AB2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD468E74-AF86-48D8-96AA-1E267084AB2F}.Release|Any CPU.Build.0 = Release|Any CPU
{F0563C1E-F8B4-47A7-9C1F-A700FF9F5C65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F0563C1E-F8B4-47A7-9C1F-A700FF9F5C65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F0563C1E-F8B4-47A7-9C1F-A700FF9F5C65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F0563C1E-F8B4-47A7-9C1F-A700FF9F5C65}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BBAF823F-E0EA-42F7-8AB9-816F22492528}
EndGlobalSection
EndGlobal
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
namespace BuildHat.Models
{
/// <summary>
/// Class containing the board information.
/// </summary>
public class BuildHatInformation
{
/// <summary>
/// Gets or sets the Version information.
/// </summary>
public string Version { get; internal set; }
/// <summary>
/// Gets or sets the signature of the firmawre.
/// </summary>
public byte[] Signature { get; internal set; }
/// <summary>
/// Gets or sets the Firmware date.
/// </summary>
public DateTimeOffset FirmwareDate { get; internal set; }
/// <summary>
/// Create a BuildHat information class.
/// </summary>
/// <param name="version">The version.</param>
/// <param name="signature">The signature.</param>
/// <param name="firmwareDate">The firmware date.</param>
public BuildHatInformation(string version, byte[] signature, DateTimeOffset firmwareDate)
{
Version = version;
Signature = signature;
FirmwareDate = firmwareDate;
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Iot.Device.BuildHat.Models
{
/// <summary>
/// A combi mode is a list of available modes
/// </summary>
public struct CombiModes
{
/// <summary>
/// Gets or sets the combi number
/// </summary>
public int Number { get; set; }
/// <summary>
/// Gets or sets a mode
/// </summary>
public int[] Modes { get; set; }
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Iot.Device.BuildHat.Models
{
/// <summary>
/// The led colors used with any of the color elements.
/// </summary>
public enum LedColor
{
/// <summary>Off</summary>
Off = 0,
/// <summary>Black</summary>
Black = 0,
/// <summary>Brown</summary>
Brown = 1,
/// <summary>Magenta</summary>
Magenta = 2,
/// <summary>Blue</summary>
Blue = 3,
/// <summary>Cyan</summary>
Cyan = 4,
/// <summary>Pale Green</summary>
PaleGreen = 5,
/// <summary>Green</summary>
Green = 6,
/// <summary>Yellow</summary>
Yellow = 7,
/// <summary>Orange</summary>
Orange = 8,
/// <summary>Red</summary>
Red = 9,
/// <summary>White</summary>
White = 10,
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace BuildHat.Models
{
/// <summary>
/// Led mode for the leds on the Build HAT.
/// </summary>
public enum LedMode
{
/// <summary>LEDs lit depend on the voltage on the input power jack (default)</summary>
VoltageDependant = -1,
/// <summary>LEDs off</summary>
Off = 0,
/// <summary>Orange</summary>
Orange = 1,
/// <summary>Green</summary>
Green = 2,
/// <summary>Orange and green together</summary>
Both = 3,
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Iot.Device.BuildHat.Models
{
/// <summary>
/// Minimum and maximum values for a specific mode type
/// </summary>
public struct MinimumMaximumValues
{
/// <summary>
/// Type of values
/// </summary>
public TypeValues TypeValues { get; set; }
/// <summary>
/// Minimum value
/// </summary>
public int MinimumValue { get; set; }
/// <summary>
/// Maximum value
/// </summary>
public int MaximumValue { get; set; }
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
namespace Iot.Device.BuildHat.Models
{
/// <summary>
/// Mode details
/// </summary>
public struct ModeDetail
{
/// <summary>
/// Gets the mode number.
/// </summary>
public int Number { get; internal set; }
/// <summary>
/// Gets the name of the mode
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// Gets the unit of the mode.
/// </summary>
public string Unit { get; internal set; }
/// <summary>
/// Gets the number of data items.
/// </summary>
public int NumberOfDataItems { get; internal set; }
/// <summary>
/// Gets the data type.
/// </summary>
public Type DataType { get; internal set; }
/// <summary>
/// Gets the number of chars to display the value
/// </summary>
public int NumberOfCharsToDisplay { get; internal set; }
/// <summary>
/// Gets the number of data in the mode
/// </summary>
public int NumberOfData { get; internal set; }
/// <summary>
/// Gets the decimal preciion (for float, 0 oterhwise)
/// </summary>
public int DecimalPrecision { get; internal set; }
/// <summary>
/// Gets the minimum and maximum values for the mode
/// </summary>
public MinimumMaximumValues[] MinimumMaximumValues { get; internal set; }
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Iot.Device.BuildHat.Models
{
/// <summary>
/// When running a motor to a position, the way to go.
/// </summary>
public enum PositionWay
{
/// <summary>Shortest way</summary>
Shortest,
/// <summary>Clockwise way</summary>
Clockwise,
/// <summary>Anti clockwise way</summary>
AntiClockwise,
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Iot.Device.BuildHat.Models
{
/// <summary>
/// Recommended PID settings
/// </summary>
public struct RecommendedPid
{
/// <summary>
/// Gets or sets the PID1
/// </summary>
public int Pid1 { get; set; }
/// <summary>
/// Gets or sets the PID2
/// </summary>
public int Pid2 { get; set; }
/// <summary>
/// Gets or sets the PID4
/// </summary>
public int Pid3 { get; set; }
/// <summary>
/// Gets or sets the PID5
/// </summary>
public int Pid4 { get; set; }
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Iot.Device.BuildHat.Models
{
/// <summary>
/// Sensor ports 1, 2, 3 and 4
/// </summary>
public enum SensorPort : byte
{
// Used to select the ports for sensors
/// <summary>Port A</summary>
PortA = 0,
/// <summary>Port B</summary>
PortB = 1,
/// <summary>Port C</summary>
PortC = 2,
/// <summary>Port D</summary>
PortD = 3,
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Iot.Device.BuildHat.Models
{
/// <summary>
/// All type of supported sensors
/// </summary>
public enum SensorType : byte
{
// Those ones are passive
/// <summary>None</summary>
None = 0,
/// <summary>System medium motor</summary>
SystemMediumMotor = 1,
/// <summary>System train motor</summary>
SystemTrainMotor = 2,
/// <summary>System turntable motor</summary>
SystemTurntableMotor = 3,
/// <summary>General PWM/third party</summary>
GeneralPwm = 4,
/// <summary>Button/touch sensor</summary>
ButtonOrTouchSensor = 5,
/// <summary>Technic large motor (some have active ID)</summary>
TechnicLargeMotor = 6,
/// <summary>Technic XL motor (some have active ID)</summary>
TechnicXLMotor = 7,
/// <summary>Simple lights</summary>
SimpleLights = 8,
/// <summary>Future lights 1</summary>
FutureLights1 = 9,
/// <summary>Future lights 2</summary>
FutureLights2 = 10,
/// <summary>System future actuator (train points)</summary>
SystemFutureActuator = 11,
// Following ones are active
/// <summary>WeDo tilt sensor</summary>
WeDoTiltSensor = 0x22,
/// <summary>Wido motion sensor</summary>
WeDoDistanceSensor = 0x23,
/// <summary>Colour and distance sensor</summary>
ColourAndDistanceSensor = 0x25,
/// <summary>Medium linear motor</summary>
MediumLinearMotor = 0x26,
/// <summary>Technic large motor</summary>
TechnicLargeMotorId = 0x2E,
/// <summary>Technic XL motor</summary>
TechnicXLMotorId = 0x2F,
/// <summary>SPIKE Prime medium motor</summary>
SpikePrimeMediumMotor = 0x30,
/// <summary>SPIKE Prime large motor</summary>
SpikePrimeLargeMotor = 0x31,
/// <summary>SPIKE Prime colour sensor</summary>
SpikePrimeColorSensor = 0x3D,
/// <summary>SPIKE Prime ultrasonic distance sensor</summary>
SpikePrimeUltrasonicDistanceSensor = 0x3E,
/// <summary>SPIKE Prime force sensor</summary>
SpikePrimeForceSensor = 0x3F,
/// <summary>SPIKE Essential 3x3 colour light matrix</summary>
SpikeEssential3x3ColorLightMatrix = 0x40,
/// <summary>SPIKE Essential small angular motor</summary>
SpikeEssentialSmallAngularMotor = 0x41,
/// <summary>Technic medium motor</summary>
TechnicMediumAngularMotor = 0x4B,
/// <summary>Techni motor</summary>
TechnicMotor = 0x4C,
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Iot.Device.BuildHat.Models
{
/// <summary>
/// The type of values for each mode
/// </summary>
public enum TypeValues
{
/// <summary>Raw values</summary>
Raw,
/// <summary>Percent values</summary>
Percent,
/// <summary>Signal values</summary>
Signal,
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Iot.Device.BuildHat.Models;
using Iot.Device.BuildHat.Sensors;
namespace Iot.Device.BuildHat.Motors
{
/// <summary>
/// Active motor
/// </summary>
public class ActiveMotor : ActiveSensor, IMotor
{
private int _tacho;
private int _absoluteTacho;
private int _speed;
internal double PowerLimit;
/// <summary>
/// Gets or sets the target speed
/// </summary>
public int TargetSpeed { get; set; }
/// <inheritdoc/>
public int Speed
{
get => _speed;
internal set
{
if (_speed != value)
{
_speed = value;
OnPropertyChanged(nameof(Speed));
}
OnPropertyUpdated(nameof(Speed));
}
}
/// <summary>
/// Gets the current tachometer count.
/// </summary>
public int Position
{
get => _tacho;
internal set
{
if (_tacho != value)
{
_tacho = value;
OnPropertyChanged(nameof(Position));
}
OnPropertyUpdated(nameof(Position));
}
}
/// <summary>
/// Gets the current tachometer count.
/// </summary>
public int AbsolutePosition
{
get => _absoluteTacho;
internal set
{
if (_absoluteTacho != value)
{
_absoluteTacho = value;
OnPropertyChanged(nameof(AbsolutePosition));
}
OnPropertyUpdated(nameof(AbsolutePosition));
}
}
/// <inheritdoc/>
public override string SensorName => GetMotorName();
/// <summary>
/// Creates an active motor.
/// </summary>
/// <param name="brick">The brick.</param>
/// <param name="port">The port.</param>
/// <param name="motorType">The active motor type.</param>
protected internal ActiveMotor(Brick brick, SensorPort port, SensorType motorType)
: base(brick, port, motorType)
{
// Set a defautl plimit and bias, the default ones are too small especially
// the plimit one
SetBias(0.3);
SetPowerLimit(0.7);
}
/// <inheritdoc/>
public string GetMotorName() => SensorType switch
{
SensorType.TechnicXLMotorId => "Technic XL motor",
SensorType.TechnicLargeMotorId => "Technic large motor",
SensorType.MediumLinearMotor => "Medium linear motor",
SensorType.SpikeEssentialSmallAngularMotor => "SPIKE Essential small angular motor",
SensorType.SpikePrimeLargeMotor => "SPIKE Prime large motor",
SensorType.SpikePrimeMediumMotor => "SPIKE Prime medium motor",
SensorType.TechnicMediumAngularMotor => "Technical medium angular motor",
SensorType.TechnicMotor => "Technical motor",
_ => string.Empty,
};
/// <inheritdoc/>
public int GetSpeed() => Speed;
/// <summary>
/// Gets the current tachometer count.
/// </summary>
/// <returns>The current tachometer count.</returns>
public int GetPosition() => Position;
/// <summary>
/// Gets the current absolute tachometer count.
/// </summary>
/// <returns>The current absolute tachometer count.</returns>
public int GetAbsolutePosition() => AbsolutePosition;
/// <inheritdoc/>
public void SetSpeed(int speed) => TargetSpeed = speed;
/// <inheritdoc/>
public void Start()
{
Brick.SetMotorPower(Port, TargetSpeed);
}
/// <inheritdoc/>
public void Start(int speed)
{
SetSpeed(speed);
Start();
}
/// <inheritdoc/>
public void Stop()
{
SetSpeed(0);
Brick.SetMotorPower(Port, TargetSpeed);
}
/// <inheritdoc/>
public void SetBias(double bias) => Brick.SetMotorBias(Port, bias);
/// <inheritdoc/>
public void SetPowerLimit(double plimit)
{
Brick.SetMotorLimits(Port, plimit);
PowerLimit = plimit;
}
/// <summary>
/// Run the motor to an absolute position.
/// </summary>
/// <param name="targetPosition">The target angle from -180 to +180.</param>
/// <param name="way">The way to go to the position.</param>
/// <param name="blocking">True to block the function and wait for the execution.</param>
public void MoveToAbsolutePosition(int targetPosition, PositionWay way, bool blocking = false) => Brick.MoveMotorToAbsolutePosition(Port, targetPosition, way, TargetSpeed, blocking);
/// <summary>
/// Run the specified motors for an amount of seconds.
/// </summary>
/// <param name="seconds">The amount of seconds.</param>
/// <param name="blocking">True to block the function and wait for the execution.</param>
public void MoveForSeconds(double seconds, bool blocking = false) => Brick.MoveMotorForSeconds(Port, seconds, TargetSpeed, blocking);
/// <summary>
/// Run the motor to an absolute position.
/// </summary>
/// <param name="targetPosition">The target angle from -180 to +180.</param>
/// <param name="blocking">True to block the function and wait for the execution.</param>
public void MoveToPosition(int targetPosition, bool blocking = false) => Brick.MoveMotorToPosition(Port, targetPosition, TargetSpeed, blocking);
/// <summary>
/// Run the motor for a specific number of degrees.
/// </summary>
/// <param name="targetPosition">The target angle in degrees.</param>
/// <param name="blocking">True to block the function and wait for the execution.</param>
public void MoveForDegrees(int targetPosition, bool blocking = false) => Brick.MoveMotorForDegrees(Port, targetPosition, TargetSpeed, blocking);
/// <inheritdoc/>
public void Float() => Brick.FloatMotor(Port);
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Iot.Device.BuildHat.Models;
namespace Iot.Device.BuildHat.Motors
{
/// <summary>
/// Interface for a motor
/// </summary>
public interface IMotor
{
/// <summary>
/// Set the speed of the motor
/// </summary>
/// <param name="speed">speed is between -100 and +100</param>
void SetSpeed(int speed);
/// <summary>
/// Stop the Motor
/// </summary>
void Stop();
/// <summary>
/// Start the motor
/// </summary>
void Start();
/// <summary>
/// Start with the specified speed
/// </summary>
/// <param name="speed">speed is between -100 and +100</param>
void Start(int speed);
/// <summary>
/// Get the speed
/// </summary>
/// <returns>speed is between -100 and +100</returns>
int GetSpeed();
/// <summary>
/// Sets the bias of the motor.
/// </summary>
/// <param name="bias">Bias, must be between 0 and 1.</param>
void SetBias(double bias);
/// <summary>
/// Sets the power consumption limit.
/// </summary>
/// <param name="plimit">The power consumption limit. Must be between 0 and 1.</param>
void SetPowerLimit(double plimit);
/// <summary>
/// Gets the speed of the motor
/// speed is between -100 and +100
/// </summary>
int Speed { get; }
/// <summary>
/// Motor port
/// </summary>
SensorPort Port { get; }
/// <summary>
/// Motor type
/// </summary>
SensorType SensorType { get; }
/// <summary>
/// Gets the name of the sensor.
/// </summary>
/// <returns>The sensor name.</returns>
string GetMotorName();
/// <summary>
/// Gets true if the motor is connected.
/// </summary>
bool IsConnected { get; }
/// <summary>
/// Floats the motor and stop all constraints on it.
/// </summary>
void Float();
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Iot.Device.BuildHat.Models;
using Iot.Device.BuildHat.Sensors;
namespace Iot.Device.BuildHat.Motors
{
/// <summary>
/// Creates a passive motor
/// </summary>
public class PassiveMotor : Sensor, IMotor
{
private bool _isRunning;
private int _speed;
/// <inheritdoc/>
public int Speed { get => _speed; set => SetSpeed(value); }
/// <summary>
/// Creates a passive motor.
/// </summary>
/// <param name="brick">The brick.</param>
/// <param name="port">The port.</param>
/// <param name="motorType">The active motor type.</param>
protected internal PassiveMotor(Brick brick, SensorPort port, SensorType motorType)
: base(brick, port, motorType)
{
// Set a defautl plimit and bias, the default ones are too small especially
// the plimit one
SetBias(0.3);
SetPowerLimit(0.7);
}
/// <inheritdoc/>
public string GetMotorName() => SensorType switch
{
SensorType.SystemTrainMotor => "System train motor",
SensorType.SystemTurntableMotor => "System turntable motor",
SensorType.SystemMediumMotor => "System medium Motor",
SensorType.TechnicLargeMotor => "Technic large motor",
SensorType.TechnicXLMotor => "Technic XL motor",
_ => string.Empty,
};
/// <inheritdoc/>
public override string SensorName => GetMotorName();
/// <inheritdoc/>
public int GetSpeed() => Speed;
/// <inheritdoc/>
public void SetBias(double bias) => Brick.SetMotorBias(Port, bias);
/// <inheritdoc/>
public void SetPowerLimit(double plimit) => Brick.SetMotorLimits(Port, plimit);
/// <inheritdoc/>
public void SetSpeed(int speed)
{
_speed = speed;
if (_isRunning)
{
Start();
}
}
/// <inheritdoc/>
public void Start()
{
Brick.SetMotorPower(Port, Speed);
_isRunning = true;
}
/// <inheritdoc/>
public void Start(int speed)
{
SetSpeed(speed);
Start();
}
/// <inheritdoc/>
public void Stop()
{
SetSpeed(0);
_isRunning = false;
}
/// <inheritdoc/>
public void Float() => Brick.FloatMotor(Port);
}
}
此差异已折叠。
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Iot.Device.BuildHat {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resource {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resource() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Iot.Device.BuildHat.Resource", typeof(Resource).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] firmware {
get {
object obj = ResourceManager.GetObject("firmware", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] signature {
get {
object obj = ResourceManager.GetObject("signature", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] version {
get {
object obj = ResourceManager.GetObject("version", resourceCulture);
return ((byte[])(obj));
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="firmware" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>data\firmware.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="signature" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>data\signature.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="version" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>data\version;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>
\ No newline at end of file
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Iot.Device.BuildHat.Models;
namespace Iot.Device.BuildHat.Sensors
{
/// <summary>
/// Interface that all active elements implement
/// </summary>
public class ActiveSensor : Sensor
{
// This is used when measuring internal sensors, that's the maximum time
// any sensor will respond according to tsts and reading various sources
internal const int TimeoutMeasuresSeconds = 4;
// Thoss fields is populated by the birck once it is enumerated.
internal List<ModeDetail> ModeDetailsInternal;
internal List<CombiModes> CombiModesInternal;
// This is used for advance mapping between the modes and the properties
internal int[] CombiReadingModes = new int[0];
// For the property
internal string[] _valuesAsString = new string[0];
internal bool _hasValueAsStringUpdated;
/// <summary>
/// Property to return the raw value of the sensort as a string. It will contains as the first elements PxCy.
/// x = the port number, y = 0 for continuous reading, 1 for single reading.
/// The rest are the measures.
/// </summary>
public IEnumerable<string> ValuesAsString
{
get => _valuesAsString;
internal set
{
if (!_valuesAsString.SequenceEqual(value))
{
_valuesAsString = value.ToArray();
OnPropertyChanged(nameof(ValuesAsString));
}
_hasValueAsStringUpdated = true;
OnPropertyUpdated(nameof(ValuesAsString));
}
}
/// <summary>
/// Baud rate the sensor is connected
/// </summary>
public int BaudRate { get; internal set; }
/// <summary>
/// Hardware version
/// </summary>
public uint HardwareVersion { get; internal set; }
/// <summary>
/// Software version
/// </summary>
public uint SoftwareVersion { get; internal set; }
/// <summary>
/// Gets the possible combi modes. Note, is will be empty if none.
/// </summary>
public IEnumerable<CombiModes> CombiModes { get => CombiModesInternal.ToArray(); }
/// <summary>
/// Gets the mode details of the sensor.
/// </summary>
public IEnumerable<ModeDetail> ModeDetails { get => ModeDetailsInternal.ToArray(); }
/// <summary>
/// Numbers the of modes.
/// </summary>
/// <returns>The number of modes</returns>
public int NumberOfModes { get => ModeDetailsInternal.Count; }
/// <summary>
/// Gets the recommended Speed PID settings
/// </summary>
public RecommendedPid SpeedPid { get; internal set; }
/// <summary>
/// Gets the recommended Speed PID settings
/// </summary>
public RecommendedPid PositionPid { get; internal set; }
/// <summary>
/// Creates an active element.
/// </summary>
/// <param name="brick">The brick.</param>
/// <param name="port">The port.</param>
/// <param name="type">The sensor type.</param>
protected internal ActiveSensor(Brick brick, SensorPort port, SensorType type)
: base(brick, port, type)
{
ModeDetailsInternal = new List<ModeDetail>();
CombiModesInternal = new List<CombiModes>();
}
/// <summary>
/// Returned the name of the selectd mode
/// </summary>
/// <param name="modes">The mode to select</param>
/// <param name="once">True to read only once, false to set the continuous reading</param>
public void SelectCombiModesAndRead(int[] modes, bool once) => Brick.SelectCombiModesAndRead(Port, modes, once);
/// <summary>
/// Returned the name of the selectd mode
/// </summary>
/// <param name="mode">The mode to select</param>
/// <param name="once">True to read only once, false to set the continuous reading</param>
public void SelectModeAndRead(int mode, bool once) => Brick.SelectModeAndRead(Port, mode, once);
/// <inheritdoc/>
public override string SensorName => "Active sensor";
/// <summary>
/// Stop reading continuous data from a specific sensor.
/// </summary>
public void StopReading() => Brick.StopContinuousReadingSensor(Port);
/// <summary>
/// Switches a sensor on.
/// </summary>
/// <remarks>In case of a motor, this will switch the motor on full speed.</remarks>
public void SwitchOn() => Brick.SwitchSensorOn(Port);
/// <summary>
/// Switches a sensor off.
/// </summary>
/// <remarks>In case of a motor, this will switch off the motor.</remarks>
public void SwitchOff(SensorPort port) => Brick?.SwitchSensorOff(Port);
/// <summary>
/// Writes directly to the sensor. The bytes to the current port, the first one or two bytes being header bytes. The message is padded if
/// necessary, and length and checksum fields are automatically populated.
/// </summary>
/// <param name="data">The buffer to send.</param>
/// <param name="singleHeader">True for single header byte.</param>
public void WriteBytes(ReadOnlySpan<byte> data, bool singleHeader) => Brick.WriteBytesToSensor(Port, data, singleHeader);
internal bool SetupModeAndRead(int mode, ref bool trigger, bool once = true)
{
trigger = false;
DateTime dt = DateTime.Now.AddSeconds(TimeoutMeasuresSeconds);
Brick.SelectModeAndRead(Port, mode, once);
while (!trigger && (dt > DateTime.Now))
{
Thread.Sleep(10);
}
return trigger;
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Iot.Device.BuildHat.Models;
namespace Iot.Device.BuildHat.Sensors
{
/// <summary>
/// A simple passive button.
/// </summary>
public class ButtonSensor : Sensor
{
private bool _isPressed;
/// <summary>
/// Gets true when the button is pressed.
/// </summary>
public bool IsPressed
{
get => _isPressed;
internal set
{
if (_isPressed != value)
{
_isPressed = value;
OnPropertyChanged(nameof(IsPressed));
}
OnPropertyUpdated(nameof(IsPressed));
}
}
/// <inheritdoc/>
public override string SensorName => "Button sensor";
/// <summary>
/// Button sensor.
/// </summary>
/// <param name="brick">The brick.</param>
/// <param name="port">The port.</param>
protected internal ButtonSensor(Brick brick, SensorPort port)
: base(brick, port, SensorType.ButtonOrTouchSensor)
{
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.IO;
using Iot.Device.BuildHat.Models;
namespace Iot.Device.BuildHat.Sensors
{
/// <summary>
/// Color and distance sensor.
/// </summary>
public class ColorAndDistanceSensor : ColorSensor
{
private int _distance;
private bool _hasDistanceUpdated;
private int _counter;
private bool _hasCounterUpdated;
/// <summary>
/// Gets the distance from the object
/// </summary>
public int Distance
{
get => _distance;
internal set
{
if (_distance != value)
{
_distance = value;
OnPropertyChanged(nameof(Distance));
}
_hasDistanceUpdated = true;
OnPropertyUpdated(nameof(Distance));
}
}
/// <summary>
/// Gets the counter of cumulated object detected.
/// </summary>
public int Counter
{
get => _counter;
internal set
{
if (_counter != value)
{
_counter = value;
OnPropertyChanged(nameof(Counter));
}
_hasCounterUpdated = true;
OnPropertyUpdated(nameof(Counter));
}
}
/// <inheritdoc/>
public override string SensorName => "Color and distance sensor";
/// <summary>
/// Creates a color and distance sensor.
/// </summary>
/// <param name="brick">The brick.</param>
/// <param name="port">The port.</param>
protected internal ColorAndDistanceSensor(Brick brick, SensorPort port)
: base(brick, port, SensorType.ColourAndDistanceSensor)
{
}
/// <summary>
/// Gets the distance of the object from 0 to +10 cm
/// </summary>
/// <returns>The distance from 0 to +10 cm.</returns>
public int GetDistance()
{
if (SetupModeAndRead(1, ref _hasDistanceUpdated))
{
return Distance;
}
throw new IOException("Can't measure the distance.");
}
/// <summary>
/// Gets the the counter of cumulated object detected.
/// </summary>
/// <returns>The counter.</returns>
public int GetCounter()
{
if (SetupModeAndRead(2, ref _hasCounterUpdated))
{
return Counter;
}
throw new IOException("Can't update the counter.");
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Text;
using Iot.Device.BuildHat.Models;
namespace Iot.Device.BuildHat.Sensors
{
/// <summary>
/// SPIKE 3x3 color light matrix.
/// </summary>
public class ColorLightMatrix : ActiveSensor
{
/// <inheritdoc/>
public override string SensorName => "SPIKE 3x3 color light matrix";
/// <summary>
/// Color light matrix.
/// </summary>
/// <param name="brick">The brick.</param>
/// <param name="port">The port.</param>
protected internal ColorLightMatrix(Brick brick, SensorPort port)
: base(brick, port, SensorType.SpikeEssential3x3ColorLightMatrix)
{
Brick.SendRawCommand($"port {(byte)Port} ; plimit 1 ; set -1\r");
}
/// <summary>
/// Displays a progress bar style from 0 to 9.
/// </summary>
/// <param name="progress">The progress bar from 0 to 9.</param>
public void DisplayProgressBar(byte progress)
{
if (progress > 9)
{
throw new ArgumentException("Progress can only be from 0 to 9");
}
Brick.SendRawCommand($"port {(byte)Port} ; select 0 ; write1 c0 {progress}");
}
/// <summary>
/// Displays the 9 leds with the same color.
/// </summary>
/// <param name="color">The color to use.</param>
public void DisplayColor(LedColor color)
{
Brick.SendRawCommand($"port {(byte)Port} ; select 1 ; write1 c1 {(byte)color:X}");
}
/// <summary>
/// Displays each pixel with a color and brightness
/// </summary>
/// <param name="brightness">The brichtness from 0 (off) to 10 (full). Must be 9 elements.</param>
/// <param name="colors">The color. Must be 9 elements.</param>
public void DisplayColorPerPixel(ReadOnlySpan<byte> brightness, ReadOnlySpan<LedColor> colors)
{
if ((brightness.Length != 9) || (colors.Length != 9))
{
throw new ArgumentException("Brightness and colors must be 9 elements.");
}
StringBuilder command = new($"port {(byte)Port} ; select 2 ; write1 c2 ");
for (int i = 0; i < brightness.Length; i++)
{
if (brightness[i] > 10)
{
throw new ArgumentException("Brightness must be between 0 and 10");
}
command.Append($"{brightness[i]:X}{(byte)colors[i]:X} ");
}
command.Append("\r");
Brick.SendRawCommand(command.ToString());
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.IO;
using Iot.Device.BuildHat.Models;
using SixLabors.ImageSharp;
namespace Iot.Device.BuildHat.Sensors
{
/// <summary>
/// Color sensor
/// </summary>
public class ColorSensor : ActiveSensor
{
internal Color _color;
internal bool _isColorDetected;
internal bool _hasColorUpdated;
private int _reflected;
private bool _hasReflectedUpdated;
private int _ambiant;
private bool _hasAmbiantUpdated;
/// <summary>
/// Gets the last measured Color
/// </summary>
public Color Color
{
get => _color;
internal set
{
if (_color != value)
{
_color = value;
OnPropertyChanged(nameof(Color));
}
_hasColorUpdated = true;
OnPropertyUpdated(nameof(Color));
}
}
/// <summary>
/// Gets true if a color is detected.
/// </summary>
public bool IsColorDetected
{
get => _isColorDetected;
internal set
{
if (_isColorDetected != value)
{
_isColorDetected = value;
OnPropertyChanged(nameof(IsColorDetected));
}
OnPropertyUpdated(nameof(IsColorDetected));
}
}
/// <summary>
/// Gets the reflected light.
/// </summary>
public int ReflectedLight
{
get => _reflected;
internal set
{
if (_reflected != value)
{
_reflected = value;
OnPropertyChanged(nameof(ReflectedLight));
}
_hasReflectedUpdated = true;
OnPropertyUpdated(nameof(ReflectedLight));
}
}
/// <summary>
/// Gets the ambiant light.
/// </summary>
public int AmbiantLight
{
get => _ambiant;
internal set
{
if (_ambiant != value)
{
_ambiant = value;
OnPropertyChanged(nameof(AmbiantLight));
}
_hasAmbiantUpdated = true;
OnPropertyUpdated(nameof(AmbiantLight));
}
}
/// <inheritdoc/>
public override string SensorName => "Color sensor";
/// <summary>
/// Creates a color sensor.
/// </summary>
/// <param name="brick">The brick.</param>
/// <param name="port">The port.</param>
/// <param name="type">The sensor type</param>
protected internal ColorSensor(Brick brick, SensorPort port, SensorType type)
: base(brick, port, type)
{
if (SensorType == SensorType.SpikePrimeColorSensor)
{
Brick.SendRawCommand($"port {(byte)Port} ; plimit 1 ; set -1");
}
else if (SensorType == SensorType.ColourAndDistanceSensor)
{
Brick.SwitchSensorOn(Port);
}
}
/// <summary>
/// Gets the color, measure the nulmber of setup times.
/// </summary>
/// <returns>The color.</returns>
public Color GetColor()
{
if (SetupModeAndRead(6, ref _hasColorUpdated))
{
return Color;
}
throw new IOException("Can't measure the color.");
}
/// <summary>
/// Gets the reflected component.
/// </summary>
/// <returns>The reflected component.</returns>
public int GetReflectedLight()
{
if (SetupModeAndRead(3, ref _hasReflectedUpdated))
{
return ReflectedLight;
}
throw new IOException("Can't measure the reflected light.");
}
/// <summary>
/// Gets the ambiant light.
/// </summary>
/// <returns>The ambiant light.</returns>
public int GetAmbiantLight()
{
if (SetupModeAndRead(4, ref _hasAmbiantUpdated))
{
return AmbiantLight;
}
throw new IOException("Can't measure the ambiant light.");
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.IO;
using Iot.Device.BuildHat.Models;
namespace Iot.Device.BuildHat.Sensors
{
/// <summary>
/// Spike force sensor
/// </summary>
public class ForceSensor : ActiveSensor
{
private int _force;
private bool _hasForceUpdated;
private bool _continuous;
private bool _isPressed;
private bool _hasIsPressedUpdated;
/// <summary>
/// Gets the force in Newtown.
/// </summary>
public int Force
{
get => _force;
internal set
{
if (_force != value)
{
_force = value;
OnPropertyChanged(nameof(Force));
}
_hasForceUpdated = true;
OnPropertyUpdated(nameof(Force));
}
}
/// <summary>
/// Gets the force in Newtown.
/// </summary>
public bool IsPressed
{
get => _isPressed;
internal set
{
if (_isPressed != value)
{
_isPressed = value;
OnPropertyChanged(nameof(IsPressed));
}
_hasIsPressedUpdated = true;
OnPropertyUpdated(nameof(IsPressed));
}
}
/// <summary>
/// Gets or sets the continuous measurement for this sensor.
/// </summary>
public bool ContinousMeasurement
{
get => _continuous;
set
{
if (_continuous != value)
{
_continuous = value;
if (_continuous)
{
Brick.SelectModeAndRead(Port, 0, _continuous);
}
else
{
Brick.StopContinuousReadingSensor(Port);
}
}
}
}
/// <inheritdoc/>
public override string SensorName => "SPIKE force sensor sensor";
/// <summary>
/// Force sensor.
/// </summary>
/// <param name="brick">The brick.</param>
/// <param name="port">The port.</param>
protected internal ForceSensor(Brick brick, SensorPort port)
: base(brick, port, SensorType.SpikePrimeForceSensor)
{
}
/// <summary>
/// Gets the force in N
/// </summary>
/// <returns></returns>
public int GetForce()
{
if (SetupModeAndRead(0, ref _hasForceUpdated, ContinousMeasurement))
{
return Force;
}
throw new IOException("Can't measure the force.");
}
/// <summary>
/// Gets if the sensor is pressed
/// </summary>
/// <returns></returns>
public bool GetPressed()
{
if (SetupModeAndRead(0, ref _hasIsPressedUpdated, ContinousMeasurement))
{
return IsPressed;
}
throw new IOException("Can't measure is the sensor is pressed.");
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using Iot.Device.BuildHat.Models;
namespace Iot.Device.BuildHat.Sensors
{
/// <summary>
/// A simple light like the train ones.
/// </summary>
public class PassiveLight : Sensor
{
private bool _isOn;
private int _brightness;
/// <summary>
/// Sets the brightness from 0 to 100.
/// </summary>
public int Brightness { get => _brightness; set => SetBrightness(value); }
/// <summary>
/// Gets the name of the sensor.
/// </summary>
/// <returns>The sensor name.</returns>
public override string SensorName { get => "Passive light"; }
/// <summary>
/// PAssive light.
/// </summary>
/// <param name="brick">The brick.</param>
/// <param name="port">The port.</param>
protected internal PassiveLight(Brick brick, SensorPort port)
: base(brick, port, SensorType.SimpleLights)
{
}
/// <summary>
/// Sets the brigthness from 0 to 100.
/// </summary>
/// <param name="brightness">The brightness from 0 to 100.</param>
public void SetBrightness(int brightness)
{
if ((brightness < 0) || (brightness > 100))
{
throw new ArgumentException("Brightness can only be between 0 (off) and 100 (full bright)");
}
_brightness = brightness;
if (_isOn)
{
On();
}
}
/// <summary>
/// Switch on the light.
/// </summary>
public void On()
{
Brick.SetMotorPower(Port, Brightness);
_isOn = true;
}
/// <summary>
/// Switches on the light with a specific brightness.
/// </summary>
/// <param name="brightness">The brightness from 0 to 100.</param>
public void On(int brightness)
{
SetBrightness(brightness);
On();
}
/// <summary>
/// Switechs off the light.
/// </summary>
public void Off()
{
SetBrightness(0);
_isOn = false;
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
using Iot.Device.BuildHat.Models;
namespace Iot.Device.BuildHat.Sensors
{
/// <summary>
/// Interface for a sensor
/// </summary>
public class Sensor
{
internal Brick Brick;
/// <summary>
/// Gets true if the sensor is connected.
/// </summary>
public bool IsConnected { get; internal set; }
/// <summary>
/// Creates a sensor
/// </summary>
/// <param name="brick">The brick.</param>
/// <param name="port">The port.</param>
/// <param name="type">The sensor type.</param>
protected internal Sensor(Brick brick, SensorPort port, SensorType type)
{
Brick = brick;
Port = port;
SensorType = type;
}
/// <summary>
/// Gets the name of the sensor.
/// </summary>
/// <returns>The sensor name.</returns>
public virtual string SensorName { get => "Generic sensor"; }
/// <summary>
/// Gets the Sensor port
/// </summary>
/// <returns>The sensor port</returns>
public SensorPort Port { get; internal set; }
/// <summary>
/// Gets the sensor type
/// </summary>
public SensorType SensorType { get; internal set; }
/// <summary>
/// To notify a property has changed. It means the value has changed.
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// To notify a property has been updated. It means the property has been updated regardeless of a change in its value.
/// </summary>
public event PropertyChangedEventHandler? PropertyUpdated;
/// <summary>
/// Raises the on property changed event.
/// </summary>
/// <param name="name">The property name.</param>
protected internal void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
/// <summary>
/// Raises the on property updated event.
/// </summary>
/// <param name="name">The property name.</param>
protected internal void OnPropertyUpdated(string name) => PropertyUpdated?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.IO;
using Iot.Device.BuildHat.Models;
namespace Iot.Device.BuildHat.Sensors
{
/// <summary>
/// Spike distance sensor.
/// </summary>
public class UltrasonicDistanceSensor : ActiveSensor
{
private int _distance;
private bool _hasDistanceUpdated;
private bool _continuous;
/// <summary>
/// Gets the distance. A number is in millimeters.
/// </summary>
public int Distance
{
get => _distance;
internal set
{
if (_distance != value)
{
_distance = value;
OnPropertyChanged(nameof(Distance));
}
_hasDistanceUpdated = true;
OnPropertyUpdated(nameof(Distance));
}
}
/// <summary>
/// Gets or sets the continuous measurement for this sensor.
/// </summary>
public bool ContinousMeasurement
{
get => _continuous;
set
{
if (_continuous != value)
{
_continuous = value;
if (_continuous)
{
Brick.SelectModeAndRead(Port, 1, _continuous);
}
else
{
Brick.StopContinuousReadingSensor(Port);
}
}
}
}
/// <inheritdoc/>
public override string SensorName => "SPIKE ultrasonic distance sensor";
/// <summary>
/// Ultrasonic distance sensor.
/// </summary>
/// <param name="brick">The brick.</param>
/// <param name="port">The port.</param>
protected internal UltrasonicDistanceSensor(Brick brick, SensorPort port)
: base(brick, port, SensorType.SpikePrimeUltrasonicDistanceSensor)
{
Brick.SendRawCommand($"port {(byte)Port} ; plimit 1 ; set -1\r");
}
/// <summary>
/// Gets the distance. From 0 to +10 cm.
/// </summary>
/// <returns></returns>
public int GetDistance()
{
if (SetupModeAndRead(1, ref _hasDistanceUpdated, ContinousMeasurement))
{
return Distance;
}
throw new IOException("Can't measure the distance.");
}
/// <summary>
/// Adjust the brightness of the eyes.
/// </summary>
/// <param name="eyes">The brighness percentage for each of the 4 leds from 0 to 100.</param>
public void AdjustEyesBrightness(ReadOnlySpan<byte> eyes)
{
if (eyes.Length != 4)
{
throw new ArgumentException("You must have exactly 4 brightness for the eyes leds.");
}
Brick.SendRawCommand($"port {(byte)Port} ; select 5 ; write1 {eyes[0]:X2} {eyes[1]:X2} {eyes[2]:X2} {eyes[3]:X2}");
if (ContinousMeasurement)
{
// Set continuous mode if it was present
Brick.SelectModeAndRead(Port, 1, _continuous);
}
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.IO;
using Iot.Device.BuildHat.Models;
namespace Iot.Device.BuildHat.Sensors
{
/// <summary>
/// WeDo distance sensor.
/// </summary>
public class WeDoDistanceSensor : ActiveSensor
{
private int _distance;
private bool _hasDistanceUpdated;
private bool _continuous;
/// <summary>
/// Gets the distance. A number between 0 and 10 cm.
/// </summary>
public int Distance
{
get => _distance;
internal set
{
if (_distance != value)
{
_distance = value;
OnPropertyChanged(nameof(Distance));
}
_hasDistanceUpdated = true;
OnPropertyUpdated(nameof(Distance));
}
}
/// <summary>
/// Gets or sets the continuous measurement for this sensor.
/// </summary>
public bool ContinousMeasurement
{
get => _continuous;
set
{
if (_continuous != value)
{
_continuous = value;
if (_continuous)
{
Brick.SelectModeAndRead(Port, 0, _continuous);
}
else
{
Brick.StopContinuousReadingSensor(Port);
}
}
}
}
/// <inheritdoc/>
public override string SensorName => "WeDo distance sensor";
/// <summary>
/// WeDo ditance sensor.
/// </summary>
/// <param name="brick">The brick.</param>
/// <param name="port">The port.</param>
protected internal WeDoDistanceSensor(Brick brick, SensorPort port)
: base(brick, port, SensorType.WeDoDistanceSensor)
{
}
/// <summary>
/// Gets the distance. From 0 to +10 cm.
/// </summary>
/// <returns></returns>
public int GetDistance()
{
if (SetupModeAndRead(0, ref _hasDistanceUpdated, ContinousMeasurement))
{
return Distance;
}
throw new IOException("Can't measure the distance.");
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.IO;
using Iot.Device.BuildHat.Models;
using SixLabors.ImageSharp;
namespace Iot.Device.BuildHat.Sensors
{
/// <summary>
/// WeDO tilt sensor
/// </summary>
public class WeDoTiltSensor : ActiveSensor
{
private Point _tilt;
private bool _hasTiltUpdated;
private bool _continuous;
/// <summary>
/// Gets the tilt. It's an angle from -45 to +45
/// </summary>
public Point Tilt
{
get => _tilt;
internal set
{
if (_tilt != value)
{
_tilt = value;
OnPropertyChanged(nameof(Tilt));
}
_hasTiltUpdated = true;
OnPropertyUpdated(nameof(Tilt));
}
}
/// <summary>
/// Gets or sets the continuous measurement for this sensor.
/// </summary>
public bool ContinousMeasurement
{
get => _continuous;
set
{
if (_continuous != value)
{
_continuous = value;
if (_continuous)
{
Brick.SelectModeAndRead(Port, 0, _continuous);
}
else
{
Brick.StopContinuousReadingSensor(Port);
}
}
}
}
/// <inheritdoc/>
public override string SensorName => "WeDo tilt sensor";
/// <summary>
/// WeDo tilt sensor.
/// </summary>
/// <param name="brick">The brick.</param>
/// <param name="port">The port.</param>
protected internal WeDoTiltSensor(Brick brick, SensorPort port)
: base(brick, port, SensorType.WeDoTiltSensor)
{
}
/// <summary>
/// Gets the tilt
/// </summary>
/// <returns></returns>
/// <exception cref="IOException"></exception>
public Point GetTilt()
{
if (SetupModeAndRead(0, ref _hasTiltUpdated))
{
return Tilt;
}
throw new IOException("Can't measure the tilt.");
}
}
}
; >g]mVV!Lٙ ]NF鴋X|[-;&X8\Son|$
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(DefaultSampleTfms)</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\BuildHat.csproj" />
</ItemGroup>
</Project>
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.ComponentModel;
using System.Threading;
using Iot.Device.BuildHat;
using Iot.Device.BuildHat.Models;
using Iot.Device.BuildHat.Motors;
using Iot.Device.BuildHat.Sensors;
Console.WriteLine("Hello, BuildHat!");
bool continueToRun = true;
// On Windows, connected through a serial dongle:
using Brick brick = new("COM3");
// On a Raspberry PI, you'll use:
// using Brick brick = new("/dev/serial0");
var info = brick.BuildHatInformation;
Console.WriteLine($"version: {info.Version}, firmware date: {info.FirmwareDate}, signature:");
Console.WriteLine($"{BitConverter.ToString(info.Signature)}");
Console.WriteLine($"Vin = {brick.InputVoltage.Volts} V");
Console.WriteLine("Select what you want to test:");
Console.WriteLine(" 1. Display elements details");
Console.WriteLine(" 2. Display connection/disconnecion");
Console.WriteLine(" 3. Move Motors on port A and D");
Console.WriteLine(" 4. PID with motors on port A");
Console.WriteLine(" 5. Read color sensor on Port C");
Console.WriteLine(" 6. Run train motor on Port B");
Console.WriteLine(" 7. Events with motor properties");
Console.WriteLine(" 8. Display matrix 3x3 on Port A");
while (!Console.KeyAvailable)
{
Thread.Sleep(100);
}
var choice = Console.ReadKey();
switch (choice.KeyChar)
{
case '1':
DisplayElementDetails();
break;
case '2':
DisplayConnectionDisconnection();
break;
case '3':
MoveMotorsAndBackToPosition();
break;
case '4':
DriveMotors();
break;
case '5':
ReadColorDistance();
break;
case '6':
RunTrainMotor();
break;
case '7':
MotorPropertyEventExample();
break;
case '8':
DisplayMatrix3x3();
break;
default:
break;
}
void DisplayElementDetails()
{
Console.WriteLine("Displaying details of all the connected elements");
// Display all the details of all the sensors
for (int i = 0; i < 4; i++)
{
SensorType sensor = brick.GetSensorType((SensorPort)i);
Console.Write($"Port: {i} {(Brick.IsMotor(sensor) ? "Sensor" : "Motor")} type: {sensor} Connected: ");
if (Brick.IsActiveSensor(sensor))
{
ActiveSensor activeSensor = brick.GetActiveSensor((SensorPort)i);
Console.WriteLine($"{activeSensor.IsConnected}");
foreach (var mode in activeSensor.ModeDetails)
{
Console.WriteLine($" M{mode.Number} {mode.Name} {mode.Unit}");
Console.WriteLine($" format count={mode.NumberOfData} type={mode.DataType} chars={mode.NumberOfCharsToDisplay} dp={mode.DecimalPrecision}");
foreach (var minmax in mode.MinimumMaximumValues)
{
Console.WriteLine($" {minmax.TypeValues} min={minmax.MinimumValue} max={minmax.MaximumValue}");
}
}
foreach (var combi in activeSensor.CombiModes)
{
Console.Write($" C{combi.Number} ");
foreach (var m in combi.Modes)
{
Console.Write($"{m} ");
}
Console.WriteLine();
}
Console.WriteLine($"Speed: {activeSensor.SpeedPid.Pid1} {activeSensor.SpeedPid.Pid2} {activeSensor.SpeedPid.Pid3} {activeSensor.SpeedPid.Pid4}");
Console.WriteLine($"Position: {activeSensor.PositionPid.Pid1} {activeSensor.PositionPid.Pid2} {activeSensor.PositionPid.Pid3} {activeSensor.PositionPid.Pid4}");
}
else
{
var passive = (Sensor)brick.GetSensor((SensorPort)i);
Console.WriteLine(passive.IsConnected);
}
}
Console.WriteLine("Press a key to continue");
}
void DisplayConnectionDisconnection()
{
while (!Console.KeyAvailable)
{
Console.Clear();
Console.CursorTop = 0;
for (int i = 0; i < 4; i++)
{
Console.CursorLeft = 0;
SensorType sensor = brick.GetSensorType((SensorPort)i);
Console.Write($"Port: {i} {(Brick.IsMotor(sensor) ? "Sensor" : "Motor")} type: {sensor} Connected: ");
if (sensor != SensorType.None)
{
if (Brick.IsMotor(sensor))
{
if (Brick.IsActiveSensor(sensor))
{
var motor = (ActiveMotor)brick.GetMotor((SensorPort)i);
Console.WriteLine($"{motor.IsConnected}");
}
else
{
var motor = (PassiveMotor)brick.GetMotor((SensorPort)i);
Console.WriteLine(motor.IsConnected);
}
}
else
{
if (Brick.IsActiveSensor(sensor))
{
var motor = (ActiveSensor)brick.GetSensor((SensorPort)i);
Console.WriteLine(motor.IsConnected);
}
else
{
var motor = (Sensor)brick.GetSensor((SensorPort)i);
Console.WriteLine(motor.IsConnected);
}
}
}
}
Thread.Sleep(100);
}
Console.ReadKey();
}
void MoveMotorsAndBackToPosition()
{
Console.Clear();
Console.WriteLine("Press a key to continue");
brick.WaitForSensorToConnect(SensorPort.PortA);
brick.WaitForSensorToConnect(SensorPort.PortD);
var active = (ActiveMotor)brick.GetMotor(SensorPort.PortA);
var active2 = (ActiveMotor)brick.GetMotor(SensorPort.PortD);
active.Start(50);
active2.Start(50);
// Make sure you have an active motor plug in the port A and D
while (!Console.KeyAvailable)
{
Console.CursorTop = 1;
Console.CursorLeft = 0;
Console.WriteLine($"Absolute: {active.AbsolutePosition} ");
Console.WriteLine($"Position: {active.Position} ");
Console.WriteLine($"Speed: {active.Speed} ");
Console.WriteLine();
Console.WriteLine($"Absolute: {active2.AbsolutePosition} ");
Console.WriteLine($"Position: {active2.Position} ");
Console.WriteLine($"Speed: {active2.Speed} ");
}
active.Stop();
active2.Stop();
Console.ReadKey();
Console.Clear();
Console.WriteLine("Driving back both motors to position 0, one after the other, both blocking");
Console.WriteLine("Press a key to continue");
active.TargetSpeed = 100;
active2.TargetSpeed = 100;
active.MoveToPosition(0, true);
active2.MoveToPosition(0, true);
}
void DriveMotors()
{
// Make sure you have an active motor on port A
brick.WaitForSensorToConnect(SensorPort.PortA);
var active = (ActiveMotor)brick.GetMotor(SensorPort.PortA);
active.TargetSpeed = 70;
Console.WriteLine("Moving motor to position 0");
active.MoveToPosition(0, true);
Console.WriteLine("Moving motor to position 3600 (10 turns)");
active.MoveToPosition(3600, true);
Console.WriteLine("Moving motor to position -3600 (so 20 turns the other way");
active.MoveToPosition(-3600, true);
Console.WriteLine("Moving motor to absolute position 0, should rotate by 90°");
active.MoveToAbsolutePosition(0, PositionWay.Shortest, true);
Console.WriteLine("Moving motor to position 90");
active.MoveToAbsolutePosition(90, PositionWay.Shortest, true);
Console.WriteLine("Moving motor to position 179");
active.MoveToAbsolutePosition(179, PositionWay.Shortest, true);
Console.WriteLine("Moving motor to position -180");
active.MoveToAbsolutePosition(-180, PositionWay.Shortest, true);
}
void ReadColorDistance()
{
brick.WaitForSensorToConnect(SensorPort.PortC);
var colorSensor = (ColorAndDistanceSensor)brick.GetActiveSensor(SensorPort.PortC);
while (!Console.KeyAvailable)
{
var colorRead = colorSensor.GetColor();
Console.WriteLine($"Color: {colorRead}");
var relected = colorSensor.GetReflectedLight();
Console.WriteLine($"Reflected: {relected}");
var ambiant = colorSensor.GetAmbiantLight();
Console.WriteLine($"Ambiant: {ambiant}");
var distance = colorSensor.GetDistance();
Console.WriteLine($"Distance: {distance}");
var counter = colorSensor.GetCounter();
Console.WriteLine($"Counter: {counter}");
Thread.Sleep(200);
}
}
void RunTrainMotor()
{
brick.WaitForSensorToConnect(SensorPort.PortB);
var train = (PassiveMotor)brick.GetMotor(SensorPort.PortB);
Console.WriteLine("This will run the motor for 20 secondes incrementing the PWM");
train.SetPowerLimit(1.0);
train.Start();
for (int i = 0; i < 100; i++)
{
train.SetSpeed(i);
Thread.Sleep(250);
}
Console.WriteLine("Stop the train for 2 seconds");
train.Stop();
Thread.Sleep(2000);
Console.WriteLine("Full speed backward for 2 seconds");
train.Start(-100);
Thread.Sleep(2000);
Console.WriteLine("Full speed forward for 2 seconds");
train.Start(100);
Thread.Sleep(2000);
Console.WriteLine("Stop the train");
train.Stop();
}
void SimpleButton()
{
brick.WaitForSensorToConnect(SensorPort.PortB);
var button = (ButtonSensor)brick.GetSensor(SensorPort.PortB);
}
void MotorPropertyEventExample()
{
Console.WriteLine("Move motor on Port A to more than position 100 to stop this test.");
brick.WaitForSensorToConnect(SensorPort.PortA);
var active = (ActiveMotor)brick.GetMotor(SensorPort.PortA);
continueToRun = true;
active.PropertyChanged += MotorPropertyEvent;
while (continueToRun)
{
Thread.Sleep(50);
}
active.PropertyChanged -= MotorPropertyEvent;
Console.WriteLine($"Current position: {active.Position}, eventing stopped.");
}
void MotorPropertyEvent(object? sender, PropertyChangedEventArgs e)
{
Console.WriteLine($"Property changed: {e.PropertyName}");
if (e.PropertyName == nameof(ActiveMotor.Position))
{
if (((ActiveMotor)brick.GetMotor(SensorPort.PortA)).Position > 100)
{
continueToRun = false;
}
}
}
void DisplayMatrix3x3()
{
brick.WaitForSensorToConnect(SensorPort.PortA);
var matrix = (ColorLightMatrix)brick.GetSensor(SensorPort.PortA);
for (byte i = 0; i < 10; i++)
{
// Will light every led one after the other like a progress bar
matrix.DisplayProgressBar(i);
Thread.Sleep(1000);
}
for (byte i = 0; i < 11; i++)
{
// Will display the matrix with the same color and go through all of them
matrix.DisplayColor((LedColor)i);
Thread.Sleep(1000);
}
Span<byte> brg = stackalloc byte[9] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Span<LedColor> col = stackalloc LedColor[9]
{
LedColor.White,
LedColor.White,
LedColor.White,
LedColor.White,
LedColor.White,
LedColor.White,
LedColor.White,
LedColor.White,
LedColor.White
};
// Shades of grey
matrix.DisplayColorPerPixel(brg, col);
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Reflection;
using Iot.Device.BuildHat.Models;
using Xunit;
namespace Iot.Device.BuildHat.Tests
{
public class BrickTests
{
[Theory]
[InlineData(0, PositionWay.Clockwise, 0, 0, 1)]
[InlineData(90, PositionWay.Clockwise, 0, 0, 0.25)]
[InlineData(-90, PositionWay.Clockwise, 0, 0, 0.75)]
[InlineData(0, PositionWay.AntiClockwise, 0, 0, -1)]
[InlineData(90, PositionWay.AntiClockwise, 0, 0, -0.75)]
[InlineData(-90, PositionWay.AntiClockwise, 0, 0, -0.25)]
[InlineData(0, PositionWay.Shortest, 0, 0, 0)]
[InlineData(90, PositionWay.Shortest, 0, 0, 0.25)]
[InlineData(-90, PositionWay.Shortest, 0, 0, -0.25)]
public void ToAbsolutePositionTest(int targetPosition, PositionWay way, int actualPosition, int actualAbsolutePosition, double expectedNewPosition)
{
Brick brick = GetBrick();
var brickType = typeof(Brick);
var toAbsolutePosition = brickType.GetMethod("ToAbsolutePosition", BindingFlags.NonPublic | BindingFlags.Instance);
var newPosition = toAbsolutePosition?.Invoke(brick, new object[] { targetPosition, way, actualPosition, actualAbsolutePosition });
Assert.Equal(expectedNewPosition, newPosition);
}
private Brick GetBrick()
{
var brickType = typeof(Brick);
var brick = brickType.Assembly.CreateInstance(brickType.FullName!, false, BindingFlags.Instance | BindingFlags.NonPublic, null, null, null, null);
return (Brick)brick!;
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\BuildHat.csproj" />
</ItemGroup>
</Project>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册