diff --git a/Joysticks/fsbgns530.joystick.json b/Joysticks/fsbgns530.joystick.json new file mode 100644 index 000000000..31d70b34b --- /dev/null +++ b/Joysticks/fsbgns530.joystick.json @@ -0,0 +1,155 @@ +{ + "$schema": "./mfjoystick.schema.json", + "InstanceName": "FlightSimBuilder 530", + "VendorId": 1240, + "ProductId": 59094, + "Inputs": [ + { + "Id": 1, + "Type": "Button", + "Label": "Left - Knob Outer - Left" + }, + { + "Id": 2, + "Type": "Button", + "Label": "Left - Knob Outer - Right" + }, + { + "Id": 3, + "Type": "Button", + "Label": "Left - Knob Inner - Left" + }, + { + "Id": 4, + "Type": "Button", + "Label": "Left - Knob Inner - Right" + }, + { + "Id": 5, + "Type": "Button", + "Label": "Left - Knob Inner - Push" + }, + { + "Id": 7, + "Type": "Button", + "Label": "Right - Knob Outer - Left" + }, + { + "Id": 8, + "Type": "Button", + "Label": "Right - Knob Outer - Right" + }, + { + "Id": 9, + "Type": "Button", + "Label": "Right - Knob Inner - Left" + }, + { + "Id": 10, + "Type": "Button", + "Label": "Right - Knob Inner - Right" + }, + { + "Id": 11, + "Type": "Button", + "Label": "Right - Knob Inner - Push" + }, + { + "Id": 12, + "Type": "Button", + "Label": "UNUSED" + }, + { + "Id": 13, + "Type": "Button", + "Label": "C <->" + }, + { + "Id": 14, + "Type": "Button", + "Label": ".C" + }, + { + "Id": 15, + "Type": "Button", + "Label": "V <->" + }, + { + "Id": 16, + "Type": "Button", + "Label": ".V" + }, + { + "Id": 17, + "Type": "Button", + "Label": "UNUSED" + }, + { + "Id": 18, + "Type": "Button", + "Label": "UNUSED" + }, + { + "Id": 19, + "Type": "Button", + "Label": "CDI" + }, + { + "Id": 20, + "Type": "Button", + "Label": "OBS" + }, + { + "Id": 21, + "Type": "Button", + "Label": "MSG" + }, + { + "Id": 22, + "Type": "Button", + "Label": "FPL" + }, + { + "Id": 23, + "Type": "Button", + "Label": "VNAV" + }, + { + "Id": 24, + "Type": "Button", + "Label": "PROC" + }, + { + "Id": 25, + "Type": "Button", + "Label": "RNG UP" + }, + { + "Id": 26, + "Type": "Button", + "Label": "RNG DOWN" + }, + { + "Id": 27, + "Type": "Button", + "Label": "Direct To" + }, + { + "Id": 28, + "Type": "Button", + "Label": "MENU" + }, + { + "Id": 29, + "Type": "Button", + "Label": "CLR" + }, + { + "Id": 30, + "Type": "Button", + "Label": "ENT" + } + ], + "Outputs": [ + ] +} diff --git a/Joysticks/saitek.multipanel.joystick.json b/Joysticks/saitek.multipanel.joystick.json new file mode 100644 index 000000000..5a1aef66a --- /dev/null +++ b/Joysticks/saitek.multipanel.joystick.json @@ -0,0 +1,178 @@ +{ + "$schema": "./mfjoystick.schema.json", + "InstanceName": "Pro Flight Multi Panel", + "VendorId": 1699, + "ProductId": 3334, + "Inputs": [ + { + "Id": 0, + "Type": "Button", + "Label": "Position Switch - ALT" + }, + { + "Id": 1, + "Type": "Button", + "Label": "Position Switch - VS" + }, + { + "Id": 2, + "Type": "Button", + "Label": "Position Switch - IAS" + }, + { + "Id": 3, + "Type": "Button", + "Label": "Position Switch - HDG" + }, + { + "Id": 4, + "Type": "Button", + "Label": "Position Switch - CRS" + }, + { + "Id": 5, + "Type": "Button", + "Label": "Encoder Right" + }, + { + "Id": 6, + "Type": "Button", + "Label": "Encoder Left" + }, + { + "Id": 7, + "Type": "Button", + "Label": "AP Button" + }, + { + "Id": 8, + "Type": "Button", + "Label": "HDG Button" + }, + { + "Id": 9, + "Type": "Button", + "Label": "NAV Button" + }, + { + "Id": 10, + "Type": "Button", + "Label": "IAS Button" + }, + { + "Id": 11, + "Type": "Button", + "Label": "ALT Button" + }, + { + "Id": 12, + "Type": "Button", + "Label": "VS Button" + }, + { + "Id": 13, + "Type": "Button", + "Label": "APR Button" + }, + { + "Id": 14, + "Type": "Button", + "Label": "REV Button" + }, + { + "Id": 15, + "Type": "Button", + "Label": "Auto Throttle ARM" + }, + { + "Id": 16, + "Type": "Button", + "Label": "Flaps UP" + }, + { + "Id": 17, + "Type": "Button", + "Label": "Flaps DN" + }, + { + "Id": 18, + "Type": "Button", + "Label": "Pitch Trim DN" + }, + { + "Id": 19, + "Type": "Button", + "Label": "Pitch Trim UP" + } + ], + "Outputs": [ + { + "Label": "AP Mode - On/Off", + "Type": "Output", + "Id": "AP.autopilot", + "Byte": 11, + "Bit": 0 + }, + { + "Label": "AP Mode - HDG", + "Type": "Output", + "Id": "AP.hdg", + "Byte": 11, + "Bit": 1 + }, + { + "Label": "AP Mode - NAV", + "Id": "AP.nav", + "Type": "Output", + "Byte": 11, + "Bit": 2 + }, + { + "Label": "AP Mode - IAS", + "Id": "AP.ias", + "Type": "Output", + "Byte": 11, + "Bit": 3 + }, + { + "Label": "AP Mode - ALT", + "Id": "AP.alt", + "Type": "Output", + "Byte": 11, + "Bit": 4 + }, + { + "Label": "AP Mode - VS", + "Id": "AP.vs", + "Type": "Output", + "Byte": 11, + "Bit": 5 + }, + { + "Label": "AP Mode - APR", + "Id": "AP.apr", + "Type": "Output", + "Byte": 11, + "Bit": 6 + }, + { + "Label": "AP Mode - REV", + "Id": "AP.rev", + "Type": "Output", + "Byte": 11, + "Bit": 7 + }, + { + "Label": "Line 1", + "Id": "CUSTOM.line1", + "Byte": 1, + "Type": "Custom" + }, + { + "Label": "Line 2", + "Id": "CUSTOM.line2", + "Byte": 6, + "Type": "Custom" + } + ] +} diff --git a/MobiFlight/CustomDevices/CustomDevice.cs b/MobiFlight/CustomDevices/CustomDevice.cs index 6f36f09f3..749923c98 100644 --- a/MobiFlight/CustomDevices/CustomDevice.cs +++ b/MobiFlight/CustomDevices/CustomDevice.cs @@ -125,7 +125,7 @@ public class CustomDevice : IMigrateable /// /// List of MessageTypes supported by the device. /// - public List MessageTypes = new List(); + public List MessageTypes { get; set; } = new List(); /// /// Base path for custom firmware diff --git a/MobiFlight/CustomDevices/IMessageTypeProvider.cs b/MobiFlight/CustomDevices/IMessageTypeProvider.cs new file mode 100644 index 000000000..0c426cb28 --- /dev/null +++ b/MobiFlight/CustomDevices/IMessageTypeProvider.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace MobiFlight.CustomDevices +{ + public interface IMessageTypeProvider + { + List MessageTypes { get; } + } +} \ No newline at end of file diff --git a/MobiFlight/ExecutionManager.cs b/MobiFlight/ExecutionManager.cs index 6e83e485a..e306dfd08 100644 --- a/MobiFlight/ExecutionManager.cs +++ b/MobiFlight/ExecutionManager.cs @@ -142,7 +142,7 @@ public ExecutionManager(DataGridView dataGridViewConfig, DataGridView inputsData #endif joystickManager.SetHandle(handle); joystickManager.OnButtonPressed += new ButtonEventHandler(mobiFlightCache_OnButtonPressed); - joystickManager.Connected += (o, e) => { joystickManager.Startup(); }; + joystickManager.Connected += (o, e) => { joystickManager.Startup(); }; if (Properties.Settings.Default.EnableJoystickSupport) { joystickManager.Connect(); @@ -186,7 +186,7 @@ internal Dictionary GetAvailableVariables() // cfg was not set yet, e.g. we created row and are still in edit mode and now we hit "Edit" if (cfg == null) continue; - + if (cfg.SourceType != SourceType.VARIABLE) continue; if (cfg.MobiFlightVariable == null) continue; @@ -208,7 +208,7 @@ internal Dictionary GetAvailableVariables() List actions = cfg.GetInputActionsByType(typeof(VariableInputAction)); - if(actions == null) continue; + if (actions == null) continue; actions.ForEach(action => { @@ -280,7 +280,7 @@ public bool ModulesAvailable() #if ARCAZE arcazeCache.Available() || #endif - mobiFlightCache.Available(); + mobiFlightCache.Available() || joystickManager.GetJoysticks().Count > 0; #endif } @@ -732,13 +732,23 @@ private string ExecuteDisplay(string value, OutputConfigItem cfg) if (serial.IndexOf(Joystick.SerialPrefix)==0) { - Joystick joystick = joystickManager.GetJoystickBySerial(serial); + IHidDevice joystick = joystickManager.GetJoystickBySerial(serial); if(joystick != null) { - byte state = 0; - if (value != "0") state = 1; + if (cfg.DisplayType==DeviceType.CustomDevice.ToString()) + { + var device = joystick.GetAvailableOutputDevicesAsListItems().Find(d => { + return (d.Value as ICustomDevice)?.Name == cfg.CustomDevice.CustomName; + }); + if (device == null) return value; + (device.Value as ICustomDevice).Display(cfg.CustomDevice.MessageType, value); + } else + { + byte state = 0; + if (value != "0") state = 1; - joystick.SetOutputDeviceState(cfg.Pin.DisplayPin, state); + joystick.SetOutputDeviceState(cfg.Pin.DisplayPin, state); + } joystick.UpdateOutputDeviceStates(); joystick.Update(); } else diff --git a/MobiFlight/ICustomDevice.cs b/MobiFlight/ICustomDevice.cs new file mode 100644 index 000000000..8759086a8 --- /dev/null +++ b/MobiFlight/ICustomDevice.cs @@ -0,0 +1,11 @@ +using MobiFlight.CustomDevices; +using System.Collections.Generic; + +namespace MobiFlight +{ + public interface ICustomDevice : IConnectedDevice + { + void Display(int MessageType, string value); + List MessageTypes { get; } + } +} \ No newline at end of file diff --git a/MobiFlight/Joysticks/FlightSimBuilder/GNS530.cs b/MobiFlight/Joysticks/FlightSimBuilder/GNS530.cs new file mode 100644 index 000000000..900517a04 --- /dev/null +++ b/MobiFlight/Joysticks/FlightSimBuilder/GNS530.cs @@ -0,0 +1,73 @@ +using HidSharp; +using HidSharp.Reports; +using HidSharp.Reports.Input; + +namespace MobiFlight.Joysticks.FlightSimBuilder +{ + internal class GNS530 : Joystick + { + int VendorId = 0x04D8; + int ProductId = 0xE89D; + HidStream Stream { get; set; } + HidSharp.HidDevice Device { get; set; } + + protected HidDeviceInputReceiver inputReceiver; + + public GNS530(SharpDX.DirectInput.Joystick joystick, JoystickDefinition definition) : base(joystick, definition) { + } + + public void Connect() + { + if (Device == null) + { + Device = DeviceList.Local.GetHidDeviceOrNull(vendorID: VendorId, productID: ProductId); + if (Device == null) return; + } + + Stream = Device.Open(); + var reportDescriptor = Device.GetReportDescriptor(); + inputReceiver = reportDescriptor.CreateHidDeviceInputReceiver(); + inputReceiver.Received += InputReceiver_Received; + inputReceiver.Start(Stream); + } + + private void InputReceiver_Received(object sender, System.EventArgs e) + { + var inputRec = sender as HidDeviceInputReceiver; + var inputReportBuffer = new byte[5]; + + while (inputRec.TryRead(inputReportBuffer, 0, out Report report)) + { + var newState = Gns530Report.ParseReport(inputReportBuffer).ToJoystickState(); + UpdateButtons(newState); + // at the very end update our state + State = newState; + } + } + + protected override void SendData(byte[] data) + { + /* do nothing */ + } + + public override void Update() + { + if (Stream == null || inputReceiver == null) + { + Connect(); + }; + // We don't do anything else + // because we have a callback for + // handling the incoming reports + // InputReceiver_Received(inputReceiver, new System.EventArgs()); + } + + public override void Shutdown() + { + Stream.Close(); + inputReceiver.Received -= InputReceiver_Received; + Stream = null; + inputReceiver = null; + } + } +} diff --git a/MobiFlight/Joysticks/FlightSimBuilder/Gns530Report.cs b/MobiFlight/Joysticks/FlightSimBuilder/Gns530Report.cs new file mode 100644 index 000000000..8518c9ddc --- /dev/null +++ b/MobiFlight/Joysticks/FlightSimBuilder/Gns530Report.cs @@ -0,0 +1,33 @@ +using SharpDX.DirectInput; + +namespace MobiFlight.Joysticks.FlightSimBuilder +{ + internal class Gns530Report + { + public uint reportID; + public int rxAxis; + public uint buttonState; + static public Gns530Report ParseReport(byte[] inputBuffer) + { + var result = new Gns530Report(); + // get Report ID + result.reportID = inputBuffer[0]; + // Extract the 10-bit value (bitmask 0x03FF isolates the first 10 bits) + // rxAxis = (inputBuffer[2] << 8 | inputBuffer[1]) & 0x03FF; + // get 32 bit Button report field: + result.buttonState = (uint)inputBuffer[1] + ((uint)inputBuffer[2] << 8) + ((uint)inputBuffer[3] << 16) + ((uint)inputBuffer[4] << 24); + return result; + } + public JoystickState ToJoystickState() + { + var result = new JoystickState(); + + for (int i = 0; i != 30; i++) + { + result.Buttons[i] = (buttonState & (1 << i)) != 0; + } + + return result; + } + } +} diff --git a/MobiFlight/Joysticks/HidDevice.cs b/MobiFlight/Joysticks/HidDevice.cs new file mode 100644 index 000000000..c1b5d9f29 --- /dev/null +++ b/MobiFlight/Joysticks/HidDevice.cs @@ -0,0 +1,343 @@ +using HidSharp; +using HidSharp.Reports; +using HidSharp.Reports.Input; +using MobiFlight.Config; +using MobiFlight.Joysticks.Logitech; +using SharpDX.DirectInput; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MobiFlight.Joysticks +{ + internal class HidDevice : IHidDevice + { + public static readonly string ButtonPrefix = "Button"; + public static readonly string AxisPrefix = "Axis"; + public static readonly string PovPrefix = "POV"; + public static readonly string SerialPrefix = "JS-"; + + public string Name { get { return Definition.InstanceName; }} + public string Serial { get { return $"{SerialPrefix}{Definition.InstanceName}"; } } + + protected JoystickState State = null; + protected List Buttons = new List(); + private readonly List Axes = new List(); + private readonly List POV = new List(); + private readonly List Outputs = new List(); + + public event ButtonEventHandler OnButtonPressed; + + protected readonly JoystickDefinition Definition; + protected bool RequiresOutputUpdate = false; + private HidStream Stream; + private HidSharp.HidDevice Device; + + protected HidDeviceInputReceiver inputReceiver; + + public HidDevice(JoystickDefinition definition) + { + Definition = definition; + } + + public void Connect() + { + if (Device == null) + { + Device = DeviceList.Local.GetHidDeviceOrNull(vendorID: Definition.VendorId, productID: Definition.ProductId); + if (Device == null) return; + } + + Stream = Device.Open(); + var reportDescriptor = Device.GetReportDescriptor(); + InitializeDevicesWithDescriptor(reportDescriptor); + inputReceiver = reportDescriptor.CreateHidDeviceInputReceiver(); + inputReceiver.Received += InputReceiver_Received; + inputReceiver.Start(Stream); + } + + private void InitializeDevicesWithDescriptor(ReportDescriptor reportDescriptor) + { + reportDescriptor.Reports.ToList().ForEach(report => + { + if (report.ReportType == ReportType.Input) + { + report.DataItems.ToList().ForEach(data => + { + if (data.ExpectedUsageType == ExpectedUsageType.PushButton) + { + for (var i = 0; i < data.ElementCount; i++) + { + var buttonName = $"{ButtonPrefix} {i}"; + var buttonLabel = MapDeviceNameToLabel(buttonName); + Buttons.Add(new JoystickDevice() { Name = buttonName, Label = buttonLabel, Type = DeviceType.Button, JoystickDeviceType = JoystickDeviceType.Button }); + } + } + }); + } + + if (report.ReportType == ReportType.Feature) + { + // Do something with the feature information from the json file + } + + if (report.ReportType == ReportType.Output) + { + // Do something with the output information from the json file + } + }); + reportDescriptor.DeviceItems.ToList().ForEach(item => { + var parser = item.CreateDeviceItemInputParser(); + }); + + EnumerateOutputDevices(); + } + + virtual protected void EnumerateOutputDevices() + { + Outputs.Clear(); + + Definition?.Outputs?.ForEach(output => { + JoystickOutputDevice device = new JoystickOutputDevice() { Label = output.Label, Name = output.Id, Byte = output.Byte, Bit = output.Bit }; + if (output.Type=="Custom") + { + device = new JoystickStringOutputDevice(device); + } + Outputs.Add(device); + }); + return; + } + + public string MapDeviceNameToLabel(string deviceName) + { + // First try and look for a custom label. + var input = Definition?.FindInputByName(deviceName); + if (input != null) + { + return input.Label; + } + + string result = string.Empty; + + if (deviceName.StartsWith(ButtonPrefix)) + { + result = Buttons.Find(b => b.Name == deviceName)?.Label ?? string.Empty; + } + else if (deviceName.StartsWith(AxisPrefix)) + { + result = Axes.Find(a => a.Name == deviceName)?.Label ?? string.Empty; + } + else if (deviceName.StartsWith(PovPrefix)) + { + result = POV.Find(p => p.Name == deviceName)?.Label ?? string.Empty; + } + + if (result == string.Empty) + result = deviceName; + + return result; + } + + protected void UpdateButtons(JoystickState newState) + { + if (Buttons.Count == 0) return; + + for (int i = 0; i < newState.Buttons.Length; i++) + { + if (!StateExists() || State.Buttons.Length < i || State.Buttons[i] != newState.Buttons[i]) + { + if (newState.Buttons[i] || (State != null)) + OnButtonPressed?.Invoke(this, new InputEventArgs() + { + Name = Name, + DeviceId = Buttons[i].Name, + DeviceLabel = Buttons[i].Label, + Serial = Serial, + Type = DeviceType.Button, + Value = newState.Buttons[i] ? 0 : 1 + }); + } + } + } + + private void InputReceiver_Received(object sender, System.EventArgs e) + { + var inputRec = sender as HidDeviceInputReceiver; + var inputReportBuffer = new byte[32]; + + while (inputRec.TryRead(inputReportBuffer, 0, out Report report)) + { + var newState = MultipanelReport.ParseReport(inputReportBuffer).ToJoystickState(); + UpdateButtons(newState); + // at the very end update our state + State = newState; + } + } + + private bool StateExists() + { + return State != null; + } + + public void SetOutputDeviceState(string name, byte state) + { + foreach (var light in Outputs.FindAll(l => l.JoystickDeviceType == JoystickDeviceType.Light)) + { + if (light.Label != name) continue; + if (light.State == state) continue; + + light.State = state; + RequiresOutputUpdate = true; + break; + } + } + public void SetCustomDeviceState(string name, string value) + { + foreach (var light in Outputs.FindAll(l => l.JoystickDeviceType == JoystickDeviceType.String)) + { + // + } + } + + protected virtual void SendData(byte[] data) + { + // Don't try and send data if no outputs are defined. + if (Definition?.Outputs == null || Definition?.Outputs.Count == 0) + { + return; + } + + if (!RequiresOutputUpdate) return; + if (Stream == null) + { + Connect(); + }; + Stream.SetFeature(data); + + RequiresOutputUpdate = false; + } + + public void UpdateOutputDeviceStates() + { + var data = new byte[] { 0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x01, 0, 0xff }; + var mappingTable = new Dictionary() + { + { " ", 0b00001111 }, + { "0", 0x00 }, + { "1", 0x01 }, + { "2", 0x02 }, + { "3", 0x03 }, + { "4", 0x04 }, + { "5", 0x05 }, + { "6", 0x06 }, + { "7", 0x07 }, + { "8", 0x08 }, + { "9", 0x09 }, + }; + + foreach (var light in Outputs.FindAll(l=>l.JoystickDeviceType==JoystickDeviceType.Light)) + { + data[light.Byte] |= (byte)(light.State << light.Bit); + } + + foreach (var stringOutput in Outputs.FindAll(l=>l.JoystickDeviceType==JoystickDeviceType.String)) + { + var strOutputDevice = stringOutput as JoystickStringOutputDevice; + if (strOutputDevice == null) continue; + if (strOutputDevice.StringState == null) continue; + for (var i = 0; i!= strOutputDevice.StringState.Length; i++) + { + var value = mappingTable.ContainsKey(strOutputDevice.StringState[i].ToString()) ? mappingTable[strOutputDevice.StringState[i].ToString()] : (byte)0b00001111; + data[i + strOutputDevice.Byte] = value; + } + } + RequiresOutputUpdate = true; + + SendData(data); + } + + public virtual void Update() + { + if (Stream == null || inputReceiver == null) + { + Connect(); + }; + } + + public virtual void Shutdown() + { + Stream.Close(); + inputReceiver.Received -= InputReceiver_Received; + Stream = null; + inputReceiver = null; + } + + public List> GetAvailableDevicesAsListItems() + { + List> result = new List>(); + + GetButtonsSorted().ForEach((item) => + { + result.Add(item.ToListItem()); + }); + GetAxisSorted().ForEach((item) => + { + result.Add(item.ToListItem()); + }); + POV.ForEach((item) => + { + result.Add(item.ToListItem()); + }); + return result; + } + + public List> GetAvailableOutputDevicesAsListItems() + { + List> result = new List>(); + Outputs.ForEach((item) => + { + result.Add(item.ToListItem()); + }); + return result; + } + + private List GetButtonsSorted() + { + var buttons = Buttons.ToArray().ToList(); + buttons.Sort(SortByPositionInDefintion); + return Buttons; + } + + private List GetAxisSorted() + { + var axes = Axes.ToArray().ToList(); + Axes.Sort(SortByPositionInDefintion); + + return Axes; + } + + public int GetIndexForKey(string key) + { + return Definition?.Inputs?.FindIndex(input => input.Name == key) ?? 0; + } + + int SortByPositionInDefintion(JoystickDevice b1, JoystickDevice b2) + { + if (GetIndexForKey(b1.Name) == GetIndexForKey(b2.Name)) return 0; + if (GetIndexForKey(b1.Name) > GetIndexForKey(b2.Name)) return 1; + return -1; + } + + public void Stop() + { + foreach (var light in Outputs) + { + light.State = 0; + } + RequiresOutputUpdate = true; + UpdateOutputDeviceStates(); + } + + + } +} diff --git a/MobiFlight/Joysticks/IHidDevice.cs b/MobiFlight/Joysticks/IHidDevice.cs new file mode 100644 index 000000000..3fccd890b --- /dev/null +++ b/MobiFlight/Joysticks/IHidDevice.cs @@ -0,0 +1,22 @@ +using MobiFlight.Config; +using System.Collections.Generic; +using System; + +namespace MobiFlight +{ + public interface IHidDevice + { + string Name { get; } + + string Serial { get; } + + List> GetAvailableDevicesAsListItems(); + List> GetAvailableOutputDevicesAsListItems(); + string MapDeviceNameToLabel(string deviceName); + void SetOutputDeviceState(string displayPin, byte state); + void Shutdown(); + void Stop(); + void Update(); + void UpdateOutputDeviceStates(); + } +} \ No newline at end of file diff --git a/MobiFlight/Joysticks/Joystick.cs b/MobiFlight/Joysticks/Joystick.cs index 705e967f2..0d3eb3d17 100644 --- a/MobiFlight/Joysticks/Joystick.cs +++ b/MobiFlight/Joysticks/Joystick.cs @@ -12,7 +12,7 @@ public class JoystickNotConnectedException : Exception public JoystickNotConnectedException(string Message) : base(Message) { } } - public class Joystick + public class Joystick : IHidDevice { public static readonly string ButtonPrefix = "Button"; public static readonly string AxisPrefix = "Axis"; @@ -32,7 +32,7 @@ public class Joystick private HidDevice Device; protected bool RequiresOutputUpdate = false; - private JoystickState State = null; + protected JoystickState State = null; private HidStream Stream; private static readonly Dictionary UsageMap = new Dictionary @@ -366,7 +366,7 @@ private void UpdateAxis(JoystickState newState) } } - private void UpdateButtons(JoystickState newState) + protected void UpdateButtons(JoystickState newState) { if (Buttons.Count==0) return; diff --git a/MobiFlight/Joysticks/JoystickDefinition.cs b/MobiFlight/Joysticks/JoystickDefinition.cs index afdea18f4..052a589f5 100644 --- a/MobiFlight/Joysticks/JoystickDefinition.cs +++ b/MobiFlight/Joysticks/JoystickDefinition.cs @@ -33,6 +33,7 @@ public class JoystickDefinition : IMigrateable /// public int VendorId; + /// /// Finds a JoystickInput given an input name. This will eventually get replaced with a method that /// looks up by Id instead. diff --git a/MobiFlight/Joysticks/JoystickDevice.cs b/MobiFlight/Joysticks/JoystickDevice.cs index 002645d54..1857b3c30 100644 --- a/MobiFlight/Joysticks/JoystickDevice.cs +++ b/MobiFlight/Joysticks/JoystickDevice.cs @@ -1,5 +1,7 @@ using MobiFlight.Config; +using MobiFlight.CustomDevices; using System; +using System.Collections.Generic; namespace MobiFlight { @@ -24,7 +26,48 @@ public class JoystickOutputDevice : JoystickDevice public byte State = 0; public JoystickOutputDevice() { - Type = DeviceType.LedModule; + JoystickDeviceType = JoystickDeviceType.Light; + Type = DeviceType.Output; + } + } + + public class JoystickStringOutputDevice : JoystickOutputDevice, ICustomDevice + { + public string StringState = null; + + public JoystickStringOutputDevice() + { + Type = DeviceType.CustomDevice; + JoystickDeviceType = JoystickDeviceType.String; + } + + public JoystickStringOutputDevice(JoystickOutputDevice device) + { + Type = DeviceType.CustomDevice; + Name = device.Name; + Label = device.Label; + Byte = device.Byte; + JoystickDeviceType = JoystickDeviceType.String; + } + + public void Display(int MessageType, string value) + { + StringState = value; + } + + public List MessageTypes + { + get + { + var list = new List(); + list.Add(new MessageType() { Id=1, Description = "You can display up to 5 characters.", Label="Set LCD" }); + return list; + } + } + + public void Stop() + { + // Do nothing } } } diff --git a/MobiFlight/Joysticks/JoystickDeviceType.cs b/MobiFlight/Joysticks/JoystickDeviceType.cs index ffc1ce6b4..08b2482ed 100644 --- a/MobiFlight/Joysticks/JoystickDeviceType.cs +++ b/MobiFlight/Joysticks/JoystickDeviceType.cs @@ -5,6 +5,7 @@ public enum JoystickDeviceType Button, Axis, POV, - Light + Light, + String } } diff --git a/MobiFlight/Joysticks/JoystickManager.cs b/MobiFlight/Joysticks/JoystickManager.cs index 09d50c6c1..dc5fd9bc2 100644 --- a/MobiFlight/Joysticks/JoystickManager.cs +++ b/MobiFlight/Joysticks/JoystickManager.cs @@ -1,10 +1,14 @@ -using MobiFlight.Joysticks.Octavi; +using HidSharp; +using MobiFlight.Joysticks.FlightSimBuilder; +using MobiFlight.Joysticks.Logitech; +using MobiFlight.Joysticks.Octavi; using Newtonsoft.Json; using SharpDX.DirectInput; using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using System.Timers; namespace MobiFlight @@ -31,7 +35,8 @@ public class JoystickManager public event ButtonEventHandler OnButtonPressed; private readonly Timer PollTimer = new Timer(); private readonly List Joysticks = new List(); - private readonly List ExcludedJoysticks = new List(); + private readonly List HidDevices = new List(); + private readonly List ExcludedJoysticks = new List(); private IntPtr Handle; public JoystickManager() @@ -114,12 +119,15 @@ public void Stop() } } - public List GetJoysticks() + public List GetJoysticks() { - return Joysticks; + var result = new List(); + result.AddRange(Joysticks); + result.AddRange(HidDevices); + return result; } - public List GetExcludedJoysticks() + public List GetExcludedJoysticks() { return ExcludedJoysticks; } @@ -129,14 +137,14 @@ public void SetHandle(IntPtr handle) Handle = handle; } - public void Connect() + public async void Connect() { var di = new SharpDX.DirectInput.DirectInput(); Joysticks?.Clear(); ExcludedJoysticks?.Clear(); List settingsExcludedJoysticks = JsonConvert.DeserializeObject>(Properties.Settings.Default.ExcludedJoysticks); - - var devices = di.GetDevices(DeviceClass.GameControl, DeviceEnumerationFlags.AttachedOnly).ToList(); + + var devices = await Task.Run(() => { return di.GetDevices(DeviceClass.GameControl, DeviceEnumerationFlags.AttachedOnly).ToList(); }).ConfigureAwait(false); foreach (var d in devices) { @@ -148,7 +156,7 @@ public void Connect() continue; } - MobiFlight.Joystick js; + Joystick js; if (d.InstanceName == "Octavi" || d.InstanceName == "IFR1") { js = new Octavi( @@ -158,6 +166,15 @@ public void Connect() GetDefinitionByInstanceName("Octavi") ); } + else if (d.InstanceName == "FlightSimBuilder 530" || d.InstanceName == "FlightSimBuilder 530") + { + js = new GNS530( + new SharpDX.DirectInput.Joystick(di, d.InstanceGuid), + // statically set this to Octavi + // until we might support (Octavi|IFR1) or similar + GetDefinitionByInstanceName("FlightSimBuilder 530") + ); + } else { js = new Joystick(new SharpDX.DirectInput.Joystick(di, d.InstanceGuid), GetDefinitionByInstanceName(d.InstanceName)); @@ -185,6 +202,8 @@ public void Connect() } } + ConnectToDirectHid(); + if (JoysticksConnected()) { Joysticks.Sort((j1, j2) => j1.Name.CompareTo(j2.Name)); @@ -192,12 +211,37 @@ public void Connect() } } + private void ConnectToDirectHid() + { + HidDevices.Clear(); + var directHidDevice = DeviceList.Local.GetHidDevices(); + directHidDevice.ToList().ForEach(d => + { + try + { + var FriendlyName = d.GetFriendlyName(); + + var definition = GetDefinitionByInstanceName(FriendlyName); + if (definition == null) return; + + var js = new Multipanel(definition); + js.OnButtonPressed += Js_OnButtonPressed; + js.Connect(); + HidDevices.Add(js); + }catch (Exception ex) { + return; + } + }); + } + private void Js_OnDisconnected(object sender, EventArgs e) { - var js = sender as Joystick; + var js = sender as IHidDevice; Log.Instance.log($"Joystick disconnected: {js.Name}.", LogSeverity.Info); - lock (Joysticks) - Joysticks.Remove(js); + + if(js is Joystick) + lock (Joysticks) + Joysticks.Remove(js as Joystick); } private bool HasAxisOrButtons(Joystick js) @@ -212,9 +256,12 @@ private void Js_OnButtonPressed(object sender, InputEventArgs e) OnButtonPressed?.Invoke(sender, e); } - internal Joystick GetJoystickBySerial(string serial) + internal IHidDevice GetJoystickBySerial(string serial) { - return Joysticks.Find(js => js.Serial == serial); + var result = Joysticks.Find(js => js.Serial == serial); + if (result != null) return result; + + return HidDevices.Find(js => js.Serial == serial); } public Dictionary GetStatistics() diff --git a/MobiFlight/Joysticks/JoystickOutput.cs b/MobiFlight/Joysticks/JoystickOutput.cs index cf3d1ae5f..bffa91eab 100644 --- a/MobiFlight/Joysticks/JoystickOutput.cs +++ b/MobiFlight/Joysticks/JoystickOutput.cs @@ -8,6 +8,10 @@ namespace MobiFlight { public class JoystickOutput { + /// + /// Unique Id for the output. + /// + public string Type; /// /// Unique Id for the output. /// diff --git a/MobiFlight/Joysticks/Logitech/Multipanel.cs b/MobiFlight/Joysticks/Logitech/Multipanel.cs new file mode 100644 index 000000000..596640b40 --- /dev/null +++ b/MobiFlight/Joysticks/Logitech/Multipanel.cs @@ -0,0 +1,12 @@ +using HidSharp; +using HidSharp.Reports; +using HidSharp.Reports.Input; + +namespace MobiFlight.Joysticks.Logitech +{ + internal class Multipanel : MobiFlight.Joysticks.HidDevice + { + public Multipanel(JoystickDefinition definition) : base(definition){ + } + } +} diff --git a/MobiFlight/Joysticks/Logitech/MultipanelReport.cs b/MobiFlight/Joysticks/Logitech/MultipanelReport.cs new file mode 100644 index 000000000..0ceda0b29 --- /dev/null +++ b/MobiFlight/Joysticks/Logitech/MultipanelReport.cs @@ -0,0 +1,33 @@ +using SharpDX.DirectInput; + +namespace MobiFlight.Joysticks.Logitech +{ + internal class MultipanelReport + { + public uint reportID; + public int rxAxis; + public uint buttonState; + static public MultipanelReport ParseReport(byte[] inputBuffer) + { + var result = new MultipanelReport(); + // get Report ID + result.reportID = inputBuffer[0]; + // Extract the 10-bit value (bitmask 0x03FF isolates the first 10 bits) + // rxAxis = (inputBuffer[2] << 8 | inputBuffer[1]) & 0x03FF; + // get 32 bit Button report field: + result.buttonState = (uint)inputBuffer[1] + ((uint)inputBuffer[2] << 8) + ((uint)inputBuffer[3] << 16) + ((uint)inputBuffer[4] << 24); + return result; + } + public JoystickState ToJoystickState() + { + var result = new JoystickState(); + + for (int i = 0; i != 30; i++) + { + result.Buttons[i] = (buttonState & (1 << i)) != 0; + } + + return result; + } + } +} diff --git a/MobiFlight/Joysticks/Octavi/Octavi.cs b/MobiFlight/Joysticks/Octavi/Octavi.cs index c275d9080..afdd9615f 100644 --- a/MobiFlight/Joysticks/Octavi/Octavi.cs +++ b/MobiFlight/Joysticks/Octavi/Octavi.cs @@ -1,9 +1,7 @@ using HidSharp; using HidSharp.Reports; using HidSharp.Reports.Input; -using SharpDX.DirectInput; using System.Collections.Generic; -using System.Threading; namespace MobiFlight.Joysticks.Octavi { @@ -12,9 +10,9 @@ internal class Octavi : Joystick int VendorId = 0x04D8; int ProductId = 0xE6D6; HidStream Stream { get; set; } - HidDevice Device { get; set; } + HidSharp.HidDevice Device { get; set; } - protected HidSharp.Reports.Input.HidDeviceInputReceiver inputReceiver; + protected HidDeviceInputReceiver inputReceiver; protected ReportDescriptor reportDescriptor; protected Dictionary OctaviButtons = new Dictionary(); diff --git a/MobiFlight/MobiFlightCustomDevice.cs b/MobiFlight/MobiFlightCustomDevice.cs index 62c547d21..ec1b61f80 100644 --- a/MobiFlight/MobiFlightCustomDevice.cs +++ b/MobiFlight/MobiFlightCustomDevice.cs @@ -1,8 +1,10 @@ using CommandMessenger; +using MobiFlight.CustomDevices; +using System.Collections.Generic; namespace MobiFlight { - public class MobiFlightCustomDevice : IConnectedDevice + public class MobiFlightCustomDevice : ICustomDevice { public const string TYPE = "CustomDevice"; public const int MESSAGE_STOP = -1; @@ -12,6 +14,7 @@ public class MobiFlightCustomDevice : IConnectedDevice public DeviceType Type { get; set; } = DeviceType.CustomDevice; public int DeviceNumber { get; set; } public CustomDevices.CustomDevice CustomDevice { get; set; } + public List MessageTypes { get { return CustomDevice.MessageTypes; } } public void Display(int MessageType, string value) { diff --git a/MobiFlightConnector.csproj b/MobiFlightConnector.csproj index 58c303424..467a692ab 100644 --- a/MobiFlightConnector.csproj +++ b/MobiFlightConnector.csproj @@ -264,6 +264,7 @@ + @@ -272,6 +273,10 @@ + + + + @@ -280,6 +285,8 @@ + + @@ -1054,6 +1061,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/UI/Dialogs/ConfigWizard.cs b/UI/Dialogs/ConfigWizard.cs index fe3501b23..324e4381e 100644 --- a/UI/Dialogs/ConfigWizard.cs +++ b/UI/Dialogs/ConfigWizard.cs @@ -309,7 +309,7 @@ protected void _AddMobiFlightModules(List DisplayModuleList) protected void _AddJoysticks(List DisplayModuleList) { - foreach (Joystick joystick in _execManager.GetJoystickManager().GetJoysticks()) + foreach (IHidDevice joystick in _execManager.GetJoystickManager().GetJoysticks()) { if (joystick.GetAvailableOutputDevicesAsListItems().Count == 0) continue; diff --git a/UI/Dialogs/InputConfigWizard.cs b/UI/Dialogs/InputConfigWizard.cs index f45fabd1d..dde417e2c 100644 --- a/UI/Dialogs/InputConfigWizard.cs +++ b/UI/Dialogs/InputConfigWizard.cs @@ -210,7 +210,7 @@ public void initWithArcazeCache(ArcazeCache arcazeCache) }); } - foreach (Joystick joystick in _execManager.GetJoystickManager().GetJoysticks()) + foreach (IHidDevice joystick in _execManager.GetJoystickManager().GetJoysticks()) { if (joystick.GetAvailableDevicesAsListItems().Count > 0) inputModuleNameComboBox.Items.Add(new ListItem() @@ -254,7 +254,7 @@ public void initWithoutArcazeCache() // preconditionPinSerialComboBox.Items.Add(module.Name + "/ " + module.Serial); } - foreach (Joystick joystick in _execManager.GetJoystickManager().GetJoysticks()) + foreach (IHidDevice joystick in _execManager.GetJoystickManager().GetJoysticks()) { inputModuleNameComboBox.Items.Add(new ListItem() { @@ -468,7 +468,7 @@ private void ModuleSerialComboBox_SelectedIndexChanged(object sender, EventArgs // Add all Joysticks else if (Joystick.IsJoystickSerial(serial)) { - Joystick joystick = _execManager.GetJoystickManager().GetJoystickBySerial(serial); + IHidDevice joystick = _execManager.GetJoystickManager().GetJoystickBySerial(serial); inputTypeComboBox.Items.AddRange(joystick.GetAvailableDevicesAsListItems().ToArray()); } // Add all MidiBoards diff --git a/UI/MainForm.cs b/UI/MainForm.cs index d15f7dee2..9f87fd2fd 100644 --- a/UI/MainForm.cs +++ b/UI/MainForm.cs @@ -1494,7 +1494,7 @@ private void _checkForOrphanedJoysticks(bool showNotNecessaryMessage) List serials = new List(); List NotConnectedJoysticks = new List(); - foreach (Joystick j in execManager.GetJoystickManager().GetJoysticks()) + foreach (IHidDevice j in execManager.GetJoystickManager().GetJoysticks()) { serials.Add($"{j.Name} {SerialNumber.SerialSeparator}{j.Serial}"); } diff --git a/UI/Panels/Output/CustomDevicePanel.cs b/UI/Panels/Output/CustomDevicePanel.cs index c77bedf44..5dfbd6400 100644 --- a/UI/Panels/Output/CustomDevicePanel.cs +++ b/UI/Panels/Output/CustomDevicePanel.cs @@ -18,7 +18,7 @@ public CustomDevicePanel() InitializeComponent(); } - public void SetCustomDeviceNames(List> pins) + public void SetCustomDeviceNames(List> pins) { customDeviceNamesComboBox.ValueMember = "Value"; customDeviceNamesComboBox.DisplayMember = "Label"; @@ -50,7 +50,7 @@ internal OutputConfigItem syncToConfig(OutputConfigItem config) { if (customDeviceNamesComboBox.SelectedValue != null) { - config.CustomDevice.CustomName = (customDeviceNamesComboBox.SelectedValue as MobiFlightCustomDevice).Name.ToString (); + config.CustomDevice.CustomName = (customDeviceNamesComboBox.SelectedValue as ICustomDevice).Name.ToString (); } if (MessageTypeComboBox.SelectedValue != null) @@ -66,13 +66,12 @@ internal OutputConfigItem syncToConfig(OutputConfigItem config) private void customDeviceNameComboBox_SelectedValueChanged(object sender, EventArgs e) { var messages = new List>(); - var customDevice = (customDeviceNamesComboBox.SelectedValue as MobiFlightCustomDevice); + var customDevice = (customDeviceNamesComboBox.SelectedValue as ICustomDevice); if (customDevice == null) return; - customDevice.CustomDevice - .MessageTypes + customDevice.MessageTypes .ForEach( (m) => { messages.Add(new ListItem() { Value = m, Label = m.Label }); } ); diff --git a/UI/Panels/OutputWizard/DisplayPanel.cs b/UI/Panels/OutputWizard/DisplayPanel.cs index dac1bfd54..a5ac5fe35 100644 --- a/UI/Panels/OutputWizard/DisplayPanel.cs +++ b/UI/Panels/OutputWizard/DisplayPanel.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Data; +using System.Linq; using System.Windows.Forms; namespace MobiFlight.UI.Panels.OutputWizard @@ -293,7 +294,16 @@ private void displaySerialComboBox_SelectedIndexChanged(object sender, EventArgs } else if (serial.IndexOf(Joystick.SerialPrefix) == 0) { - deviceTypeOptions.Add(new ListItem() { Value = MobiFlightOutput.TYPE, Label = "LED / Output" }); + var joystick = _execManager.GetJoystickManager().GetJoystickBySerial(serial); + if (joystick.GetAvailableOutputDevicesAsListItems().ToList().Find(x=> x.Value is JoystickOutputDevice) != null) + { + deviceTypeOptions.Add(new ListItem() { Value = MobiFlightOutput.TYPE, Label = "LED / Output" }); + } + + if (joystick.GetAvailableOutputDevicesAsListItems().ToList().Find(x => x.Value is JoystickStringOutputDevice) != null) + { + deviceTypeOptions.Add(new ListItem() { Value = MobiFlightCustomDevice.TYPE, Label = "Text" }); + } } else if (serial.IndexOf(MidiBoard.SerialPrefix) == 0) { @@ -445,21 +455,34 @@ private void displayTypeComboBox_SelectedIndexChanged(object sender, EventArgs e private bool InitializeJoystickDisplays(ComboBox cb, string serial) { - Joystick joystick = _execManager.GetJoystickManager().GetJoystickBySerial(serial); + IHidDevice joystick = _execManager.GetJoystickManager().GetJoystickBySerial(serial); displayPinPanel.SetModule(null); displayPinPanel.displayPinBrightnessPanel.Visible = false; displayPinPanel.displayPinBrightnessPanel.Enabled = false; List outputs = new List(); - foreach (var device in joystick.GetAvailableOutputDevicesAsListItems()) + foreach (var device in joystick.GetAvailableOutputDevicesAsListItems()) { outputs.Add(new ListItem() { Value = device.Label, Label = device.Label }); + } - displayPinPanel.WideStyle = true; - displayPinPanel.EnablePWMSelect(false); - displayPinPanel.SetPorts(new List()); - displayPinPanel.SetPins(outputs); + if (outputs.Count>0) + { + displayPinPanel.WideStyle = true; + displayPinPanel.EnablePWMSelect(false); + displayPinPanel.SetPorts(new List()); + displayPinPanel.SetPins(outputs); + } + + var customDevices = new List>(); + foreach (var device in joystick.GetAvailableOutputDevicesAsListItems()) + { + if (device.Value.Type != DeviceType.CustomDevice) continue; + customDevices.Add(new ListItem() { Value = device.Value as ICustomDevice, Label = device.Label }); + } + + customDevicePanel.SetCustomDeviceNames(customDevices); return true; } @@ -560,7 +583,7 @@ private bool InitializeMobiFlightDisplays(ComboBox cb, string serial) List stepper = new List(); List lcdDisplays = new List(); List shiftRegisters = new List(); - List> customDevices = new List>(); + List> customDevices = new List>(); if (module!=null) @@ -596,7 +619,7 @@ private bool InitializeMobiFlightDisplays(ComboBox cb, string serial) break; case DeviceType.CustomDevice: - customDevices.Add(new ListItem() + customDevices.Add(new ListItem() { Value = device as MobiFlightCustomDevice, Label = device.Name diff --git a/examples/mobiflight-in-action-episode-1.mcc b/examples/mobiflight-in-action-episode-1.mcc index b154f864c..784056614 100644 --- a/examples/mobiflight-in-action-episode-1.mcc +++ b/examples/mobiflight-in-action-episode-1.mcc @@ -4,76 +4,146 @@ true Left Green - - - - + + + + + + + + true Right Green - - - - + + + + + + + + true Left Red - - - - + + + + + + + + - true + false Right Red - - - - + + + + + + + + - true + false Top Red - - - - + + + + + + + + - true + false Top Green - - - - + + + + + + + + - true + false Wing Flaps Indicator - - - - + + + + + - + + + + + + + + true + LED + + + + + + + + + + + + + true + ALT + + + + + + + + + + + + + true + VS + + + + + + + + + @@ -81,23 +151,23 @@ true Gear Up Switch - + + true Gear Down Switch - + +