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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CommEx/Serial/Views/ListWindow.xaml.cs b/CommEx/Serial/Views/ListWindow.xaml.cs
new file mode 100644
index 0000000..14dd50c
--- /dev/null
+++ b/CommEx/Serial/Views/ListWindow.xaml.cs
@@ -0,0 +1,35 @@
+using CommEx.Serial.ViewModels;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace CommEx.Serial.Views
+{
+ ///
+ /// ListWindow.xaml の相互作用ロジック
+ ///
+ public partial class ListWindow : Window
+ {
+ public ListWindow()
+ {
+ InitializeComponent();
+ DataContext = new ListViewModel();
+ }
+
+ public ListWindow(ListViewModel viewModel)
+ {
+ InitializeComponent();
+ DataContext = viewModel;
+ }
+ }
+}
diff --git a/CommEx/Serial/Views/SettingWindow.xaml b/CommEx/Serial/Views/SettingWindow.xaml
new file mode 100644
index 0000000..5658864
--- /dev/null
+++ b/CommEx/Serial/Views/SettingWindow.xaml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CommEx/Serial/Views/SettingWindow.xaml.cs b/CommEx/Serial/Views/SettingWindow.xaml.cs
new file mode 100644
index 0000000..79f73c1
--- /dev/null
+++ b/CommEx/Serial/Views/SettingWindow.xaml.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using System.IO.Ports;
+using BveEx.Diagnostics;
+using CommEx.Serial.ViewModels;
+
+namespace CommEx.Serial.Views
+{
+ ///
+ /// SettingWindow.xaml の相互作用ロジック
+ ///
+ public partial class SettingWindow : Window
+ {
+ public SettingWindow()
+ {
+ InitializeComponent();
+ DataContext = new PortViewModel();
+ }
+
+ public SettingWindow(PortViewModel viewModel)
+ {
+ InitializeComponent();
+ DataContext = viewModel;
+ }
+
+ private void DropDownOpened(object sender, EventArgs e)
+ {
+ if (DataContext is PortViewModel viewModel)
+ {
+ viewModel.UpdatePortsCommand.Execute(null);
+ }
+ }
+ }
+}
diff --git a/CommEx/Udp/Main.cs b/CommEx/Udp/Main.cs
index ce54c5f..e859b33 100644
--- a/CommEx/Udp/Main.cs
+++ b/CommEx/Udp/Main.cs
@@ -4,30 +4,40 @@
using System.Text;
using System.Threading.Tasks;
-using AtsEx.PluginHost.Plugins;
-using AtsEx.PluginHost.Plugins.Extensions;
+using BveEx.PluginHost.Plugins;
+using BveEx.PluginHost.Plugins.Extensions;
namespace CommEx.Udp
{
///
/// プラグインの本体
/// Plugin() の第一引数でこのプラグインの仕様を指定
- /// Plugin() の第二引数でこのプラグインが必要とするAtsEX本体の最低バージョンを指定(オプション)
+ /// Plugin() の第二引数でこのプラグインが必要とするBveEx本体の最低バージョンを指定(オプション)
///
[Plugin(PluginType.Extension)]
[Togglable]
internal class Udp : AssemblyPluginBase, ITogglableExtension, IExtension
{
+ #region Plugin Settings
+
///
public override string Title { get; } = nameof(Udp);
///
public override string Description { get; } = "UDP";
+ #endregion
+
+ #region Variables
+
///
/// プラグインの有効・無効状態
///
private bool status = false;
-
+
+ #endregion
+
+ #region Properties
+
///
public bool IsEnabled
{
@@ -35,6 +45,10 @@ public bool IsEnabled
set { status = value; }
}
+ #endregion
+
+ #region Class Functions
+
///
/// プラグインが読み込まれた時に呼ばれる
/// 初期化を実装する
@@ -44,21 +58,20 @@ public Udp(PluginBuilder builder) : base(builder)
{
}
- ///
- /// プラグインが解放されたときに呼ばれる
- /// 後処理を実装する
- ///
- public override void Dispose()
+ #endregion
+
+ #region BveEx Functions
+
+ ///
+ public override void Tick(TimeSpan elapsed)
{
}
- ///
- /// シナリオ読み込み中に毎フレーム呼び出される
- ///
- /// 前回フレームからの経過時間
- public override TickResult Tick(TimeSpan elapsed)
+ ///
+ public override void Dispose()
{
- return new ExtensionTickResult();
}
+
+ #endregion
}
}
diff --git a/ref/.gitignore b/ref/.gitignore
new file mode 100644
index 0000000..7dfd6f3
--- /dev/null
+++ b/ref/.gitignore
@@ -0,0 +1 @@
+Mackoy.IInputDevice.DLL
\ No newline at end of file