前回の記事「電車でGO!新幹線専用コントローラーをパソコンにつなぐ(通信フォーマット編)」がデバイス固有の大事なところのすべてですが、C#で使いやすいようにするクラスを書いてみたので、載せておきます。
簡単な使い方
時速100キロで走行中の表示をさせる例
Program.cs
using TAITO_DENSYA_CON_T02_ctrl;
namespace TAITO_DENSYA_CON_console
{
public class TAITO_DENSYA_console
{
public static void Main()
{
using (TAITO_DENSYA_CON_T02_USBctrl ctrl = new())
{
ctrl.ATCSpeed = 100; // ATC100 km/h
ctrl.Speed = 100; // 100 km/h
ctrl.DoorLamp = true;// 戸閉灯点灯
Task.Run(() => ctrl.Execute());// 上記の設定を反映
Console.ReadKey();
}
}
}
}
というわけで以下ソース
TAITO_DENSYA_CON_T02_ctrl.cs
// 電車でGO!新幹線専用コントローラー用ライブラリのようなもの
// 2021.11.21
// v0.1
// Copyright と∃ しまみじめ(@admiralhetare)
// 本コードは自己責任にてご自由にお使いください。無保証です。
//
// 更新履歴
// 2021.11.21 v0.1 とりあえず書けた
using System;
using LibUsbDotNet;
using LibUsbDotNet.Main;
namespace TAITO_DENSYA_CON_T02_ctrl
{
public enum SpeedDispModes
{
On,
Off,
/// <summary>
/// ---
/// </summary>
Hyphen,
/// <summary>
/// ‾‾‾
/// </summary>
Overbar,
}
/// <summary>
/// ReaderWriterLockSlimをusingで使えるようにしたもの
/// </summary>
public struct WriteLock : IDisposable
{
ReaderWriterLockSlim obj;
public WriteLock(ReaderWriterLockSlim lockObj)
{
obj = lockObj;
obj.EnterWriteLock();
}
public void Dispose()
{
obj.ExitWriteLock();
}
}
/// <summary>
/// ReaderWriterLockSlimをusingで使えるようにしたもの
/// </summary>
public struct ReadLock : IDisposable
{
ReaderWriterLockSlim obj;
public ReadLock(ReaderWriterLockSlim lockObj)
{
obj = lockObj;
obj.EnterReadLock();
}
public void Dispose()
{
obj.ExitReadLock();
}
}
/// <summary>
/// ReaderWriterLockSlimをusingで使えるようにしたもの
/// </summary>
public struct UpgradeableReadLock : IDisposable
{
ReaderWriterLockSlim obj;
public UpgradeableReadLock(ReaderWriterLockSlim lockObj)
{
obj = lockObj;
obj.EnterUpgradeableReadLock();
}
public void Dispose()
{
obj.ExitUpgradeableReadLock();
}
}
/// <summary>
/// 電車でGO!新幹線専用コントローラーを制御するクラス
/// </summary>
public class TAITO_DENSYA_CON_T02_USBctrl : IDisposable
{
private ReaderWriterLockSlim LockSlim = new ReaderWriterLockSlim();
private UsbDevice usbDevice;
private const int vid = 0x0AE4;
private const int pid = 0x0005;
private static UsbDeviceFinder MyUsbFinder = new(vid, pid);
/// <summary>
/// コンストラクタ。デバイスが見つからない等の場合は例外を吐く
/// </summary>
/// <exception cref="Exception">Device Not Found.</exception>
public TAITO_DENSYA_CON_T02_USBctrl()
{
// USBデバイス接続
try
{
using (var wl = new WriteLock(LockSlim))
usbDevice = UsbDevice.OpenUsbDevice(MyUsbFinder);
if (usbDevice == null) throw new Exception("Device Not Found.");
IUsbDevice? wholeUsbDevice = usbDevice as IUsbDevice;
if (!ReferenceEquals(wholeUsbDevice, null))
{
wholeUsbDevice.SetConfiguration(1);
wholeUsbDevice.ClaimInterface(0);
}
}
catch
{
throw;
}
// 初期値代入
VibrationBrake = false;
VibrationMascon = false;
DoorLamp = false;
ATCBar = ATCBarMin;
SpeedBar = SpeedBarMin;
Speed = SpeedMin;
ATCSpeed = ATCSpeedMin;
// イベントリスナー
USBDataReceivedEvent += SetInputsFromUSBData;
// 問答無用でデータ受信開始
BeginReceiveData();
}
/// <summary>
/// コントロール転送で送るデータ
/// </summary>
/// VibrationBrake,VibrationMascon,DoorLamp ATCBar,SpeedBar,Speed,,ATCSpeed,
private byte[] _USBdata = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
/// <summary>
/// ブレーキハンドルを振動させるかどうか
/// </summary>
public bool VibrationBrake
{
get
{
using var rl = new ReadLock(LockSlim);
return (_USBdata[0] & 0x01) == 0x01;
}
set
{
using var wl = new WriteLock(LockSlim);
_USBdata[0] = value ? (byte)0x01 : (byte)0x00;
}
}
/// <summary>
/// マスコンハンドルを振動させるかどうか
/// </summary>
public bool VibrationMascon
{
get
{
using var rl = new ReadLock(LockSlim);
return (_USBdata[1] & 0x01) == 0x01;
}
set
{
using var wl = new WriteLock(LockSlim);
_USBdata[1] = value ? (byte)0x01 : (byte)0x00;
}
}
/// <summary>
/// 戸閉灯を点灯させるかどうか
/// </summary>
public bool DoorLamp
{
get
{
using var rl = new ReadLock(LockSlim);
return (_USBdata[2] & 0x80) == 0x80;
}
set
{
using var wl = new WriteLock(LockSlim);
_USBdata[2] = value ? (byte)(_USBdata[2] | 0x80) : (byte)(_USBdata[2] & ~0x80);
}
}
private int _SpeedBar = SpeedBarMin;
/// <summary>
/// スピードバーグラフ
/// </summary>
public int SpeedBar
{
get
{
using var rl = new ReadLock(LockSlim);
return _SpeedBar;
}
set
{
using var wl = new WriteLock(LockSlim);
_SpeedBar = Clamp(value, SpeedBarMin, SpeedBarMax);
_USBdata[3] = (byte)_SpeedBar;
}
}
public const int SpeedBarMin = 0;
public const int SpeedBarMax = 0x16;
public const int SpeedBarMinSpeed = 0;
public const int SpeedBarMaxSpeed = 350;
private bool _SpeedBarAuto = true;
/// <summary>
/// スピードとスピードバーグラフを連動させるかどうか
/// </summary>
public bool SpeedBarAuto
{
get
{
using var rl = new ReadLock(LockSlim);
return _SpeedBarAuto;
}
set
{
using var wl = new WriteLock(LockSlim);
_SpeedBarAuto = value;
}
}
/// <summary>
/// フラグが立っていればSpeedBarをアップデート
/// </summary>
private void UpdateSpeedBarAuto()
{
if (SpeedBarAuto)
{ // 最後の1マスはバーグラフ点灯しないので+1する。
double doubleSpeedBar = (SpeedBarMax + 1.0) * Speed / SpeedBarMaxSpeed;
SpeedBar = (int)Math.Round(doubleSpeedBar);
}
}
/// <summary>
/// 2byteのBCDに変換する。リトルエンディアン的な感じ
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private static (byte, byte) ConvertToBCD(int value)
{
byte Lower8bit = (byte)(((value / 10 % 10) << 4) + value / 1 % 10);
byte Upper8bit = (byte)(((value / 1000 % 10) << 4) + value / 100 % 10);
return (Lower8bit, Upper8bit);
}
private int _Speed = SpeedMin;
/// <summary>
/// スピード
/// </summary>
public int Speed
{
get
{
using var rl = new ReadLock(LockSlim);
return _Speed;
}
set
{
using (var wl = new WriteLock(LockSlim))
_Speed = Clamp(value, SpeedMin, SpeedMax);
SetSpeed();
UpdateATCBarAuto();
UpdateSpeedBarAuto();
}
}
public const int SpeedMin = 0;
public const int SpeedMax = 999;
private void SetSpeed()
{
switch (SpeedDispMode)
{
case SpeedDispModes.On:
SetSpeed(ConvertToBCD(_Speed));
break;
case SpeedDispModes.Off:
SetSpeed(_SpeedOff);
break;
case SpeedDispModes.Hyphen:
SetSpeed(_SpeedHyphen);
break;
case SpeedDispModes.Overbar:
SetSpeed(_SpeedOverbar);
break;
default:
SetSpeed(ConvertToBCD(_Speed));
break;
}
}
private void SetSpeed((byte, byte) Speed)
{
using var wl = new WriteLock(LockSlim);
(_USBdata[4], _USBdata[5]) = Speed;
}
private static readonly (byte, byte) _SpeedOff = (0xaa, 0x0a);
private static readonly (byte, byte) _SpeedHyphen = (0xbb, 0x0b);
private static readonly (byte, byte) _SpeedOverbar = (0xdd, 0x0d);
private SpeedDispModes _SpeedDispMode = SpeedDispModes.On;
/// <summary>
/// スピード表示の表示モード
/// </summary>
public SpeedDispModes SpeedDispMode
{
get
{
using var rl = new ReadLock(LockSlim);
return _SpeedDispMode;
}
set
{
using (var wl = new WriteLock(LockSlim))
_SpeedDispMode = value;
SetSpeed();
}
}
private int _ATCSpeed = ATCSpeedMin;
/// <summary>
/// ATC指示速度
/// </summary>
public int ATCSpeed
{
get
{
using var rl = new ReadLock(LockSlim);
return _ATCSpeed;
}
set
{
using (var wl = new WriteLock(LockSlim))
_ATCSpeed = Clamp(value, ATCSpeedMin, ATCSpeedMax);
SetATCSpeed();
UpdateATCBarAuto();
}
}
private void SetATCSpeed((byte, byte) Speed)
{
using var wl = new WriteLock(LockSlim);
(_USBdata[6], _USBdata[7]) = Speed;
}
private void SetATCSpeed()
{
switch (ATCSpeedDispMode)
{
case SpeedDispModes.On:
SetATCSpeed(ConvertToBCD(_ATCSpeed));
break;
case SpeedDispModes.Off:
SetATCSpeed(_SpeedOff);
break;
case SpeedDispModes.Hyphen:
SetATCSpeed(_SpeedHyphen);
break;
case SpeedDispModes.Overbar:
SetATCSpeed(_SpeedOverbar);
break;
default:
SetATCSpeed(ConvertToBCD(_ATCSpeed));
break;
}
}
private SpeedDispModes _ATCSpeedDispMode = SpeedDispModes.On;
/// <summary>
/// ATC指示速度表示の表示モード
/// </summary>
public SpeedDispModes ATCSpeedDispMode
{
get
{
using var rl = new ReadLock(LockSlim);
return _ATCSpeedDispMode;
}
set
{
using (var wl = new WriteLock(LockSlim))
_ATCSpeedDispMode = value;
SetATCSpeed();
}
}
/// <summary>
/// フラグが立っていればATCBarをアップデート
/// </summary>
private void UpdateATCBarAuto()
{
if (ATCBarAuto)
ATCBar = ATCBarMax - ATCSpeed + Speed;
}
public const int ATCSpeedMin = 0;
public const int ATCSpeedMax = 999;
private int _ATCBar = ATCBarMin;
/// <summary>
/// ATC指示速度と現在速度の差を表示するバーグラフ
/// </summary>
public int ATCBar
{
get
{
using var rl = new ReadLock(LockSlim);
return _ATCBar;
}
set
{
using var wl = new WriteLock(LockSlim);
_ATCBar = Clamp(value, ATCBarMin, ATCBarMax);
_USBdata[2] = (byte)((_USBdata[2] & 0xF0) + (_ATCBar & 0x0F));
}
}
public const int ATCBarMin = 0;
public const int ATCBarMax = 10;
private bool _ATCBarAuto = true;
/// <summary>
/// ATCバーグラフを速度とATC指示速度に連動させるかどうか
/// </summary>
public bool ATCBarAuto
{
get
{
using var rl = new ReadLock(LockSlim);
return _ATCBarAuto;
}
set
{
using var wl = new WriteLock(LockSlim);
_ATCBarAuto = value;
}
}
/// <summary>
/// 最大値と最小値でクランプ
/// </summary>
/// <param name="value"></param>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns></returns>
private static int Clamp(int value, int min, int max)
{
if (value < min)
return min;
if (value > max)
return max;
return value;
}
/// <summary>
/// USBポートからデータ送信実行
/// </summary>
/// <exception cref="Exception">USB device is closed</exception>
/// <exception cref="Exception">ControlTransfer Error</exception>
public void Execute()
{
int transfered = 0;
var usp = new UsbSetupPacket(0x40, 0x09, 0x0301, 0x0000, _USBdata.Length);
bool res = false;
using (var rl = new ReadLock(LockSlim))
{
if (!usbDevice.IsOpen)
{
throw new Exception("USB device is closed");
}
res = usbDevice.ControlTransfer(ref usp, _USBdata, _USBdata.Length, out transfered);
}
if (!res)
throw new Exception("ControlTransfer Error");
}
private delegate void USBDataReceiveCallback(byte[] ReceivedData);
private event USBDataReceiveCallback USBDataReceivedEvent;
/// <summary>
/// データを受信し続けるメソッドキャンセルトークンは2秒以内で反応する?
/// </summary>
/// <param name="token"></param>
/// <exception cref="Exception">なんかエラー</exception>
private void ReceiveData(CancellationToken token)
{
while (usbDevice.IsOpen)
{
if (token.IsCancellationRequested) return;
byte[] buf = new byte[6];
int bytesRead = 0;
using (var Reader = usbDevice.OpenEndpointReader(ReadEndpointID.Ep01))
{
ErrorCode ec = ErrorCode.None;
try
{
ec = Reader.Read(buf, 2000, out bytesRead);
}
catch (Exception)
{
ec = ErrorCode.ReadFailed;
}
if (ec == ErrorCode.IoTimedOut) continue;
if (ec == ErrorCode.IoCancelled) return;
if (ec == ErrorCode.ReadFailed) return;
if (ec != ErrorCode.None) throw new Exception("usbcom ReadCtrl Error : ErrorCode==" + ec.ToString() + "\n" + UsbDevice.LastErrorString);
}
if (bytesRead > 0)
Task.Run(() => USBDataReceivedEvent?.Invoke(buf));
}
}
private Task? _ReceiveDataTask;
private CancellationTokenSource? _ReceiveDataTaskCancel;
/// <summary>
/// データ受信開始
/// </summary>
public void BeginReceiveData()
{
if (Receiving) return;
using var wl = new WriteLock(LockSlim);
_ReceiveDataTaskCancel = new CancellationTokenSource();
_ReceiveDataTask = Task.Run(() => ReceiveData(_ReceiveDataTaskCancel.Token));
}
/// <summary>
/// データ受信中かどうか
/// </summary>
public bool Receiving
{
get
{
using var rl = new ReadLock(LockSlim);
if (_ReceiveDataTask == null) return false;
return _ReceiveDataTask.Status == TaskStatus.Running;
}
}
/// <summary>
/// データ受信終了
/// </summary>
public void EndReceiveData()
{
if (!Receiving) return;
using var wl = new WriteLock(LockSlim);
_ReceiveDataTaskCancel?.Cancel();
}
public const int BrakeInputMax = 8;
public const int BrakeInputMin = 0;
private int _BrakeInput = 0;
/// <summary>
/// ブレーキハンドルのポジション0~8
/// </summary>
public int BrakeInput
{
get
{
using var rl = new ReadLock(LockSlim);
return _BrakeInput;
}
private set
{
using var wl = new WriteLock(LockSlim);
_BrakeInput = value;
}
}
public const int MasconInputMax = 13;
public const int MasconInputMin = 0;
private int _MasconInput = 0;
/// <summary>
/// マスコンハンドルのポジション0~13
/// </summary>
public int MasconInput
{
get
{
using var rl = new ReadLock(LockSlim);
return _MasconInput;
}
private set
{
using var wl = new WriteLock(LockSlim);
_MasconInput = value;
}
}
private bool _KeyUP = false;
/// <summary>
/// ↑キー
/// </summary>
public bool KeyUP
{
get
{
using var rl = new ReadLock(LockSlim);
return _KeyUP;
}
private set
{
using var wl = new WriteLock(LockSlim);
_KeyUP = value;
}
}
private bool _KeyRight = false;
/// <summary>
/// →キー
/// </summary>
public bool KeyRight
{
get
{
using var rl = new ReadLock(LockSlim);
return _KeyRight;
}
private set
{
using var wl = new WriteLock(LockSlim);
_KeyRight = value;
}
}
private bool _KeyDown = false;
/// <summary>
/// ↓キー
/// </summary>
public bool KeyDown
{
get
{
using var rl = new ReadLock(LockSlim);
return _KeyDown;
}
private set
{
using var wl = new WriteLock(LockSlim);
_KeyDown = value;
}
}
private bool _KeyLeft = false;
/// <summary>
/// ←キー
/// </summary>
public bool KeyLeft
{
get
{
using var rl = new ReadLock(LockSlim);
return _KeyLeft;
}
private set
{
using var wl = new WriteLock(LockSlim);
_KeyLeft = value;
}
}
private bool _KeySelect = false;
/// <summary>
/// セレクトキー
/// </summary>
public bool KeySelect
{
get
{
using var rl = new ReadLock(LockSlim);
return _KeySelect;
}
private set
{
using var wl = new WriteLock(LockSlim);
_KeySelect = value;
}
}
private bool _KeyStart = false;
/// <summary>
/// スタートキー
/// </summary>
public bool KeyStart
{
get
{
using var rl = new ReadLock(LockSlim);
return _KeyStart;
}
private set
{
using var wl = new WriteLock(LockSlim);
_KeyStart = value;
}
}
private bool _KeyA = false;
/// <summary>
/// Aキー
/// </summary>
public bool KeyA
{
get
{
using var rl = new ReadLock(LockSlim);
return _KeyA;
}
private set
{
using var wl = new WriteLock(LockSlim);
_KeyA = value;
}
}
/// <summary>
/// Bキー
/// </summary>
private bool _KeyB = false;
public bool KeyB
{
get
{
using var rl = new ReadLock(LockSlim);
return _KeyB;
}
private set
{
using var wl = new WriteLock(LockSlim);
_KeyB = value;
}
}
private bool _KeyC = false;
/// <summary>
/// Cキー
/// </summary>
public bool KeyC
{
get
{
using var rl = new ReadLock(LockSlim);
return _KeyC;
}
private set
{
using var wl = new WriteLock(LockSlim);
_KeyC = value;
}
}
private bool _KeyD = false;
/// <summary>
/// Dキー
/// </summary>
public bool KeyD
{
get
{
using var rl = new ReadLock(LockSlim);
return _KeyD;
}
private set
{
using var wl = new WriteLock(LockSlim);
_KeyD = value;
}
}
private bool _HornPedal = false;
/// <summary>
/// 警笛ペダルが押されているかどうか
/// </summary>
public bool HornPedal
{
get
{
using var rl = new ReadLock(LockSlim);
return _HornPedal;
}
private set
{
using var wl = new WriteLock(LockSlim);
_HornPedal = value;
}
}
/// <summary>
/// 受信したデータでプロパティ更新してイベント発火
/// </summary>
/// <param name="data"></param>
private void SetInputsFromUSBData(byte[] data)
{
//Console.WriteLine(Convert.ToHexString(data));
if (data[0] != 0xff) BrakeInput = ConvertToNotch(data[0], 28.0);
if (data[1] != 0xff) MasconInput = ConvertToNotch(data[1], 18.0);
if (data[2] == 0xff) HornPedal = false;
else if (data[2] == 0x00) HornPedal = true;
SetDirectionKeys((byte)(data[3] & 0x0f));
KeyStart = Getbit(data[4], 5);
KeySelect = Getbit(data[4], 4);
KeyA = Getbit(data[4], 3);
KeyB = Getbit(data[4], 2);
KeyC = Getbit(data[4], 1);
KeyD = Getbit(data[4], 0);
UserInputMayChangedEvent?.Invoke(this);
}
/// <summary>
/// ノッチ数に変換する
/// </summary>
/// <param name="usbdata"></param>
/// <param name="divisor"></param>
/// <returns></returns>
private static int ConvertToNotch(byte usbdata, double divisor)
{
return (int)Math.Round(usbdata / divisor, MidpointRounding.AwayFromZero) - 1;
}
/// <summary>
/// 特定のビットを取り出す
/// </summary>
/// <param name="bits"></param>
/// <param name="bitpos"></param>
/// <param name="ActiveHigh"></param>
/// <returns></returns>
private static bool Getbit(byte bits, int bitpos, bool ActiveHigh = true)
{
int Mask = 1 << (bitpos);
bool Res = (bits & Mask) == Mask;
return ActiveHigh ? Res : !Res;
}
/// <summary>
/// 十字キーの各ボタンの押下状態を入れる
/// </summary>
/// <param name="KeysDirection"></param>
private void SetDirectionKeys(byte KeysDirection)
{
KeyUP = false;
KeyDown = false;
KeyLeft = false;
KeyRight = false;
switch (KeysDirection)
{
case 0:
KeyUP = true;
break;
case 1:
KeyUP = true;
KeyRight = true;
break;
case 2:
KeyRight = true;
break;
case 3:
KeyRight = true;
KeyDown = true;
break;
case 4:
KeyDown = true;
break;
case 5:
KeyDown = true;
KeyLeft = true;
break;
case 6:
KeyLeft = true;
break;
case 7:
KeyLeft = true;
KeyUP = false;
break;
}
}
public delegate void UserInputMayChanged(TAITO_DENSYA_CON_T02_USBctrl sender);
/// <summary>
/// コントローラに何か入力変化があったかもしれない時に発火するイベント
/// </summary>
public event UserInputMayChanged? UserInputMayChangedEvent;
public void Dispose()
{
EndReceiveData();
USBDataReceivedEvent -= SetInputsFromUSBData;
usbDevice.Close();
}
}
}