0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

電車でGO!新幹線専用コントローラーをパソコンにつなぐ(ライブラリ編)

Last updated at Posted at 2021-11-20

前回の記事「電車で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();
        }
    }
}
0
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?