diff --git a/CommEx/CommEx.csproj b/CommEx/CommEx.csproj index b0ebe19..d7e2c69 100644 --- a/CommEx/CommEx.csproj +++ b/CommEx/CommEx.csproj @@ -7,6 +7,7 @@ false false true + true @@ -18,4 +19,11 @@ + + + + + + + \ No newline at end of file diff --git a/CommEx/Serial/Bids/Serial.cs b/CommEx/Serial/Bids/Serial.cs new file mode 100644 index 0000000..d55d3eb --- /dev/null +++ b/CommEx/Serial/Bids/Serial.cs @@ -0,0 +1,439 @@ +using BveEx.PluginHost; +using BveEx.Diagnostics; +using BveEx.Extensions.Native; +using System; +using System.Collections.Generic; +using System.Data.SqlTypes; +using System.Diagnostics; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Reflection; +using System.Windows.Input; +using BveEx.PluginHost.Input; +using BveEx.Extensions.Native.Input; +using System.Windows.Media.Animation; +using CommEx.Serial.Common; + +namespace CommEx.Serial.Bids +{ + internal enum Errors + { + /// + /// 原因不明エラー + /// + Unknown, + /// + /// コンバータとBIDSpp.dllとの間の接続が確立されていない + /// + NotConnected, + /// + /// 要求情報コードの数値部が不正 + /// + ErrorInCodeNumber, + /// + /// 要求情報コードの記号部が不正 + /// + ErrorInCodeSymbol, + /// + /// 識別子が不正 + /// + ErrorInIdentifier, + /// + /// 数値変換がオーバーフローした + /// + Overflow, + /// + /// 要求情報コードの数値部に数値以外が混入している + /// + BadFormatInCode, + /// + /// 要求情報コードの数値部もしくは記号部が不正 + /// + BadFormatCode, + /// + /// BVEのウィンドウハンドルを取得できない(キーイベント送信時) + /// + CantGetWindowHandle, + /// + /// (情報なし) + /// + NoInfo1, + /// + /// (情報なし) + /// + NoInfo2, + /// + /// (情報なし) + /// + NoInfo3, + /// + /// 配列の範囲外アクセス + /// + OutOfRange, + /// + /// シナリオが開始されていない + /// + NotStarted, + } + + public class BidsSerial : ISerialControl + { + #region Fields + + private const int version = 300; + + private static bool isAvailable = false; + + private static IBveHacker hacker; + private static INative native; + + /// + /// 改行コード + /// + private string lineBreak = "\r\n"; + + #endregion + + #region Structs + + struct AutoSend + { + + } + + #endregion + + #region Methods + + /// + /// インスタンスの取り込み + /// + public static void UpdateInfos(IBveHacker bveHacker, INative bveNative) + { + hacker = bveHacker; + native = bveNative; + } + + /// + /// 使用可否を設定 + /// + /// 使用可否 + public static void SetStatus(bool status) + { + isAvailable = status; + } + + /// + /// コマンドに応じた返答を生成 + /// + /// コマンド + /// 返答 + private string CreateResponse(string str) + { + string header = str.Substring(0, 2).Trim(); + string body = str.Substring(2).Trim(); + string response = str.Trim() + "X"; + + int num1 = 0; + if (!Convert.ToBoolean(int.TryParse(body.Substring(1), out num1))) + { + if (body.ElementAt(0) != 'I') + { + return CreateError(Errors.BadFormatInCode); + } + } + + switch (body.ElementAt(0)) + { + case 'A': // 状態監視 + return CreateError(Errors.ErrorInCodeSymbol); + case 'I': // 運転情報 + int num2 = 0; + if (!Convert.ToBoolean(int.TryParse(body.Substring(2), out num2))) + { + CreateError(Errors.BadFormatInCode); + } + + switch (body.ElementAt(1)) + { + case 'C': // Spec + switch (num2) + { + case 0: // Bノッチ数 + return response + native.VehicleSpec.BrakeNotches.ToString(); + case 1: // Pノッチ数 + return response + native.VehicleSpec.PowerNotches.ToString(); + case 2: // ATS確認段 + return response + native.VehicleSpec.AtsNotch.ToString(); + case 3: // B67相当段 + return response + native.VehicleSpec.B67Notch.ToString(); + case 4: // 車両編成数 + return response + native.VehicleSpec.Cars.ToString(); + default: + CreateError(Errors.ErrorInCodeNumber); + break; + } + break; + case 'E': // Status + switch (num2) + { + case 0: // 列車位置[m] + return response + native.VehicleState.Location.ToString(); + case 1: // 列車速度[km/h] + return response + native.VehicleState.Speed.ToString(); + case 2: // 現在時刻[ms] + return response + native.VehicleState.Time.TotalMilliseconds.ToString(); + case 3: // BC Pres[kPa] + return response + native.VehicleState.BcPressure.ToString(); + case 4: // MR Pres [kPa] + return response + native.VehicleState.MrPressure.ToString(); + case 5: // ER Pres [kPa] + return response + native.VehicleState.ErPressure.ToString(); + case 6: // BP Pres [kPa] + return response + native.VehicleState.BpPressure.ToString(); + case 7: // SAP Pres [kPa] + return response + native.VehicleState.SapPressure.ToString(); + case 8: // 電流 [A] + return response + native.VehicleState.Current.ToString(); + //case 9: // 電圧 [V](準備工事) + // return response + hacker.Scenario.Vehicle.Instruments.Cab.Handles.PowerNotch.ToString(); + // return response + hacker.Scenario.Vehicle.Instruments.Electricity. + case 10: // 現在時刻(HH)[時] + return response + native.VehicleState.Time.Hours.ToString(); + case 11: // 現在時刻(MM)[分] + return response + native.VehicleState.Time.Minutes.ToString(); + case 12: // 現在時刻(SS)[秒] + return response + native.VehicleState.Time.Seconds.ToString(); + case 13: // 現在時刻(ms)[ミリ秒] + return response + native.VehicleState.Time.Milliseconds.ToString(); + default: + CreateError(Errors.ErrorInCodeNumber); + break; + } + break; + case 'H': // Handle + switch (num2) + { + case 0: // Bノッチ位置 + return response + hacker.Scenario.Vehicle.Instruments.Cab.Handles.BrakeNotch.ToString(); + case 1: // Pノッチ位置 + return response + hacker.Scenario.Vehicle.Instruments.Cab.Handles.PowerNotch.ToString(); + case 2: // レバーサー位置 + return response + hacker.Scenario.Vehicle.Instruments.Cab.Handles.ReverserPosition.ToString(); + case 3: // 定速状態(準備工事) + return response + hacker.Scenario.Vehicle.Instruments.Cab.Handles.ConstantSpeedMode.ToString(); + default: + return CreateError(Errors.ErrorInCodeNumber); + } + case 'P': // Panel + for (int i = 0; i < body.Length; i++) + { + try + { + return response + native.AtsPanelArray[num2].ToString(); + //int val = hacker.Scenario.Vehicle.Instruments.AtsPlugin.PanelArray[num2]; + //return response + val.ToString(); + } + catch (Exception e) + { +#if DEBUG + ErrorDialogInfo errorDialogInfo = new ErrorDialogInfo("エラー:配列の範囲外アクセス", e.Source, e.Message); + ErrorDialog.Show(errorDialogInfo); +#endif + return CreateError(Errors.OutOfRange); + } + } + return CreateError(Errors.ErrorInCodeNumber); + case 'S': // Sound + for (int i = 0; i < body.Length; i++) + { + try + { + return response + native.AtsSoundArray[num2].ToString(); + //int val = hacker.Scenario.Vehicle.Instruments.AtsPlugin.SoundArray[num2]; + //return response + val.ToString(); + } + catch (Exception e) + { +#if DEBUG + ErrorDialogInfo errorDialogInfo = new ErrorDialogInfo("エラー:配列の範囲外アクセス", e.Source, e.Message); + ErrorDialog.Show(errorDialogInfo); +#endif + return CreateError(Errors.OutOfRange); + } + } + return CreateError(Errors.ErrorInCodeNumber); + case 'D': // ドア状態 + switch (num2) + { + case 0: // 全体 + return response + hacker.Scenario.Vehicle.Conductor.Doors.AreAllClosed; + case -1: // 左(準備工事) + case 1: // 右(準備工事) + default: + return CreateError(Errors.ErrorInCodeNumber); + } + default: + return CreateError(Errors.ErrorInCodeSymbol); + } + break; + case 'R': // レバーサー操作要求 + if (-1 <= num1 && num1 <= 1) + { + hacker.Scenario.Vehicle.Instruments.Cab.Handles.BrakeNotch = num1; + return response + 0.ToString(); + } + return CreateError(Errors.ErrorInCodeSymbol); + case 'S': // ワンハンドル操作要求 + //if (num1 > 0) + //{ + // hacker.Scenario.Vehicle.Instruments.Cab.Handles.PowerNotch += num1; + // hacker.Scenario.Vehicle.Instruments.Cab.Handles.BrakeNotch += num1; + // return response + 0.ToString(); + //} + //else if (num1 < 0) + //{ + // hacker.Scenario.Vehicle.Instruments.Cab.Handles.PowerNotch += num1; + // hacker.Scenario.Vehicle.Instruments.Cab.Handles.BrakeNotch += num1; + // return response + 0.ToString(); + //} + //else if (num1 == 0) + //{ + // hacker.Scenario.Vehicle.Instruments.Cab.Handles.PowerNotch = 0; + // hacker.Scenario.Vehicle.Instruments.Cab.Handles.BrakeNotch = 0; + // return response + 0.ToString(); + //} + return CreateError(Errors.ErrorInCodeSymbol); + case 'P': // 力行操作要求 + hacker.Scenario.Vehicle.Instruments.Cab.Handles.PowerNotch += num1; + return response + 0.ToString(); + case 'B': // 制動操作要求 + hacker.Scenario.Vehicle.Instruments.Cab.Handles.BrakeNotch += num1; + return response + 0.ToString(); + case 'K': // キー操作要求 + switch (body.ElementAt(1)) + { + case 'P': // Pless + if (num1 <= (int)AtsKeyName.L) + { + //hacker.InputManager.KeyDown_Invoke(InputEventArgsFactory.AtsKey((AtsKeyName)num1)); + return response + 0.ToString(); + } + return CreateError(Errors.ErrorInCodeNumber); + case 'R': // Release + if (num1 <= (int)AtsKeyName.L) + { + //hacker.InputManager.KeyUp_Invoke(InputEventArgsFactory.AtsKey((AtsKeyName)num1)); + return response + 0.ToString(); + } + return CreateError(Errors.ErrorInCodeNumber); + default: + return CreateError(Errors.ErrorInCodeSymbol); + } + return CreateError(Errors.ErrorInCodeSymbol); + case 'V': // バージョン情報 + return header + version.ToString(); + case 'E': // エラー情報 + return CreateError(Errors.ErrorInCodeSymbol); + case 'H': // 保安装置情報 + return CreateError(Errors.ErrorInCodeSymbol); + default: + return CreateError(Errors.ErrorInCodeSymbol); + } + return null; + } + + private string CreateError(Errors err, string header = "EX") + { +#if DEBUG + Debug.WriteLine(err.ToString()); +#endif + return header + "E" + (int)err; + } + + #endregion + + #region Interface Implementation + + /// + public void PortOpen(SerialPort serialPort) + { + serialPort.NewLine = lineBreak; + serialPort.DataReceived += DataReceived; + } + + /// + public void PortClose(SerialPort serialPort) + { + serialPort.DataReceived -= DataReceived; + } + + #endregion + + #region Event Handlers + + /// + /// シリアルポートの受信時に呼ばれる + /// + /// + /// event args + private void DataReceived(object sender, SerialDataReceivedEventArgs e) + { + SerialPort port = (SerialPort)sender; + string str = ""; + try + { + str = port.ReadLine(); + } + catch (Exception ex) + { +#if DEBUG + ErrorDialog.Show(new ErrorDialogInfo("エラー:シリアル読み込み失敗", ex.Source, ex.Message)); +#endif + return; + } + str = str.Trim(); + Debug.Print("Serial Receive Data" + str); + + if (str.Length < 5) + { + return; + } + + if (str.StartsWith("EX") || str.StartsWith("TR")) + { + string response; + if (native == null) + { + response = CreateError(Errors.NotStarted); + } + else if (!isAvailable) + { + response = CreateError(Errors.NotStarted); + } + else + { + response = CreateResponse(str); + } + + if (response != null) + { + Debug.Print("Serial Send Data" + response); + try + { + port.WriteLine(response); + } + catch (Exception ex) + { + ErrorDialog.Show(new ErrorDialogInfo("ポートが無効状態です。", ex.Source, ex.Message)); + } + } + } + } + + #endregion + } +} diff --git a/CommEx/Serial/Common/BoolToColorConverter.cs b/CommEx/Serial/Common/BoolToColorConverter.cs new file mode 100644 index 0000000..202c9d2 --- /dev/null +++ b/CommEx/Serial/Common/BoolToColorConverter.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using System.Windows.Media; + +namespace CommEx.Serial.Common +{ + /// + /// bool 型を Color に変換するコンバータ + /// + public class BoolToColorConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is bool isOpen) + { + return isOpen ? Brushes.Green : Brushes.Red; + } + + return Brushes.Gray; // デフォルト色 + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is Brush brush) + { + if (brush == Brushes.Green) + { + return true; + } + else if (brush == Brushes.Red) + { + return false; + } + else if (brush == Brushes.Gray) + { + return false; + } + } + + throw new InvalidOperationException("Unsupported conversion"); + } + } +} diff --git a/CommEx/Serial/Common/EnumConverters.cs b/CommEx/Serial/Common/EnumConverters.cs new file mode 100644 index 0000000..e43a863 --- /dev/null +++ b/CommEx/Serial/Common/EnumConverters.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace CommEx.Serial.Common +{ + #region StringConverter + + /// + /// + /// Enum の Value と String を変換するコンバータ + /// String はそのまま Enum の Value として扱われる + /// + public class EnumToStringConverter : EnumConverter + { + public EnumToStringConverter(Type type) : base(type) + { + if (!type.IsEnum) + { + throw new ArgumentException("Type must be an Enum."); + } + } + /// + /// Enum の Value から String を取得 + /// Enum.Value -> string + /// + /// Enum.value + /// string + /// string + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (destinationType == typeof(string) && value != null) + { + return value.ToString(); + } + return base.ConvertTo(context, culture, value, destinationType); + } + + /// + /// String から Enum の Value を取得 + /// string -> Enum.Value + /// + /// string + /// Enum.value + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string stringValue) + { + return Enum.Parse(EnumType, stringValue); + } + return base.ConvertFrom(context, culture, value); + } + } + + #endregion + + #region DescriptionConverter + + /// + /// + /// Enum の Value とその Description を変換するコンバータ + /// + public class EnumToDescriptionConverter : EnumConverter + { + public EnumToDescriptionConverter(Type type) : base(type) + { + if (!type.IsEnum) + { + throw new ArgumentException("Type must be an Enum."); + } + } + + /// + /// Enum の Value から DescriptionAttribute を取得 + /// Enum.Value -> string + /// + /// Enum.value + /// string + /// string + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (destinationType == typeof(string) && value != null) + { + var fieldInfo = value.GetType().GetField(value.ToString()); + var descriptionAttribute = fieldInfo?.GetCustomAttribute(); + if (descriptionAttribute != null) + { + return descriptionAttribute.Description; + } + } + return base.ConvertTo(context, culture, value, destinationType); + } + + /// + /// Enum の DescriptionAttribute から Value を取得 + /// string -> Enum.Value + /// + /// string + /// Enum.value + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string stringValue) + { + foreach (var field in EnumType.GetFields(BindingFlags.Public | BindingFlags.Static)) + { + var descriptionAttribute = field.GetCustomAttribute(); + if (descriptionAttribute != null && descriptionAttribute.Description == stringValue) + { + return Enum.Parse(EnumType, field.Name); + } + } + } + return base.ConvertFrom(context, culture, value); + } + } + + #endregion +} diff --git a/CommEx/Serial/Common/ISerialControl.cs b/CommEx/Serial/Common/ISerialControl.cs new file mode 100644 index 0000000..91eb16f --- /dev/null +++ b/CommEx/Serial/Common/ISerialControl.cs @@ -0,0 +1,86 @@ +using BveEx.PluginHost; +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CommEx.Serial.Common +{ + public interface ISerialControl + { + /// + /// ポートを開ける前に呼ばれる + /// + /// + void PortOpen(SerialPort serialPort); + + /// + /// ポートを閉じた後に呼ばれる + /// + /// + void PortClose(SerialPort serialPort); + + /// + /// シリアルポートの受信時に呼ばれる + /// + /// + /// + //void DataReceived(object sender, SerialDataReceivedEventArgs e); + } + + interface IBveEx + { + /// + /// 全ての BveEx 拡張機能が読み込まれ、BveEx.PluginHost.Plugins.Extensions プロパティが取得可能になると発生 + /// + /// + /// + void AllExtensionsLoaded(object sender, EventArgs e); + + /// + /// シナリオ読み込み + /// + /// + void OnScenarioCreated(ScenarioCreatedEventArgs e); + + /// + /// シナリオ読み込み中に毎フレーム呼び出される + /// + /// + void Tick(TimeSpan elapsed); + + /// + /// シナリオ終了 + /// + /// + void ScenarioClosed(EventArgs e); + } + + /// + /// 入力されたキーを送信する + /// + /// + /// + /// + //[DllImport("user32.dll", SetLastError = true)] + //public extern static void SendInput(int nInputs, Input[] pInputs, int cbsize); + + /// + /// BveHacker と Native の初期化 + /// + /// + /// + /// 引数がnullの時に投げる例外 + //public static void Load(IBveHacker bveHacker, INative native) + //{ + // native = native ?? throw new ArgumentNullException(nameof(native)); + // bveHacker = bveHacker ?? throw new ArgumentNullException(nameof(bveHacker)); + //} + //public static void Load(IBveHacker bveHacker) + //{ + // bveHacker = bveHacker ?? throw new ArgumentNullException(nameof(bveHacker)); + //} + +} diff --git a/CommEx/Serial/Common/Loopback.cs b/CommEx/Serial/Common/Loopback.cs new file mode 100644 index 0000000..6979f51 --- /dev/null +++ b/CommEx/Serial/Common/Loopback.cs @@ -0,0 +1,50 @@ +using BveEx.Diagnostics; +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CommEx.Serial.Common +{ + /// + /// シリアルループバック + /// + internal class Loopback : ISerialControl + { + /// + public void PortOpen(SerialPort serialPort) + { + serialPort.DataReceived += DataReceived; + } + + /// + /// シリアル受信時のイベントハンドラ + /// 送られてきた情報をそっくりそのまま返す + /// + /// 受信したポートの + /// イベントデータ + private void DataReceived(object sender, SerialDataReceivedEventArgs e) + { + try + { + SerialPort serialPort = (SerialPort)sender; + string str = serialPort.ReadLine(); + serialPort.WriteLine(str); + } + catch (Exception ex) + { +#if DEBUG + ErrorDialog.Show(new ErrorDialogInfo("通信エラー", ex.Source, ex.Message)); +#endif + } + } + + /// + public void PortClose(SerialPort serialPort) + { + serialPort.DataReceived -= DataReceived; + } + } +} diff --git a/CommEx/Serial/Common/NewLines.cs b/CommEx/Serial/Common/NewLines.cs new file mode 100644 index 0000000..0bb6ac4 --- /dev/null +++ b/CommEx/Serial/Common/NewLines.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using System.Drawing; + +namespace CommEx.Serial.Common +{ + /// + /// 改行文字 + /// + [TypeConverter(typeof(EnumToStringConverter))] + public enum NewLines + { + [Description("\n")] + LF, + [Description("\r\n")] + CRLF, + [Description("\r")] + CR + } +} diff --git a/CommEx/Serial/Common/Sample.Setting.xml b/CommEx/Serial/Common/Sample.Setting.xml new file mode 100644 index 0000000..ec39dd7 --- /dev/null +++ b/CommEx/Serial/Common/Sample.Setting.xml @@ -0,0 +1,16 @@ + + + + + 115200 + 8 + false + None + CRLF + None + One + true + Loopback + + + \ No newline at end of file diff --git a/CommEx/Serial/Common/SaveSettings.cs b/CommEx/Serial/Common/SaveSettings.cs new file mode 100644 index 0000000..c941486 --- /dev/null +++ b/CommEx/Serial/Common/SaveSettings.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; +using CommEx.Serial.ViewModels; +using System.Diagnostics; + +namespace CommEx.Serial.Common +{ + class SaveSettings + { + #region Static Methods + + /// + /// 保存先のファイルパスを動的に取得 + /// このdllのファイルパス - ".dll" + ".Settings.xml" + /// + /// 保存先のファイルパス + /// + private static string GetSettingsFilePath() + { + string assemblyLocation = System.Reflection.Assembly.GetExecutingAssembly().Location; + string directory = Path.GetDirectoryName(assemblyLocation) ?? throw new InvalidOperationException("アセンブリのディレクトリを取得できません。"); + string fileName = Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetExecutingAssembly().Location); + return Path.Combine(directory, $"{fileName}.Settings.xml"); + } + + /// + /// のプロパティをXMLファイルに保存 + /// + /// 保存する インスタンス + public static void Save(ListViewModel viewModel) + { + try + { + string filePath = GetSettingsFilePath(); + + using (var writer = new StreamWriter(filePath)) + { + var serializer = new XmlSerializer(typeof(ListViewModel)); + serializer.Serialize(writer, viewModel); + } + } + catch (Exception ex) + { + Debug.Print($"Error saving settings: {ex.Message}"); + } + } + + /// + /// XMLファイルから のプロパティを読み込み + /// + /// 読み込まれた インスタンス + public static ListViewModel Load() + { + try + { + string filePath = GetSettingsFilePath(); + + if (!File.Exists(filePath)) return new ListViewModel(); + + using (var reader = new StreamReader(filePath)) + { + var serializer = new XmlSerializer(typeof(ListViewModel)); + var serializableObject = (ListViewModel)serializer.Deserialize(reader); + return serializableObject; + } + } + catch (Exception ex) + { + Debug.Print($"Error loading settings: {ex.Message}"); + return new ListViewModel(); + } + } + + #endregion + } +} diff --git a/CommEx/Serial/Common/SerialControl.cs b/CommEx/Serial/Common/SerialControl.cs new file mode 100644 index 0000000..fe67bb1 --- /dev/null +++ b/CommEx/Serial/Common/SerialControl.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace CommEx.Serial.Common +{ + /// + /// シリアル通信の制御器 + /// + [TypeConverter(typeof(EnumToDescriptionConverter))] + public enum Controller + { + [Description("Loopback")] + Loopback, + [Description("BIDS互換")] + Bids, + } + + /// + /// + /// Controller とその ISerialControl を変換するコンバータ + /// + public class ControllerToISerialControlConverter : EnumToDescriptionConverter + { + public ControllerToISerialControlConverter(Type type) : base(type) + { + if (type != typeof(Controller)) + { + throw new ArgumentException($"Type must be {typeof(Controller)}."); + } + } + + /// + /// の value に応じたクラス + /// + private static readonly Dictionary ClassDictionary = new Dictionary + { + // { , typeof(<制御クラス>) }, + { Controller.Loopback, typeof(Loopback) }, + { Controller.Bids, typeof(Bids.BidsSerial) } + }; + + /// + /// Enum の Value から ISerialControl のインスタンスを取得 + /// Enum.Value -> ISerialControl + /// + /// Enum.value + /// ISerialControl + /// ISerialControl + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (value == null) + { + Debug.WriteLine("value が null です。"); + return null; + } + + if (destinationType == typeof(ISerialControl)) + { + + if (ClassDictionary.TryGetValue((Controller)value, out Type type)) + { + return (ISerialControl)Activator.CreateInstance(type); + } + else + { + Debug.WriteLine("クラスが登録されていません。"); + return null; + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + /// + /// ISerialControl のインスタンスから Enum の Value を取得 + /// ISerialControl -> Enum.Value + /// + /// ISerialControl + /// Enum.value + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value == null) + { + Debug.WriteLine("value が null です。"); + return null; + } + + if (value is ISerialControl) + { + // controllerの型をSerialControl.Controllerに変換 + // 現在のcontrollerインスタンスがClassDictionaryのどのキーに対応するかを探す + foreach (var item in ClassDictionary) + { + if (item.Value == value.GetType()) + { + return item.Key; + } + } + Debug.WriteLine("クラスが登録されていません。"); + return null; + } + + return base.ConvertFrom(context, culture, value); + } + } + +} diff --git a/CommEx/Serial/Main.cs b/CommEx/Serial/Main.cs index 18d3516..6672f8a 100644 --- a/CommEx/Serial/Main.cs +++ b/CommEx/Serial/Main.cs @@ -1,33 +1,91 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; - -using AtsEx.PluginHost.Plugins; -using AtsEx.PluginHost.Plugins.Extensions; +using System.Windows.Forms; +using System.Xml.Linq; +using BveEx.Extensions.ContextMenuHacker; +using BveEx.PluginHost; +//using BveEx.PluginHost.Scripting; +using BveEx.PluginHost.Plugins; +using BveEx.PluginHost.Plugins.Extensions; +using BveTypes.ClassWrappers; +using BveEx.Extensions.Native; +using BveEx.Diagnostics; +using CommEx.Serial.ViewModels; +using CommEx.Serial.Views; +using CommEx.Serial.Common; +using CommEx.Serial.Bids; namespace CommEx.Serial { /// /// プラグインの本体 /// Plugin() の第一引数でこのプラグインの仕様を指定 - /// Plugin() の第二引数でこのプラグインが必要とするAtsEX本体の最低バージョンを指定(オプション) + /// Plugin() の第二引数でこのプラグインが必要とするBveEx本体の最低バージョンを指定(オプション) /// [Plugin(PluginType.Extension)] [Togglable] internal class Serial : AssemblyPluginBase, ITogglableExtension, IExtension { + #region Plugin Settings + /// public override string Title { get; } = nameof(Serial); /// public override string Description { get; } = "シリアル通信"; + #endregion + + #region Variables + /// /// プラグインの有効・無効状態 /// private bool status = true; + /// + /// シナリオ + /// + private Scenario scenario; + + /// + /// BveHacker + /// + private IBveHacker bveHacker; + + /// + /// Native + /// + private INative native; + + /// + /// 右クリックメニュー操作用 + /// ContextMenuHacker + /// + private IContextMenuHacker cmx; + + /// + /// 右クリックメニューの設定ボタン + /// + private ToolStripMenuItem setting; + + /// + /// ウィンドウ + /// + private readonly ListWindow window; + + /// + /// ビューモデル + /// + protected ListViewModel viewModel; + + #endregion + + #region Properties + /// public bool IsEnabled { @@ -35,6 +93,10 @@ public bool IsEnabled set { status = value; } } + #endregion + + #region Class Functions + /// /// プラグインが読み込まれた時に呼ばれる /// 初期化を実装する @@ -42,34 +104,149 @@ public bool IsEnabled /// public Serial(PluginBuilder builder) : base(builder) { - Extensions.AllExtensionsLoaded += Extensions_AllExtensionsLoaded; + Debug.Listeners.Add(new TextWriterTraceListener(Console.Out)); + Debug.AutoFlush = true; + + Extensions.AllExtensionsLoaded += AllExtensionsLoaded; + + BveHacker.ScenarioCreated += OnScenarioCreated; + BveHacker.ScenarioClosed += ScenarioClosed; + + viewModel = SaveSettings.Load(); + //viewModel = new ListViewModel(); + + window = new ListWindow(viewModel); + window.Closing += WindowClosing; + window.Hide(); + + foreach (var item in viewModel.PortViewModels) + { + item.CheckAutoConnect(); + } + } + + #endregion + + #region BveEx Functions + + /// + public override void Dispose() + { + SaveSettings.Save(viewModel); + + Extensions.AllExtensionsLoaded -= AllExtensionsLoaded; + BveHacker.ScenarioCreated -= OnScenarioCreated; + BveHacker.ScenarioClosed -= ScenarioClosed; + window.Closing -= WindowClosing; + window.Close(); + } + + /// + public override void Tick(TimeSpan elapsed) + { + BidsSerial.SetStatus(true); } + #endregion + + #region BveEx Event Handlers + /// - /// 全ての AtsEX 拡張機能が読み込まれ、AtsEx.PluginHost.Plugins.Extensions プロパティが取得可能になると発生 + /// 全ての BveEx 拡張機能が読み込まれ、BveEx.PluginHost.Plugins.Extensions プロパティが取得可能になると発生 /// /// /// - private void Extensions_AllExtensionsLoaded(object sender, EventArgs e) + private void AllExtensionsLoaded(object sender, EventArgs e) { + bveHacker = BveHacker; + cmx = Extensions.GetExtension(); + native = Extensions.GetExtension(); + + setting = cmx.AddCheckableMenuItem("シリアル通信設定", MenuItemCheckedChanged, ContextMenuItemType.CoreAndExtensions); + native.Started += NativeStarted; + +#if DEBUG + setting.Checked = true; +#else + setting.Checked = false; +#endif + + BidsSerial.UpdateInfos(bveHacker, native); } /// - /// プラグインが解放されたときに呼ばれる - /// 後処理を実装する + /// シナリオ読み込み /// - public override void Dispose() + /// + private void OnScenarioCreated(ScenarioCreatedEventArgs e) + { + scenario = e.Scenario; + } + + /// + /// シナリオ終了 + /// + /// + /// + private void ScenarioClosed(EventArgs e) { - Extensions.AllExtensionsLoaded -= Extensions_AllExtensionsLoaded; + BidsSerial.SetStatus(false); } + #endregion + + #region Event Handlers + /// - /// シナリオ読み込み中に毎フレーム呼び出される + /// /// - /// 前回フレームからの経過時間 - public override TickResult Tick(TimeSpan elapsed) + /// + /// + /// + private void NativeStarted(object sender, StartedEventArgs e) { - return new ExtensionTickResult(); +#if DEBUG + ErrorDialog.Show(new ErrorDialogInfo("started", "sender", "message")); +#else + throw new NotImplementedException(); +#endif } + + /// + /// 右クリックメニューのクリックイベントハンドラ + /// + /// + /// + private void MenuItemCheckedChanged(object sender, EventArgs e) + { + if (setting != null) + { + if (setting.Checked) + { + window.Show(); + } + else + { + window.Hide(); + } + } + } + + /// + /// リストウィンドウの閉じるボタンのクリックイベントハンドラ + /// + /// ListWindow + /// キャンセルできるイベントのデータ + private void WindowClosing(object sender, System.ComponentModel.CancelEventArgs e) + { + if (sender is ListWindow window) + { + e.Cancel = true; + window.Hide(); + setting.Checked = false; + } + } + + #endregion } } diff --git a/CommEx/Serial/ViewModels/BaseViewModel.cs b/CommEx/Serial/ViewModels/BaseViewModel.cs new file mode 100644 index 0000000..43b60cb --- /dev/null +++ b/CommEx/Serial/ViewModels/BaseViewModel.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace CommEx.Serial.ViewModels +{ + /// + /// ViewModel の基底クラス + /// + public abstract class BaseViewModel : INotifyPropertyChanged + { + #region Interface Implementation + + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// View に値の変更を通知 + /// + /// 呼び出し元のプロパティ名(自動取得) + protected void RaisePropertyChanged([CallerMemberName] string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + #endregion + } + + /// + /// コマンドのリレー用クラス + /// + public class RelayCommand : ICommand + { + private readonly Action _execute; + private readonly Func _canExecute; + + public RelayCommand(Action execute, Func canExecute = null) + { + _execute = execute; + _canExecute = canExecute; + } + + public event EventHandler CanExecuteChanged; + + public bool CanExecute(object parameter) => _canExecute == null || _canExecute(); + + public void Execute(object parameter) => _execute(); + + public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } +} diff --git a/CommEx/Serial/ViewModels/ListViewModel.cs b/CommEx/Serial/ViewModels/ListViewModel.cs new file mode 100644 index 0000000..eb95927 --- /dev/null +++ b/CommEx/Serial/ViewModels/ListViewModel.cs @@ -0,0 +1,190 @@ +using BveEx.Diagnostics; +using CommEx.Serial.Bids; +using CommEx.Serial.Common; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.IO.Ports; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; +using System.Windows.Input; +using CommEx.Serial.Views; + +namespace CommEx.Serial.ViewModels +{ + [XmlRoot("Settings")] + public class ListViewModel: BaseViewModel + { + #region Fields + + /// + /// 子要素の ViewModel + /// + private ObservableCollection portViewModels; + + /// + /// 現在選択中の ViewModel + /// + private PortViewModel selectedPort; + + #endregion + + #region Properties + + /// + /// 子要素の ViewModel + /// + [XmlArray("PortSettings")] + [XmlArrayItem("Port")] + public ObservableCollection PortViewModels + { + get { return portViewModels; } + set + { + portViewModels = value; + RaisePropertyChanged(); + } + } + + /// + /// 現在選択中の ViewModel + /// + [XmlIgnore] + public PortViewModel SelectedPort + { + get { return selectedPort; } + set + { + selectedPort = value; + RaisePropertyChanged(); + RaisePropertyChanged("IsButtonAvailable"); + UpdateCommands(); + } + } + + /// + /// ボタンが利用可能か + /// + [XmlIgnore] + public bool IsButtonAvailable => SelectedPort != null; + + /// + /// 追加コマンド + /// + [XmlIgnore] + public ICommand AddItemCommand { get; } + + /// + /// 設定コマンド + /// + [XmlIgnore] + public ICommand SettingCommand { get; } + + /// + /// 削除コマンド + /// + [XmlIgnore] + public ICommand DeleteItemCommand { get; } + + #endregion + + #region Methods + + /// + /// ListViewModel をデフォルト値で初期化 + /// + public ListViewModel() + { + portViewModels = new ObservableCollection(); + + AddItemCommand = new RelayCommand(AddItem); + SettingCommand = new RelayCommand(ShowSettingWindow, IsSelectedItemNotNull); + DeleteItemCommand = new RelayCommand(ClearSelectedItem, IsSelectedItemNotNull); + } + + /// + /// ListViewModel を で初期化 + /// + public ListViewModel(PortViewModel viewModel) + { + portViewModels = new ObservableCollection + { + viewModel + }; + + AddItemCommand = new RelayCommand(AddItem); + SettingCommand = new RelayCommand(ShowSettingWindow, IsSelectedItemNotNull); + DeleteItemCommand = new RelayCommand(ClearSelectedItem, IsSelectedItemNotNull); + } + + /// + /// ListViewModel を で初期化 + /// + public ListViewModel(Collection viewModels) + { + portViewModels = new ObservableCollection(viewModels); + + AddItemCommand = new RelayCommand(AddItem); + SettingCommand = new RelayCommand(ShowSettingWindow, IsSelectedItemNotNull); + DeleteItemCommand = new RelayCommand(ClearSelectedItem, IsSelectedItemNotNull); + } + + /// + /// コマンドの実行可否を更新 + /// + private void UpdateCommands() + { + (SettingCommand as RelayCommand).RaiseCanExecuteChanged(); + (DeleteItemCommand as RelayCommand).RaiseCanExecuteChanged(); + } + + /// + /// 選択された内容がnullか判定 + /// + /// 選択された内容がnullか否か + private bool IsSelectedItemNotNull() => SelectedPort != null; + + /// + /// ポート設定を追加 + /// + private void AddItem() + { + var index = portViewModels.IndexOf(SelectedPort); + portViewModels.Add(new PortViewModel()); + if (index < 0) index = portViewModels.Count - 1; + selectedPort = portViewModels.ElementAt(index); + } + + /// + /// 設定ウィンドウを表示 + /// + private void ShowSettingWindow() + { + // 設定ウィンドウをモーダルで表示する + SelectedPort.ShowSettingWindow(true); + } + + /// + /// ポート設定を削除 + /// + private void ClearSelectedItem() + { + if (SelectedPort != null) + { + SelectedPort.Dispose(); + PortViewModels.Remove(SelectedPort); + SelectedPort = null; + RaisePropertyChanged(nameof(SelectedPort)); + } + } + + #endregion + } +} diff --git a/CommEx/Serial/ViewModels/PortViewModel.cs b/CommEx/Serial/ViewModels/PortViewModel.cs new file mode 100644 index 0000000..be99a8e --- /dev/null +++ b/CommEx/Serial/ViewModels/PortViewModel.cs @@ -0,0 +1,655 @@ +using System; +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing.Imaging; +using System.IO.Ports; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using System.Collections.ObjectModel; +using System.IO; +using System.Windows.Input; +using BveEx.Diagnostics; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; +using CommEx.Serial.Common; +using System.Xml.Serialization; +using CommEx.Serial.Bids; +using CommEx.Serial.Views; + +namespace CommEx.Serial.ViewModels +{ + [Serializable] + [XmlRoot("Port")] + public class PortViewModel : BaseViewModel + { + #region Fields + + /// + /// シリアルポート + /// + private SerialPort port; + + /// + /// シリアルの制御 + /// + private ISerialControl controller; + + /// + /// 自動接続の設定 + /// + private bool isAutoConnent; + + /// + /// 表示用テキスト + /// + private string message = ""; + + /// + /// Enum と Description のコンバータ + /// + private static readonly EnumToDescriptionConverter e2dconv = new EnumToDescriptionConverter(typeof(NewLines)); + + /// + /// Controller と ISerialControl のコンバータ + /// + private static readonly ControllerToISerialControlConverter c2iconv = new ControllerToISerialControlConverter(typeof(Controller)); + + #endregion + + #region Properties + + /// + /// ボーレート[Baud] + /// + [Browsable(true)] + //[DefaultValue(9600)] + [MonitoringDescription("BaudRate")] + [XmlElement("BaudRate")] + public int BaudRate + { + get + { + return port.BaudRate; + } + set + { + port.BaudRate = value; + RaisePropertyChanged(); + } + } + + /// + /// データビット[bit] + /// + [Browsable(true)] + //[DefaultValue(8)] + [MonitoringDescription("DataBits")] + [XmlElement("DataBits")] + public int DataBits + { + get + { + return port.DataBits; + } + set + { + port.DataBits = value; + RaisePropertyChanged(); + } + } + + /// + /// DTR 有効/無効 + /// + [Browsable(true)] + //[DefaultValue(false)] + [MonitoringDescription("DtrEnable")] + [XmlElement("DtrEnable")] + public bool DtrEnable + { + get + { + return port.DtrEnable; + } + set + { + port.DtrEnable = value; + RaisePropertyChanged(); + } + } + + /// + /// テキストのエンコーディング + /// + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [MonitoringDescription("Encoding")] + [XmlIgnore] + public Encoding Encoding + { + get + { + return port.Encoding; + } + set + { + port.Encoding = value; + RaisePropertyChanged(); + } + } + + /// + /// フロー制御 + /// + [Browsable(true)] + //[DefaultValue(Handshake.None)] + [MonitoringDescription("Handshake")] + [XmlElement("Handshake")] + public Handshake Handshake + { + get + { + return port.Handshake; + } + set + { + port.Handshake = value; + RaisePropertyChanged(); + } + } + + /// + /// ポートの状態 + /// + [Browsable(true)] + //[DefaultValue(false)] + [MonitoringDescription("IsOpen")] + [XmlIgnore] + public bool IsOpen + { + get + { + return port.IsOpen; + } + } + + /// + /// ポートの状態 + /// + [Browsable(false)] + [XmlIgnore] + public bool IsClosed => !IsOpen; + + /// + /// 改行文字 + /// + [Browsable(true)] + //[DefaultValue(NewLines.LF)] + [MonitoringDescription("NewLine")] + [XmlElement("NewLine")] + public NewLines NewLine + { + get + { + return (NewLines)e2dconv.ConvertFrom(port.NewLine); + } + set + { + port.NewLine = (string)e2dconv.ConvertTo(value, typeof(string)); + RaisePropertyChanged(); + } + } + + /// + /// パリティ + /// + [Browsable(true)] + //[DefaultValue(Parity.None)] + [MonitoringDescription("Parity")] + [XmlElement("Parity")] + public Parity Parity + { + get + { + return port.Parity; + } + set + { + port.Parity = value; + RaisePropertyChanged(); + } + } + + /// + /// ポート名 + /// + [Browsable(true)] + //[DefaultValue("COM1")] + [MonitoringDescription("PortName")] + [XmlAttribute("PortName")] + public string PortName + { + get + { + return port.PortName; + } + set + { + port.PortName = value; + RaisePropertyChanged(); + } + } + + /// + /// ストップビット[bit] + /// + [Browsable(true)] + //[DefaultValue(StopBits.One)] + [MonitoringDescription("StopBits")] + [XmlElement("StopBits")] + public StopBits StopBits + { + get + { + return port.StopBits; + } + set + { + port.StopBits = value; + RaisePropertyChanged(); + } + } + + /// + /// 自動接続設定 + /// + [Browsable(true)] + //[DefaultValue(false)] + [MonitoringDescription("IsAutoConnent")] + [XmlElement("IsAutoConnent")] + public bool IsAutoConnent + { + get + { + return isAutoConnent; + } + set + { + isAutoConnent = value; + RaisePropertyChanged(); + } + } + + /// + /// 表示用テキスト + /// + [DefaultValue("")] + [XmlIgnore] + public string Message + { + get { return message; } + set { message = value; } + } + + /// + /// ボタン用テキスト + /// + [DefaultValue("Open")] + [XmlIgnore] + public string OperationString + { + get + { + if (IsOpen) + { + return "Close"; + } + else + { + return "Open"; + } + } + } + + /// + /// 現在選択中の Controller + /// + [XmlElement] + public Controller? Controller + { + get + { + return (Controller)c2iconv.ConvertFrom(controller); + } + set + { + controller = (ISerialControl)c2iconv.ConvertTo(value, typeof(ISerialControl)); + RaisePropertyChanged(); + } + } + + /// + /// 使用可能なポートの選択肢リスト + /// + [XmlIgnore] + public static ObservableCollection AvailablePorts { get; } = new ObservableCollection(); + + /// + /// ボーレートの選択肢リスト + /// + [XmlIgnore] + public static ObservableCollection BaudRates { get; } = new ObservableCollection { 9600, 19200, 38400, 57600, 115200 }; + + /// + /// データビットの選択肢リスト + /// + [XmlIgnore] + public static ObservableCollection DataBitsOptions { get; } = new ObservableCollection { 5, 6, 7, 8 }; + + /// + /// ストップビットの選択肢リスト + /// + [XmlIgnore] + public static ObservableCollection StopBitsOptions { get; } = new ObservableCollection { StopBits.One, StopBits.OnePointFive, StopBits.Two }; + + /// + /// パリティの選択肢リスト + /// + [XmlIgnore] + public static ObservableCollection ParityOptions { get; } = new ObservableCollection(Enum.GetValues(typeof(Parity)) as Parity[]); + + /// + /// フロー制御の選択肢リスト + /// + [XmlIgnore] + public static ObservableCollection HandshakeOptions { get; } = new ObservableCollection(Enum.GetValues(typeof(Handshake)) as Handshake[]); + + /// + /// 改行文字の選択肢リスト + /// + [XmlIgnore] + public static ObservableCollection NewLineOptions { get; } = new ObservableCollection(Enum.GetValues(typeof(NewLines)) as NewLines[]); + + /// + /// コントローラーの選択肢リスト + /// + [XmlIgnore] + public static ObservableCollection ControllerOptions { get; } = new ObservableCollection(Enum.GetValues(typeof(Controller)) as Controller[]); + + /// + /// ポートリストのアップデートコマンド + /// + [XmlIgnore] + public ICommand UpdatePortsCommand { get; } + + /// + /// ポートの開閉コマンド + /// + [XmlIgnore] + public ICommand OpenClosePortCommand { get; } + + #endregion + + #region Methods + + /// + /// 初期化処理 + /// + private void Initialize(SerialPort serialPort = null, ISerialControl serialControl = null) + { + if (serialPort == null) + { + port = new SerialPort(); + } + else + { + port = serialPort; + } + if (serialControl == null) + { + controller = (ISerialControl)c2iconv.ConvertTo(Enum.GetValues(typeof(Controller)).Cast().Min(), typeof(ISerialControl)); + } + else + { + controller = serialControl; + } + + UpdatePorts(); + } + + /// + /// 自動接続処理 + /// + public void CheckAutoConnect() + { + if (isAutoConnent && IsClosed) + { + if (AvailablePorts.Contains(PortName)) + { + OpenClosePort(); + } + else + { + ErrorDialog.Show(new ErrorDialogInfo("自動接続対象のポートが存在しません。", null, $"ポート {PortName} が見つかりません。")); + Message = "ポートが見つかりません。"; + } + } + } + + /// + /// ViewModel をデフォルト値で初期化 + /// + public PortViewModel() + { + UpdatePortsCommand = new RelayCommand(UpdatePorts); + OpenClosePortCommand = new RelayCommand(OpenClosePort, CanOpenClosePort); + + Initialize(); + } + + /// + /// ViewModel を値を指定して初期化 + /// + /// ポート名 + /// ボーレート + /// パリティ + /// データビット + /// ストップビット + public PortViewModel(string portName = "COM0", int baudRate = 115200, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One) + { + UpdatePortsCommand = new RelayCommand(UpdatePorts); + OpenClosePortCommand = new RelayCommand(OpenClosePort, CanOpenClosePort); + + Initialize(new SerialPort(portName, baudRate, parity, dataBits, stopBits)); + } + + /// + /// ViewModel を で初期化 + /// + /// 初期化に使用する + public PortViewModel(SerialPort serialPort) + { + UpdatePortsCommand = new RelayCommand(UpdatePorts); + OpenClosePortCommand = new RelayCommand(OpenClosePort, CanOpenClosePort); + + Initialize(serialPort); + } + + /// + /// ISerialControl を指定して初期化 + /// + /// シリアル制御 + /// ポート名 + /// ボーレート + /// パリティ + /// データビット + /// ストップビット + public PortViewModel(ISerialControl serialControls, string portName = "COM0", int baudRate = 115200, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One) + { + port = new SerialPort(portName, baudRate, parity, dataBits, stopBits); + controller = serialControls; + + UpdatePortsCommand = new RelayCommand(UpdatePorts); + OpenClosePortCommand = new RelayCommand(OpenClosePort, CanOpenClosePort); + + //UpdatePorts(); + } + + /// + /// ViewModel をデフォルト値で ISerialControl を指定して初期化 + /// + public PortViewModel(ISerialControl serialControls) + { + port = new SerialPort(); + controller = serialControls; + + UpdatePortsCommand = new RelayCommand(UpdatePorts); + OpenClosePortCommand = new RelayCommand(OpenClosePort, CanOpenClosePort); + + //UpdatePorts(); + } + + /// + /// 設定ウィンドウを表示 + /// + /// モーダルとして表示するかどうか + /// モーダルとして開いたウィンドウが閉じられたか否か + public bool? ShowSettingWindow(bool isModal = false) + { + SettingWindow settingWindow = new SettingWindow(this); + if (isModal) + { + return settingWindow.ShowDialog(); + } + else + { + settingWindow.Show(); + } + return null; + } + + /// + /// リソースの解放 + /// + public void Dispose() + { + if (port != null) + { + if (port.IsOpen) + { + port.Close(); + } + port.Dispose(); + } + } + + /// + /// ポートの開閉が可能か否か判定 + /// + /// ポート操作可否 + private bool CanOpenClosePort() => port != null && !string.IsNullOrEmpty(PortName); + + /// + /// ポートリストのアップデート + /// + private static void UpdatePorts() + { + AvailablePorts.Clear(); + foreach (var port in SerialPort.GetPortNames()) + { + AvailablePorts.Add(port); + } + } + + /// + /// ポートの開閉 + /// + private void OpenClosePort() + { + if (IsClosed) + { + // ポートを開ける + try + { + controller.PortOpen(port); + port.Open(); + + if (IsOpen) + { + message = "Port: Open"; + } + else + { + message = "Port: Close"; + } + } + catch (UnauthorizedAccessException ex) + { + ErrorDialog.Show(new ErrorDialogInfo("ポートが既に使われています。", ex.Source, ex.Message)); + Message = "ポートが既に使われています。"; + } + catch (ArgumentOutOfRangeException ex) + { + ErrorDialog.Show(new ErrorDialogInfo("ポートの設定が無効です。", ex.Source, ex.Message)); + Message = "ポートの設定が無効です。"; + } + catch (ArgumentException ex) + { + ErrorDialog.Show(new ErrorDialogInfo("このポートはサポートされていません。", ex.Source, ex.Message)); + Message = "このポートはサポートされていません。"; + } + catch (IOException ex) + { + ErrorDialog.Show(new ErrorDialogInfo("ポートが無効状態です。", ex.Source, ex.Message)); + Message = "ポートが無効状態です。"; + } + catch (Exception ex) + { + ErrorDialog.Show(new ErrorDialogInfo("ポートを開いたときにエラーが発生しました。", ex.Source, ex.Message)); + Message = "ポートを開いたときにエラーが発生しました。"; + } + } + else + { + // ポートを閉じる + try + { + port.Close(); + controller.PortClose(port); + + if (IsOpen) + { + message = "Port: Open"; + } + else + { + message = "Port: Close"; + } + } + catch (IOException ex) + { + ErrorDialog.Show(new ErrorDialogInfo("ポートが無効状態です。", ex.Source, ex.Message)); + Message = "ポートが無効状態です。"; + } + catch (Exception ex) + { + ErrorDialog.Show(new ErrorDialogInfo("ポートを閉じたときにエラーが発生しました。", ex.Source, ex.Message)); + Message = "ポートを閉じたときにエラーが発生しました。"; + } + } + + // プロパティの変更通知 + RaisePropertyChanged("IsOpen"); + RaisePropertyChanged("IsClosed"); + RaisePropertyChanged("OperationString"); + RaisePropertyChanged("Message"); + } + + #endregion + } +} diff --git a/CommEx/Serial/Views/ListWindow.xaml b/CommEx/Serial/Views/ListWindow.xaml new file mode 100644 index 0000000..d018932 --- /dev/null +++ b/CommEx/Serial/Views/ListWindow.xaml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + +