diff --git a/examples/SharpBrick.PoweredUp.Examples/ExampleMoveHubColors.cs b/examples/SharpBrick.PoweredUp.Examples/ExampleMoveHubColors.cs new file mode 100644 index 0000000..5a7f9ee --- /dev/null +++ b/examples/SharpBrick.PoweredUp.Examples/ExampleMoveHubColors.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading.Tasks; +using SharpBrick.PoweredUp; + +namespace Example +{ + public class ExampleMoveHubColors : BaseExample + { + public override async Task ExecuteAsync() + { + using (var moveHub = Host.FindByType()) + { + await moveHub.RgbLight.SetRgbColorsAsync(0x00, 0xff, 0x00); + + await Task.Delay(2000); + + await moveHub.RgbLight.SetRgbColorsAsync(0xff, 0x00, 0x00); + + await Task.Delay(2000); + + await moveHub.SwitchOffAsync(); + } + } + } +} \ No newline at end of file diff --git a/examples/SharpBrick.PoweredUp.Examples/ExampleMoveHubExternalMediumLinearMotorControl.cs b/examples/SharpBrick.PoweredUp.Examples/ExampleMoveHubExternalMediumLinearMotorControl.cs new file mode 100644 index 0000000..3b6bf25 --- /dev/null +++ b/examples/SharpBrick.PoweredUp.Examples/ExampleMoveHubExternalMediumLinearMotorControl.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using SharpBrick.PoweredUp; + +namespace Example +{ + public class ExampleMoveHubExternalMediumLinearMotorControl : BaseExample + { + public override async Task ExecuteAsync() + { + using (var moveHub = Host.FindByType()) + { + // This is if you have a linear motor plugged into port D (ie. R2D2) + var externalMotor = moveHub.D.GetDevice(); + + await externalMotor.SetAccelerationTimeAsync(3000); + await externalMotor.SetDecelerationTimeAsync(1000); + await externalMotor.StartSpeedForTimeAsync(2000, 90, 100, SpecialSpeed.Hold, SpeedProfiles.AccelerationProfile | SpeedProfiles.DecelerationProfile); + + await Task.Delay(50000); + + await moveHub.SwitchOffAsync(); + } + } + } +} diff --git a/examples/SharpBrick.PoweredUp.Examples/ExampleMoveHubInternalTachoMotorControl.cs b/examples/SharpBrick.PoweredUp.Examples/ExampleMoveHubInternalTachoMotorControl.cs new file mode 100644 index 0000000..c1c5e1e --- /dev/null +++ b/examples/SharpBrick.PoweredUp.Examples/ExampleMoveHubInternalTachoMotorControl.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using SharpBrick.PoweredUp; + +namespace Example +{ + public class ExampleMoveHubInternalTachoMotorControl : BaseExample + { + public override async Task ExecuteAsync() + { + using (var moveHub = Host.FindByType()) + { + var internalMotor = moveHub.MotorAtAB; + await internalMotor.StartSpeedAsync(50, 100); + await internalMotor.StartSpeedForTimeAsync(2000, 90, 100, SpecialSpeed.Hold, SpeedProfiles.AccelerationProfile | SpeedProfiles.DecelerationProfile); + + await Task.Delay(3000); + await internalMotor.StopByBrakeAsync(); + + var leftMotor = moveHub.LeftMotorAtB; + var rightMotor = moveHub.RightMotorAtA; + await leftMotor.StartSpeedAsync(10, 100); + await leftMotor.StartSpeedForTimeAsync(1000, 10, 100, SpecialSpeed.Hold, SpeedProfiles.AccelerationProfile | SpeedProfiles.DecelerationProfile); + await rightMotor.StartSpeedAsync(90, 100); + await rightMotor.StartSpeedForTimeAsync(1000, 90, 100, SpecialSpeed.Hold, SpeedProfiles.AccelerationProfile | SpeedProfiles.DecelerationProfile); + + await Task.Delay(2000); + + await leftMotor.StopByBrakeAsync(); + await rightMotor.StopByBrakeAsync(); + + await moveHub.SwitchOffAsync(); + } + } + } +} \ No newline at end of file diff --git a/examples/SharpBrick.PoweredUp.Examples/ExampleMoveHubTiltSensor.cs b/examples/SharpBrick.PoweredUp.Examples/ExampleMoveHubTiltSensor.cs new file mode 100644 index 0000000..5c5ded4 --- /dev/null +++ b/examples/SharpBrick.PoweredUp.Examples/ExampleMoveHubTiltSensor.cs @@ -0,0 +1,55 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using SharpBrick.PoweredUp; + +namespace Example +{ + public class ExampleMoveHubTiltSensor : BaseExample + { + public override async Task ExecuteAsync() + { + using (var moveHub = Host.FindByType()) + { + var device = moveHub.TiltSensor; + await device.TiltConfigOrientationAsync(TiltConfigOrientation.Front); + + // Note that you can only have 1 notification running at a time + + await device.SetupNotificationAsync(device.ModeIndexTwoAxisFull, true, 1); + using var twoAxisFullSubscription = device.TwoAxisFullObservable.Subscribe(x => Log.LogWarning($"Two Axis Values - Roll: {x.roll}, Pitch: {x.pitch}")); + + Console.WriteLine("Press any key to continue"); + Console.ReadKey(); + + await device.SetupNotificationAsync(device.ModeIndexTwoAxisState, true, 1); + using var twoAxisStateSubscription = device.TwoAxisStateObservable.Subscribe(x => Log.LogWarning($"Two Axis State: {x}")); + + Console.WriteLine("Press any key to continue"); + Console.ReadKey(); + + await device.SetupNotificationAsync(device.ModeIndexThreeAxisState, true, 1); + using var threeAxisStateSubscription = device.ThreeAxisStateObservable.Subscribe(x => Log.LogWarning($"Three Axis State: {x}")); + + Console.WriteLine("Press any key to continue"); + Console.ReadKey(); + + await device.SetupNotificationAsync(device.ModeIndexThreeAxisFull, true, 1); + using var threeAxisFullSubscription = device.ThreeAxisFullObservable.Subscribe(x => Log.LogWarning($"Three Axis Values - Roll: {x.roll}, Pitch: {x.pitch}, Yaw: {x.yaw}")); + + Console.WriteLine("Press any key to continue"); + Console.ReadKey(); + + // This configures a minimum threshold for an impact to be registered (should be a light tap) and subscribes to the count of impacts + await device.TiltConfigImpactAsync(10, 1270); + await device.SetupNotificationAsync(device.ModeIndexImpacts, true, deltaInterval: 1); + using var impactSubscription = device.ImpactsObservable.Subscribe(x => Log.LogWarning($"Impact Count: {x.SI}")); + + Console.WriteLine("Press any key to continue"); + Console.ReadKey(); + + await moveHub.SwitchOffAsync(); + } + } + } +} \ No newline at end of file diff --git a/examples/SharpBrick.PoweredUp.Examples/Program.cs b/examples/SharpBrick.PoweredUp.Examples/Program.cs index 6bfcff1..f2d1b13 100644 --- a/examples/SharpBrick.PoweredUp.Examples/Program.cs +++ b/examples/SharpBrick.PoweredUp.Examples/Program.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Example; namespace SharpBrick.PoweredUp.Examples { @@ -43,7 +44,12 @@ static async Task Main(string[] args) //example = new Example.ExampleMarioAccelerometer(); //example = new Example.ExampleDuploTrainBase(); //example = new Example.ExampleTechnicColorSensor(); - example = new Example.ExampleTechnicDistanceSensor(); + //example = new Example.ExampleTechnicDistanceSensor(); + //example = new Example.ExampleTechnicMediumHubGestSensor(); + //example = new Example.ExampleMoveHubInternalTachoMotorControl(); + //example = new Example.ExampleMoveHubExternalMediumLinearMotorControl(); + //example = new Example.ExampleMoveHubColors(); + example = new Example.ExampleMoveHubTiltSensor(); // NOTE: Examples are programmed object oriented style. Base class implements methods Configure, DiscoverAsync and ExecuteAsync to be overwriten on demand. await example.InitHostAndDiscoverAsync(enableTrace); diff --git a/src/SharpBrick.PoweredUp/Devices/Current.cs b/src/SharpBrick.PoweredUp/Devices/Current.cs index ee2ad6c..6143bf4 100644 --- a/src/SharpBrick.PoweredUp/Devices/Current.cs +++ b/src/SharpBrick.PoweredUp/Devices/Current.cs @@ -72,6 +72,24 @@ public IEnumerable GetStaticPortInfoMessages(Version softwareVersion, Ve 0B-00-44-3B-01-04-6D-41-00-00-00 08-00-44-3B-01-05-10-00 0A-00-44-3B-01-80-01-01-04-00 +", + (_, _, SystemType.LegoSystem_MoveHub) => @" +0B-00-43-3B-01-02-02-03-00-00-00 +05-00-43-3B-02 +11-00-44-3B-00-00-43-55-52-20-4C-00-00-00-00-00-00 +0E-00-44-3B-00-01-00-00-00-00-00-F0-7F-45 +0E-00-44-3B-00-02-00-00-00-00-00-00-C8-42 +0E-00-44-3B-00-03-00-00-00-00-00-C0-18-45 +0A-00-44-3B-00-04-6D-41-00-00 +08-00-44-3B-00-05-10-00 +0A-00-44-3B-00-80-01-01-04-00 +11-00-44-3B-01-00-43-55-52-20-53-00-00-00-00-00-00 +0E-00-44-3B-01-01-00-00-00-00-00-F0-7F-45 +0E-00-44-3B-01-02-00-00-00-00-00-00-C8-42 +0E-00-44-3B-01-03-00-00-00-00-00-C0-18-45 +0A-00-44-3B-01-04-6D-41-00-00 +08-00-44-3B-01-05-10-00 +0A-00-44-3B-01-80-01-01-04-00 ", _ => throw new NotSupportedException(), }).Trim().Split("\n").Select(s => BytesStringUtil.StringToData(s)); diff --git a/src/SharpBrick.PoweredUp/Devices/DeviceFactory.cs b/src/SharpBrick.PoweredUp/Devices/DeviceFactory.cs index 64674c5..c41c77f 100644 --- a/src/SharpBrick.PoweredUp/Devices/DeviceFactory.cs +++ b/src/SharpBrick.PoweredUp/Devices/DeviceFactory.cs @@ -56,6 +56,8 @@ public Type GetTypeFromDeviceType(DeviceType deviceType) DeviceType.DuploTrainBaseSpeaker => typeof(DuploTrainBaseSpeaker), DeviceType.DuploTrainBaseColorSensor => typeof(DuploTrainBaseColorSensor), DeviceType.DuploTrainBaseSpeedometer => typeof(DuploTrainBaseSpeedometer), + DeviceType.MoveHubInternalMotor => typeof(MoveHubInternalMotor), + DeviceType.MoveHubTiltSensor => typeof(MoveHubTiltSensor), _ => null, }; @@ -88,6 +90,8 @@ public static DeviceType GetDeviceTypeFromType(Type type) nameof(DuploTrainBaseSpeaker) => DeviceType.DuploTrainBaseSpeaker, nameof(DuploTrainBaseColorSensor) => DeviceType.DuploTrainBaseColorSensor, nameof(DuploTrainBaseSpeedometer) => DeviceType.DuploTrainBaseSpeedometer, + nameof(MoveHubInternalMotor) => DeviceType.MoveHubInternalMotor, + nameof(MoveHubTiltSensor) => DeviceType.MoveHubTiltSensor, _ => DeviceType.Unknown, }; } diff --git a/src/SharpBrick.PoweredUp/Devices/MoveHubInternalMotor.cs b/src/SharpBrick.PoweredUp/Devices/MoveHubInternalMotor.cs new file mode 100644 index 0000000..bad6fdc --- /dev/null +++ b/src/SharpBrick.PoweredUp/Devices/MoveHubInternalMotor.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SharpBrick.PoweredUp.Protocol; +using SharpBrick.PoweredUp.Utils; + +namespace SharpBrick.PoweredUp +{ + public class MoveHubInternalMotor : TachoMotor, IPoweredUpDevice + { + public MoveHubInternalMotor() + : base() + { } + public MoveHubInternalMotor(ILegoWirelessProtocol protocol, byte hubId, byte portId) + : base(protocol, hubId, portId) + { } + + public IEnumerable GetStaticPortInfoMessages(Version softwareVersion, Version hardwareVersion, SystemType systemType) + => @" +0B-00-43-00-01-0F-03-06-00-07-00 +07-00-43-00-02-06-00 +11-00-44-00-00-00-50-4F-57-45-52-00-00-00-00-00-00 +0E-00-44-00-00-01-00-00-C8-C2-00-00-C8-42 +0E-00-44-00-00-02-00-00-C8-C2-00-00-C8-42 +0E-00-44-00-00-03-00-00-C8-C2-00-00-C8-42 +0A-00-44-00-00-04-50-43-54-00 +08-00-44-00-00-05-00-10 +0A-00-44-00-00-80-01-00-01-00 +11-00-44-00-01-00-53-50-45-45-44-00-00-00-00-00-00 +0E-00-44-00-01-01-00-00-C8-C2-00-00-C8-42 +0E-00-44-00-01-02-00-00-C8-C2-00-00-C8-42 +0E-00-44-00-01-03-00-00-C8-C2-00-00-C8-42 +0A-00-44-00-01-04-50-43-54-00 +08-00-44-00-01-05-10-10 +0A-00-44-00-01-80-01-00-04-00 +11-00-44-00-02-00-50-4F-53-00-00-00-00-00-00-00-00 +0E-00-44-00-02-01-00-00-B4-C3-00-00-B4-43 +0E-00-44-00-02-02-00-00-C8-C2-00-00-C8-42 +0E-00-44-00-02-03-00-00-B4-C3-00-00-B4-43 +0A-00-44-00-02-04-44-45-47-00 +08-00-44-00-02-05-08-08 +0A-00-44-00-02-80-01-02-04-00 +".Trim().Split("\n").Select(s => BytesStringUtil.StringToData(s)); + } +} \ No newline at end of file diff --git a/src/SharpBrick.PoweredUp/Devices/MoveHubTiltSensor.cs b/src/SharpBrick.PoweredUp/Devices/MoveHubTiltSensor.cs new file mode 100644 index 0000000..0a69bf8 --- /dev/null +++ b/src/SharpBrick.PoweredUp/Devices/MoveHubTiltSensor.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using SharpBrick.PoweredUp.Protocol; +using SharpBrick.PoweredUp.Protocol.Knowledge; +using SharpBrick.PoweredUp.Protocol.Messages; +using SharpBrick.PoweredUp.Utils; + +namespace SharpBrick.PoweredUp +{ + public class MoveHubTiltSensor : Device, IPoweredUpDevice + { + protected MultiValueMode _twoAxisFullMode; + protected SingleValueMode _twoAxisStateMode; + protected SingleValueMode _threeAxisStateMode; + protected SingleValueMode _impactsMode; + protected MultiValueMode _threeAxisFullMode; + + /// + /// Two axis full values + /// + public byte ModeIndexTwoAxisFull { get; protected set; } = 0; + /// + /// Two Axis simple state of orientation + /// + public byte ModeIndexTwoAxisState { get; protected set; } = 1; + /// + /// Three Axis simple state of orientation which provides more states than + /// + public byte ModeIndexThreeAxisState { get; protected set; } = 2; + /// + /// Count of impacts + /// + public byte ModeIndexImpacts { get; protected set; } = 3; + /// + /// Three axis full values + /// + public byte ModeIndexThreeAxisFull { get; protected set; } = 4; + public byte ModeIndexOrientationConfig { get; protected set; } = 5; + public byte ModeIndexImpactsConfig { get; protected set; } = 6; + public byte ModeIndexCalibration { get; protected set; } = 7; + + public (sbyte roll, sbyte pitch) TwoAxisFull => (_twoAxisFullMode.SI[0], _twoAxisFullMode.SI[1]); + public MoveHubTiltSimpleOrientation TwoAxisState => (MoveHubTiltSimpleOrientation)_twoAxisStateMode.SI; + public MoveHubTiltOrientation ThreeAxisState => (MoveHubTiltOrientation)_threeAxisStateMode.SI; + public int Impacts => _impactsMode.SI; + public (sbyte roll, sbyte pitch, sbyte yaw) ThreeAxisFull => (_threeAxisFullMode.SI[0], _threeAxisFullMode.SI[1], _threeAxisFullMode.SI[2]); + + public IObservable<(sbyte roll, sbyte pitch)> TwoAxisFullObservable => _twoAxisFullMode.Observable.Select(v => (v.SI[0], v.SI[1])); + public IObservable TwoAxisStateObservable => _twoAxisStateMode.Observable.Select(x => (MoveHubTiltSimpleOrientation)x.SI); + public IObservable ThreeAxisStateObservable => _threeAxisStateMode.Observable.Select(x => (MoveHubTiltOrientation)x.SI); + public IObservable> ImpactsObservable => _impactsMode.Observable; + public IObservable<(sbyte roll, sbyte pitch, sbyte yaw)> ThreeAxisFullObservable => _threeAxisFullMode.Observable.Select(v => (v.SI[0], v.SI[1], v.SI[2])); + + public MoveHubTiltSensor() + { } + + public MoveHubTiltSensor(ILegoWirelessProtocol protocol, byte hubId, byte portId) + : base(protocol, hubId, portId) + { + _twoAxisFullMode = MultiValueMode(ModeIndexTwoAxisFull); + _twoAxisStateMode = SingleValueMode(ModeIndexTwoAxisState); + _threeAxisStateMode = SingleValueMode(ModeIndexThreeAxisState); + _impactsMode = SingleValueMode(ModeIndexImpacts); + _threeAxisFullMode = MultiValueMode(ModeIndexThreeAxisFull); + + ObserveForPropertyChanged(_twoAxisFullMode.Observable, nameof(TwoAxisFull)); + ObserveForPropertyChanged(_twoAxisStateMode.Observable, nameof(TwoAxisState)); + ObserveForPropertyChanged(_threeAxisStateMode.Observable, nameof(ThreeAxisState)); + ObserveForPropertyChanged(_impactsMode.Observable, nameof(Impacts)); + ObserveForPropertyChanged(_threeAxisFullMode.Observable, nameof(ThreeAxisFull)); + } + + public void ExtendPortMode(PortModeInfo modeInfo) + { + // Percentage is disabled for three axis full (ACCEL) since in some cases the hub + // can report a larger than expected value which makes the percentage go beyond an sbyte min/max values and generate an exception + // TODO: This can be removed when #126 is implemented + if (modeInfo.ModeIndex == ModeIndexThreeAxisFull) + { + modeInfo.DisablePercentage = true; + } + } + + /// + /// Set the Tilt into ImpactCount mode and change (preset) the value to the given PresetValue. + /// + /// Value between 0 and int.MaxValue + /// + public async Task TiltImpactPresetAsync(int presetValue) + { + AssertIsConnected(); + + if (presetValue < 0) + { + throw new ArgumentOutOfRangeException("PresetValue has to be between 0 and int.MaxValue", nameof(presetValue)); + } + + var response = await _protocol.SendPortOutputCommandAsync(new PortOutputCommandTiltImpactPresetMessage() + { + HubId = _hubId, + PortId = _portId, + ModeIndex = ModeIndexImpacts, + StartupInformation = PortOutputCommandStartupInformation.ExecuteImmediately, + CompletionInformation = PortOutputCommandCompletionInformation.CommandFeedback, + PresetValue = presetValue, + }); + + return response; + } + + /// + /// Setup Tilt ImpactThreshold and BumpHoldoff + /// + /// Impact Threshold between 0 and 127. + /// Bump Holdoff between 10ms and 1270ms. + /// + public async Task TiltConfigImpactAsync(sbyte impactThreshold, short bumpHoldoffInMs) + { + AssertIsConnected(); + + if (impactThreshold < 0) + { + throw new ArgumentOutOfRangeException("Impact Threshold has to be between 0 and 127", nameof(impactThreshold)); + } + + if (bumpHoldoffInMs < 10 || bumpHoldoffInMs > 1270) + { + throw new ArgumentOutOfRangeException("Hold off has to be between 10 and 1270 ms (in steps of 10ms)", nameof(bumpHoldoffInMs)); + } + + var response = await _protocol.SendPortOutputCommandAsync(new PortOutputCommandTiltConfigImpactMessage() + { + HubId = _hubId, + PortId = _portId, + ModeIndex = ModeIndexImpactsConfig, + StartupInformation = PortOutputCommandStartupInformation.ExecuteImmediately, + CompletionInformation = PortOutputCommandCompletionInformation.CommandFeedback, + ImpactThreshold = impactThreshold, + BumpHoldoff = (sbyte)((float)bumpHoldoffInMs / 10), + }); + + return response; + } + + /// + /// Set the Tilt into Orientation mode and set the Orientation value to Orientation. + /// + /// orientation of the tilt for 0 values. + /// + public async Task TiltConfigOrientationAsync(TiltConfigOrientation orientation) + { + AssertIsConnected(); + + var response = await _protocol.SendPortOutputCommandAsync(new PortOutputCommandTiltConfigOrientationMessage() + { + HubId = _hubId, + PortId = _portId, + ModeIndex = ModeIndexOrientationConfig, + StartupInformation = PortOutputCommandStartupInformation.ExecuteImmediately, + CompletionInformation = PortOutputCommandCompletionInformation.CommandFeedback, + Orientation = orientation + }); + + return response; + } + + public IEnumerable GetStaticPortInfoMessages(Version softwareVersion, Version hardwareVersion, SystemType systemType) + => +@" +0B-00-43-3A-01-06-08-FF-00-00-00 +07-00-43-3A-02-1F-00 +11-00-44-3A-00-00-41-4E-47-4C-45-00-00-00-00-00-00 +0E-00-44-3A-00-01-00-00-B4-C2-00-00-B4-42 +0E-00-44-3A-00-02-00-00-C8-C2-00-00-C8-42 +0E-00-44-3A-00-03-00-00-B4-C2-00-00-B4-42 +0A-00-44-3A-00-04-44-45-47-00 +08-00-44-3A-00-05-50-00 +0A-00-44-3A-00-80-02-00-03-00 +11-00-44-3A-01-00-54-49-4C-54-00-00-00-00-00-00-00 +0E-00-44-3A-01-01-00-00-00-00-00-00-20-41 +0E-00-44-3A-01-02-00-00-00-00-00-00-C8-42 +0E-00-44-3A-01-03-00-00-00-00-00-00-20-41 +0A-00-44-3A-01-04-44-49-52-00 +08-00-44-3A-01-05-44-00 +0A-00-44-3A-01-80-01-00-01-00 +11-00-44-3A-02-00-4F-52-49-4E-54-00-00-00-00-00-00 +0E-00-44-3A-02-01-00-00-00-00-00-00-A0-40 +0E-00-44-3A-02-02-00-00-00-00-00-00-C8-42 +0E-00-44-3A-02-03-00-00-00-00-00-00-A0-40 +0A-00-44-3A-02-04-44-49-52-00 +08-00-44-3A-02-05-10-00 +0A-00-44-3A-02-80-01-00-01-00 +11-00-44-3A-03-00-49-4D-50-43-54-00-00-00-00-00-00 +0E-00-44-3A-03-01-00-00-00-00-00-00-C8-42 +0E-00-44-3A-03-02-00-00-00-00-00-00-C8-42 +0E-00-44-3A-03-03-00-00-00-00-00-00-C8-42 +0A-00-44-3A-03-04-49-4D-50-00 +08-00-44-3A-03-05-08-00 +0A-00-44-3A-03-80-01-02-04-00 +11-00-44-3A-04-00-41-43-43-45-4C-00-00-00-00-00-00 +0E-00-44-3A-04-01-00-00-82-C2-00-00-82-42 +0E-00-44-3A-04-02-00-00-C8-C2-00-00-C8-42 +0E-00-44-3A-04-03-00-00-82-C2-00-00-82-42 +0A-00-44-3A-04-04-41-43-43-00 +08-00-44-3A-04-05-10-00 +0A-00-44-3A-04-80-03-00-03-00 +11-00-44-3A-05-00-4F-52-5F-43-46-00-00-00-00-00-00 +0E-00-44-3A-05-01-00-00-00-00-00-00-C0-40 +0E-00-44-3A-05-02-00-00-00-00-00-00-C8-42 +0E-00-44-3A-05-03-00-00-00-00-00-00-C0-40 +0A-00-44-3A-05-04-53-49-44-00 +08-00-44-3A-05-05-10-00 +0A-00-44-3A-05-80-01-00-01-00 +11-00-44-3A-06-00-49-4D-5F-43-46-00-00-00-00-00-00 +0E-00-44-3A-06-01-00-00-00-00-00-00-7F-43 +0E-00-44-3A-06-02-00-00-00-00-00-00-C8-42 +0E-00-44-3A-06-03-00-00-00-00-00-00-7F-43 +0A-00-44-3A-06-04-53-45-4E-00 +08-00-44-3A-06-05-10-00 +0A-00-44-3A-06-80-02-00-03-00 +11-00-44-3A-07-00-43-41-4C-49-42-00-00-00-00-00-00 +0E-00-44-3A-07-01-00-00-00-00-00-00-7F-43 +0E-00-44-3A-07-02-00-00-00-00-00-00-C8-42 +0E-00-44-3A-07-03-00-00-00-00-00-00-7F-43 +0A-00-44-3A-07-04-43-41-4C-00 +08-00-44-3A-07-05-10-00 +0A-00-44-3A-07-80-03-00-03-00 +".Trim().Split("\n").Select(s => BytesStringUtil.StringToData(s)); + } +} \ No newline at end of file diff --git a/src/SharpBrick.PoweredUp/Devices/RgbLight.cs b/src/SharpBrick.PoweredUp/Devices/RgbLight.cs index 9d52dff..8542186 100644 --- a/src/SharpBrick.PoweredUp/Devices/RgbLight.cs +++ b/src/SharpBrick.PoweredUp/Devices/RgbLight.cs @@ -144,6 +144,24 @@ public IEnumerable GetStaticPortInfoMessages(Version softwareVersion, Ve 0A-00-44-11-01-04-00-00-00-00 08-00-44-11-01-05-00-10 0A-00-44-11-01-80-03-00-03-00 +", + (_, _, SystemType.LegoSystem_MoveHub) => @" +0B-00-43-32-01-01-02-00-00-03-00 +05-00-43-32-02 +11-00-44-32-00-00-43-4F-4C-20-4F-00-00-00-00-00-00 +0E-00-44-32-00-01-00-00-00-00-00-00-20-41 +0E-00-44-32-00-02-00-00-00-00-00-00-C8-42 +0E-00-44-32-00-03-00-00-00-00-00-00-20-41 +0A-00-44-32-00-04-00-00-00-00 +08-00-44-32-00-05-00-44 +0A-00-44-32-00-80-01-00-01-00 +11-00-44-32-01-00-52-47-42-20-4F-00-00-00-00-00-00 +0E-00-44-32-01-01-00-00-00-00-00-00-7F-43 +0E-00-44-32-01-02-00-00-00-00-00-00-C8-42 +0E-00-44-32-01-03-00-00-00-00-00-00-7F-43 +0A-00-44-32-01-04-00-00-00-00 +08-00-44-32-01-05-00-10 +0A-00-44-32-01-80-03-00-03-00 ", _ => throw new NotSupportedException(), }).Trim().Split("\n").Select(s => BytesStringUtil.StringToData(s)); diff --git a/src/SharpBrick.PoweredUp/Devices/Voltage.cs b/src/SharpBrick.PoweredUp/Devices/Voltage.cs index 1fd4fda..1410a2c 100644 --- a/src/SharpBrick.PoweredUp/Devices/Voltage.cs +++ b/src/SharpBrick.PoweredUp/Devices/Voltage.cs @@ -126,8 +126,25 @@ public IEnumerable GetStaticPortInfoMessages(Version softwareVersion, Ve 08-00-44-14-01-05-10-00 0A-00-44-14-01-80-01-01-04-00 ", - - _ => throw new NotSupportedException(), + (_, _, SystemType.LegoSystem_MoveHub) => @" +0B-00-43-3C-01-02-02-03-00-00-00 +05-00-43-3C-02 +11-00-44-3C-00-00-56-4C-54-20-4C-00-00-00-00-00-00 +0E-00-44-3C-00-01-00-00-00-00-00-50-73-45 +0E-00-44-3C-00-02-00-00-00-00-00-00-C8-42 +0E-00-44-3C-00-03-00-00-00-00-00-00-16-46 +0A-00-44-3C-00-04-6D-56-00-00 +08-00-44-3C-00-05-10-00 +0A-00-44-3C-00-80-01-01-04-00 +11-00-44-3C-01-00-56-4C-54-20-53-00-00-00-00-00-00 +0E-00-44-3C-01-01-00-00-00-00-00-50-73-45 +0E-00-44-3C-01-02-00-00-00-00-00-00-C8-42 +0E-00-44-3C-01-03-00-00-00-00-00-00-16-46 +0A-00-44-3C-01-04-6D-56-00-00 +08-00-44-3C-01-05-10-00 +0A-00-44-3C-01-80-01-01-04-00 +", + _ => throw new NotImplementedException(), }).Trim().Split("\n").Select(s => BytesStringUtil.StringToData(s)); } } \ No newline at end of file diff --git a/src/SharpBrick.PoweredUp/Enums/DeviceType.cs b/src/SharpBrick.PoweredUp/Enums/DeviceType.cs index 8b6c994..71ec8cb 100644 --- a/src/SharpBrick.PoweredUp/Enums/DeviceType.cs +++ b/src/SharpBrick.PoweredUp/Enums/DeviceType.cs @@ -16,8 +16,8 @@ public enum DeviceType : ushort MotionSensor = 0x0023, // MOTION_SENSOR VisionSensor = 0x0025, // COLOR_DISTANCE_SENSOR MediumLinearMotor = 0x0026, // MEDIUM_LINEAR_MOTOR - InternalMotorWithTacho = 0x0027, // MOVE_HUB_MEDIUM_LINEAR_MOTOR - InternalTilt = 0x0028, // MOVE_HUB_TILT_SENSOR + MoveHubInternalMotor = 0x0027, // MOVE_HUB_MEDIUM_LINEAR_MOTOR + MoveHubTiltSensor = 0x0028, // MOVE_HUB_TILT_SENSOR DuploTrainBaseMotor = 0x0029, // UNSPECED, DUPLO_TRAIN_BASE_MOTOR DuploTrainBaseSpeaker = 0x002A, // UNSPECED, DUPLO_TRAIN_BASE_SPEAKER diff --git a/src/SharpBrick.PoweredUp/Enums/MoveHubTiltFactoryOrientation.cs b/src/SharpBrick.PoweredUp/Enums/MoveHubTiltFactoryOrientation.cs new file mode 100644 index 0000000..c5f413a --- /dev/null +++ b/src/SharpBrick.PoweredUp/Enums/MoveHubTiltFactoryOrientation.cs @@ -0,0 +1,15 @@ +namespace SharpBrick.PoweredUp +{ + public enum MoveHubTiltFactoryOrientation : byte + { + /// + /// Laying flat XY + /// + LayingFlat = 0x01, + + /// + /// Standing (Z) in long direction + /// + Standing = 0x02, + } +} \ No newline at end of file diff --git a/src/SharpBrick.PoweredUp/Enums/MoveHubTiltOrientation.cs b/src/SharpBrick.PoweredUp/Enums/MoveHubTiltOrientation.cs new file mode 100644 index 0000000..75f36fc --- /dev/null +++ b/src/SharpBrick.PoweredUp/Enums/MoveHubTiltOrientation.cs @@ -0,0 +1,15 @@ +namespace SharpBrick.PoweredUp +{ + /// + /// Simple tilt orientation state based on 3-axis providing more possible states than + /// + public enum MoveHubTiltOrientation : sbyte + { + Bottom = 0x00, + Front = 0x01, + Back = 0x02, + Left = 0x03, + Right = 0x04, + Top = 0x05, + } +} \ No newline at end of file diff --git a/src/SharpBrick.PoweredUp/Enums/MoveHubTiltSimpleOrientation.cs b/src/SharpBrick.PoweredUp/Enums/MoveHubTiltSimpleOrientation.cs new file mode 100644 index 0000000..a2f0bd1 --- /dev/null +++ b/src/SharpBrick.PoweredUp/Enums/MoveHubTiltSimpleOrientation.cs @@ -0,0 +1,14 @@ +namespace SharpBrick.PoweredUp +{ + /// + /// Simple tilt orientation state based on 2-axis only + /// + public enum MoveHubTiltSimpleOrientation : sbyte + { + Horzontial = 0x00, + Down = 0x03, + Left = 0x05, + Right = 0x07, + Up = 0x09 + } +} \ No newline at end of file diff --git a/src/SharpBrick.PoweredUp/Enums/SystemType.cs b/src/SharpBrick.PoweredUp/Enums/SystemType.cs index 3f89243..548d2f9 100644 --- a/src/SharpBrick.PoweredUp/Enums/SystemType.cs +++ b/src/SharpBrick.PoweredUp/Enums/SystemType.cs @@ -7,7 +7,7 @@ public enum SystemType : byte LegoDuplo_DuploTrain = 0b001_00000, - LegoSystem_BoostHub = 0b010_00000, + LegoSystem_MoveHub = 0b010_00000, LegoSystem_TwoPortHub = 0b010_00001, LegoSystem_TwoPortHandset = 0b010_00010, LegoSystem_Mario = 0b010_00011, // UNSPECED, https://github.com/bricklife/LEGO-Mario-Reveng, 0x43, diff --git a/src/SharpBrick.PoweredUp/Hubs/Hub.cs b/src/SharpBrick.PoweredUp/Hubs/Hub.cs index d89c456..32b60bb 100644 --- a/src/SharpBrick.PoweredUp/Hubs/Hub.cs +++ b/src/SharpBrick.PoweredUp/Hubs/Hub.cs @@ -16,6 +16,7 @@ public abstract partial class Hub : IDisposable private readonly ILogger _logger; private readonly IDeviceFactory _deviceFactory; private readonly SystemType _knownSystemType; + private bool _connected = false; public ILegoWirelessProtocol Protocol { get; private set; } public byte HubId { get; private set; } @@ -74,6 +75,7 @@ public async Task ConnectAsync() //TODO HubId = hubId; _logger?.LogDebug("Finished Querying Hub Properties"); + _connected = true; } private void SetupOnHubChange() @@ -99,6 +101,16 @@ private void OnHubChange(LegoWirelessMessage message) { OnHubPropertyMessage(hubProperty); } + else if (message is HubAttachedIOForAttachedVirtualDeviceMessage attachedVirtualDeviceMessage) + { + if (!_connected) + { + // Virtual IO messages are only handled from the hubs during connect phase + // This is to allow notifications from hubs which have virtual ports built in (ie. MoveHub) when connecting + // and ensure devices are only attached once when virtual ports are manually created after connection + OnHubAttachedVirtualIOMessage(attachedVirtualDeviceMessage); + } + } else if (message is HubAttachedIOMessage hubAttachedIO) { OnHubAttachedIOMessage(hubAttachedIO); diff --git a/src/SharpBrick.PoweredUp/Hubs/HubFactory.cs b/src/SharpBrick.PoweredUp/Hubs/HubFactory.cs index a4ea941..1147592 100644 --- a/src/SharpBrick.PoweredUp/Hubs/HubFactory.cs +++ b/src/SharpBrick.PoweredUp/Hubs/HubFactory.cs @@ -42,6 +42,7 @@ public static Type GetTypeFromSystemType(SystemType systemType) SystemType.LegoTechnic_MediumHub => typeof(TechnicMediumHub), SystemType.LegoSystem_Mario => typeof(MarioHub), SystemType.LegoDuplo_DuploTrain => typeof(DuploTrainBaseHub), + SystemType.LegoSystem_MoveHub => typeof(MoveHub), _ => throw new NotSupportedException(), }; @@ -53,6 +54,7 @@ public static SystemType GetSystemTypeFromType(Type type) nameof(TechnicMediumHub) => SystemType.LegoTechnic_MediumHub, nameof(MarioHub) => SystemType.LegoSystem_Mario, nameof(DuploTrainBaseHub) => SystemType.LegoDuplo_DuploTrain, + nameof(MoveHub) => SystemType.LegoSystem_MoveHub, _ => throw new NotSupportedException(), }; } diff --git a/src/SharpBrick.PoweredUp/Hubs/Hub_Ports.cs b/src/SharpBrick.PoweredUp/Hubs/Hub_Ports.cs index d4843ad..8790e10 100644 --- a/src/SharpBrick.PoweredUp/Hubs/Hub_Ports.cs +++ b/src/SharpBrick.PoweredUp/Hubs/Hub_Ports.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using SharpBrick.PoweredUp.Protocol; using SharpBrick.PoweredUp.Protocol.Messages; +using Microsoft.Extensions.Logging; namespace SharpBrick.PoweredUp { @@ -111,13 +112,22 @@ private void OnHubAttachedIOMessage(HubAttachedIOMessage hubAttachedIO) { case HubAttachedIOForAttachedDeviceMessage attachedDeviceMessage: port = Port(attachedDeviceMessage.PortId); - + if (port == null) + { + _logger?.LogInformation($"Hub sent notification of attached device with port id '{hubAttachedIO.PortId}' but hub type '{GetType().Name}' is not configured for this port"); + return; + } var device = _deviceFactory.CreateConnected(attachedDeviceMessage.IOTypeId, Protocol, attachedDeviceMessage.HubId, attachedDeviceMessage.PortId); port.AttachDevice(device, attachedDeviceMessage.IOTypeId); break; case HubAttachedIOForDetachedDeviceMessage detachedDeviceMessage: port = Port(detachedDeviceMessage.PortId); + if (port == null) + { + _logger?.LogInformation($"Hub sent notification of detached device with port id '{hubAttachedIO.PortId}' but hub type '{GetType().Name}' is not configured for this port"); + return; + } port.DetachDevice(); @@ -127,10 +137,8 @@ private void OnHubAttachedIOMessage(HubAttachedIOMessage hubAttachedIO) } break; - // case HubAttachedIOForAttachedVirtualDeviceMessage attachedVirtualDeviceMessage: - // OnHubAttachedVirtualIOMessage(attachedVirtualDeviceMessage); - // break; + // Note - HubAttachedIOForAttachedVirtualDeviceMessage is handled directly in OnHubChange not here } } diff --git a/src/SharpBrick.PoweredUp/Hubs/MoveHub.cs b/src/SharpBrick.PoweredUp/Hubs/MoveHub.cs new file mode 100644 index 0000000..f45b568 --- /dev/null +++ b/src/SharpBrick.PoweredUp/Hubs/MoveHub.cs @@ -0,0 +1,66 @@ +using System; +using Microsoft.Extensions.Logging; +using SharpBrick.PoweredUp.Devices; +using SharpBrick.PoweredUp.Protocol; + +namespace SharpBrick.PoweredUp +{ + public class MoveHub : Hub + { + public MoveHub(ILegoWirelessProtocol protocol, IDeviceFactory deviceFactory, ILogger logger, IServiceProvider serviceProvider = default) + : base(protocol, deviceFactory, logger, serviceProvider, SystemType.LegoSystem_MoveHub, new Port[] { + new Port(0, "A", false, expectedDevice: DeviceType.MoveHubInternalMotor), + new Port(1, "B", false, expectedDevice: DeviceType.MoveHubInternalMotor), + // Since ports C and D can be any compatible sensor or motor, we don't set an expected device type here + new Port(2, nameof(C), true), + new Port(3, nameof(D), true), + new Port(16, "AB", false, expectedDevice: DeviceType.MoveHubInternalMotor, true), + new Port(50, string.Empty, false, expectedDevice: DeviceType.RgbLight), + new Port(58, string.Empty, false, expectedDevice: DeviceType.MoveHubTiltSensor), + new Port(59, string.Empty, false, expectedDevice: DeviceType.Current), + new Port(60, string.Empty, false, expectedDevice: DeviceType.Voltage), + // Note that there is a port with id 70 but is not currently known what this does (suspected to be debug port) + }, + knownProperties: new HubProperty[] { + HubProperty.AdvertisingName, + HubProperty.Button, + HubProperty.FwVersion, + HubProperty.HwVersion, + HubProperty.Rssi, + HubProperty.BatteryVoltage, + HubProperty.BatteryType, + HubProperty.ManufacturerName, + HubProperty.RadioFirmwareVersion, + HubProperty.LegoWirelessProtocolVersion, + HubProperty.SystemTypeId, + HubProperty.HardwareNetworkId, + HubProperty.PrimaryMacAddress, + HubProperty.SecondaryMacAddress, + //HubProperty.HardwareNetworkFamily, // Does not appear to work on Move Hub + }) + { } + + public Port C => Port(2); + public Port D => Port(3); + + public RgbLight RgbLight => Port(50).GetDevice(); + public MoveHubTiltSensor TiltSensor => Port(58).GetDevice(); + public Current Current => Port(59).GetDevice(); + public Voltage Voltage => Port(60).GetDevice(); + + /// + /// This is the virtual port of the motors built into the MoveHub. This controls both left and right motors + /// + public MoveHubInternalMotor MotorAtAB => Port(16).GetDevice(); + + /// + /// This is the motor built into the MoveHub controlling the left motor (B) + /// + public MoveHubInternalMotor LeftMotorAtB => Port(1).GetDevice(); + + /// + /// This is the motor built into the MoveHub controlling the right motor (A) + /// + public MoveHubInternalMotor RightMotorAtA => Port(0).GetDevice(); + } +} \ No newline at end of file diff --git a/src/SharpBrick.PoweredUp/IServiceCollectionExtensions.cs b/src/SharpBrick.PoweredUp/IServiceCollectionExtensions.cs index cec1e53..3dd8ec4 100644 --- a/src/SharpBrick.PoweredUp/IServiceCollectionExtensions.cs +++ b/src/SharpBrick.PoweredUp/IServiceCollectionExtensions.cs @@ -26,6 +26,7 @@ public static IServiceCollection AddPoweredUp(this IServiceCollection self) .AddTransient() .AddTransient() .AddTransient() + .AddTransient() // functions .AddTransient()