PCからリモートするC#のソフトです。

VisualStudioを使います。

 

form1にツールボックスからコントロールを追加します。

ユーザーコントロールを追加します。名前はContPanelにします。

ContPanelにツールボックスからコントロールを追加します。

Form1.csのコードを全て挿げ替えます。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace cs_pic32_remote
{
    //2022_0903
    public partial class Form1 : Form
    {

        public const int Rx_Byte = 230;
        public const int Tx_Byte = 128;

        public const int TxData数 = 12;
        public const int RxData数 = 12;

        public const int 最大列車数 = 8;
        public const int 列車数 = 2;

        public const int SPEED_MAX = 0x3C0;
        private const int 減速時スピード = 40;


        private int err_count = 0;



        List<train> 列車 = new List<train>();
        List<ContPanel> Panel = new List<ContPanel>();

        rs_data 受信データ;


        class position_info
        {
            public int 区間;
            public int 路線;
            public int 方向;
            public int 相;

            public position_info(int 区間, int 路線, int 方向, int 相)
            {
                this.区間 = 区間;
                this.路線 = 路線;
                this.方向 = 方向;
                this.相 = 相;
            }


            public static position_info operator +(position_info p, int n)
            {
                position_info ret = new position_info(p.区間, p.路線, p.方向, p.相);

                if (ret.方向 == 0) ret.区間 = (ret.区間 + n) & 0b111;
                else ret.区間 = (ret.区間 - n) & 0b111;

                return ret;
            }

            public static position_info operator -(position_info p, int n)
            {
                position_info ret = new position_info(p.区間, p.路線, p.方向, p.相);

                if (ret.方向 == 0) ret.区間 = (ret.区間 - n) & 0b111;
                else ret.区間 = (ret.区間 + n) & 0b111;

                return ret;
            }


            public void 次区間()
            {
                if (方向 == 0) 区間 = (区間 + 1) & 0b111;
                else 区間 = (区間 - 1) & 0b111;
            }

            public void 前区間()
            {
                if (方向 == 0) 区間 = (区間 - 1) & 0b111;
                else 区間 = (区間 + 1) & 0b111;

            }

            public void 逆転()
            {
                if (方向 == 0) 方向 = 1;
                else 方向 = 0;
            }

            public position_info copy()
            {
                return new position_info(区間, 路線, 方向, 相);
            }

            public byte Byte情報
            {
                get { return (byte)(区間 + 路線 * 8 + 方向 * 0x80 + 相 * 0x40); }
            }


            public static bool operator ==(position_info c1, position_info c2)
            {
                if (c1.路線 == c2.路線 && c1.区間 == c2.区間)
                    return true;
                else
                    return false;
            }

            public static bool operator !=(position_info c1, position_info c2)
            {
                return !(c1 == c2);
            }



        }

        class train
        {
            public position_info 現位置;
            public position_info 前位置;
            public position_info 次位置;
            public position_info 次々位置;

            public float speed;

            public bool 区間変化;
            public bool 相変更FLAG;
            public bool 安全;
            public bool 逆転;
            public int Power;
            public bool speed_manual;
            public int 設定speed;


            public train()
            {
                //Dummyで登録(路線=7)
                現位置 = new position_info(0, 7, 0, 0);
                前位置 = 現位置.copy();
                次位置 = 現位置.copy();
                次々位置 = 現位置.copy();
            }

            public train(position_info 現)
            {
                現位置 = 現;
                前位置 = 現位置 - 1;
                次位置 = 現位置 + 1;
                次々位置 = 現位置 + 2;

                区間変化 = true;

            }

            public void 逆転処理()
            {
                //現位置を前位置に、前位置を現位置に
                現位置.逆転();
                前位置.逆転();

                次位置 = 現位置.copy(); //前位置に入れるため
                現位置 = 前位置.copy();
                前位置 = 次位置.copy();

                次位置 = 現位置.copy();
                次位置.次区間();
                次々位置 = 次位置.copy();
                次々位置.次区間();
            }

            public void 区間変更()
            {
                前位置 = 現位置.copy();
                現位置 = 次位置.copy();
                次位置 = 次々位置.copy();
                次々位置.次区間();

                区間変化 = true;
            }


            public byte Status
            {
                get
                {
                    byte ret = 0;

                    if (区間変化) ret = 1;

                    if (!相変更FLAG)
                    {
                        //相変更中は区間変更しない
                        if (安全) ret += 0x80;
                    }

                    //Speed Manual
                    if (speed_manual) ret += 0x2;

                    return ret;
                }
            }

            public bool Enable
            {
                get
                {
                    if (現位置.路線 == 7) return false;
                    else return true;
                }
            }
        }

        class rs_data
        {
            public int[] speed = new int[最大列車数];
            public int[] power = new int[最大列車数];
            public int[] 区間変化 = new int[最大列車数];


            public rs_data(byte[] data)
            {
                if (data.Length != Rx_Byte) return;
                if (data[0] != 0x55 || data[1] != 0xAA) return;//Header Check


                for (int n = 0; n < 列車数; n++)
                {
                    int pos = RxData数 * n;

                    //スピードメータ-
                    speed[n] = data[pos + 2] * 256 + data[pos + 3];
                    区間変化[n] = data[pos + 4];

                    power[n] = data[pos + 7] * 256 + data[pos + 8];

                }

            }


        }

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //ポート接続処理
            serialPort1.PortName = comboBox1.Text;

            serialPort1.BaudRate = 115200;
            serialPort1.Parity = Parity.None;
            serialPort1.DataBits = 8;
            serialPort1.StopBits = StopBits.One;

            serialPort1.ReceivedBytesThreshold = Rx_Byte;

            serialPort1.ReadTimeout = 500;
            serialPort1.WriteTimeout = 500;

            serialPort1.Open();

            timer1.Enabled = true;

            button1.Enabled = false;
            button2.Enabled = true;
            comboBox1.Enabled = false;

        }

        delegate void RsDelegate();

        private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {


            //データの受信はマルチスレッドで行われる為にデリゲートを使用して
            //メインのスレッドでデータ処理の必要があります。

            Invoke(new RsDelegate(receive_data), null);


        }

        public void receive_data()
        {

            byte[] data = new Byte[serialPort1.BytesToRead];

            if (data.Length != Rx_Byte)
            {
                return;
            }


            serialPort1.Read(data, 0, data.Length);



            if (data[0] != 0x55 || data[1] != 0xAA)
            {
                serialPort1.DiscardInBuffer();
                err_count++;
                return;
            }





            受信データ = new rs_data(data);

            //label1.Text = $"{受信データ.power[0]} {受信データ.speed[0]} {err_count }";

            for (int n = 0; n < 列車数; n++)
            {
                if (列車[n].Enable)
                {
                    Panel[n].Speed_Meter = 受信データ.speed[n];
                    int pw = 受信データ.power[n] * 256 / SPEED_MAX;
                    Panel[n].Power_Meter = pw;
                    列車[n].Power = pw;

                    if (受信データ.区間変化[n] == 0x80)
                    {
                        if (!列車[n].区間変化)
                        {
                            //立ち上がりのみ
                            列車[n].区間変更();
                            //全ての列車の情報更新
                            for(int m = 0; m < 列車数; m++)
                            {
                                列車[m].区間変化 = true;
                            }
                        }
                    }
                    else
                    {
                        列車[n].区間変化 = false;
                    }

                    Panel[n].位置情報 = $"現:路{列車[n].現位置.路線} 区{列車[n].現位置.区間}," +
                                        $"次:路{列車[n].次位置.路線} 区{列車[n].次位置.区間}," +
                                        $"向:{列車[n].現位置.方向}";
                }

            }


            //-----安全確認-----
            for (int n = 0; n < 列車数; n++)
            {

                //    If 列車(n).Enable Then
                if (!列車[n].Enable) continue;

                //        Select Case 安全確認(n)
                switch (安全確認(n))
                {
                    case "停止":
                        列車[n].安全 = false;
                        列車[n].設定speed = 0;
                        Panel[n].信号 = "赤";
                        break;

                    case "減速":
                        //                Dim 減速速度 As Integer = 減速時スピード '減速時のスピード

                        列車[n].安全 = true;
                        if (Panel[n].Speed_Cont > 減速時スピード)
                        {
                            列車[n].設定speed = 減速時スピード;
                        }
                        else
                        {
                            列車[n].設定speed = Panel[n].Speed_Cont;
                        }
                        Panel[n].信号 = "黄";

                        break;

                    case "安全":
                        Panel[n].信号 = "緑";
                        列車[n].安全 = true;

                        //                If 列車[n].逆転 = 1 Then
                        //                    '逆転フラグが1なら逆転のため停止
                        //                    列車[n].設定speed = 0
                        //                ElseIf 列車[n].point処理状況 = p処理.通過待ち Then
                        //                    'ポイント通過待ちのため停止
                        //                    列車[n].設定speed = 0
                        //                ElseIf 列車[n].yard処理状況 = y処理.Yard停止 Then
                        //                    'Yardで停止
                        //                    列車[n].設定speed = 0
                        //                    Remcon[n].Speed_Cont = 0
                        //                Else
                        列車[n].設定speed = Panel[n].Speed_Cont;
                        //                End If
                        break;

                    case "向合停止":
                        列車[n].安全 = false;
                        列車[n].設定speed = 0;
                        Panel[n].信号 = "赤";
                        //                Remcon[n].CheckBox1.Checked = True  '強制逆転
                        break;

                }



                //        End Select

                //    Else
                //        Remcon[n].信号 = "灰"
                //    End If
                //Next
            }



        }

        private void Form1_Load(object sender, EventArgs e)
        {
            String[] ports = SerialPort.GetPortNames();

            if (ports.Length == 0)
            {
                //MessageBox.Show("シリアルポートが見つかりません。");
                button1.Enabled = false;
            }
            else
            {
                comboBox1.Items.AddRange(ports);
                comboBox1.SelectedIndex = 0;
            }

            //comboBox1.Text = "COM4";

            button1.Text = "接続";
            button2.Text = "切断";
            groupBox1.Text = "";
            button2.Enabled = false; //切断ボタンdisable
            label1.Text = "";



            列車.Add(new train(new position_info(1, 0, 0, 0)));
            列車.Add(new train(new position_info(5, 0, 0, 1)));

            //DummyData
            for (int n = 列車.Count; n < 最大列車数; n++)
            {
                列車.Add(new train());
            }

            for (int n = 0; n < 列車数; n++)
            {
                Panel.Add(new ContPanel(n));

                Panel[n].Top = 25;
                Panel[n].Left = Panel[0].Width * n + 2;
                Controls.Add(Panel[n]);

                Panel[n].逆転_Click += new EventHandler(逆転_Click);

                //AddHandler Remcon(n).逆転_Click, AddressOf 逆転_Click
                //AddHandler Remcon(n).ポイント_Click, AddressOf ポイント_Click
                //AddHandler Remcon(n).Yard_Click, AddressOf Yard_Click
                //Remcon(n).add_point(ポイント) 'リモコンにポイント番号登録


            }

            //位置調整
            groupBox1.Top = 25;
            groupBox1.Left = Panel[0].Width * Panel.Count + 50;

            this.Width = groupBox1.Left + groupBox1.Width + 20;


            timer1.Interval = 200;//タイマーインターバル200ms


        }


        //    Function 安全確認(ByVal n As Integer) As String
        String 安全確認(int n)
        {
            bool 停止 = false;
            bool 減速 = false;
            bool 向合停止 = false;


            //    For m As Integer = 0 To 列車数 - 1
            for (int m = 0; m < 列車数; m++)
            {

                //        If m<> n Then
                if (m == n) continue;
                //            '次位置が他の列車の現、前、次位置と同じなら停止

                if (列車[n].次位置 == 列車[m].現位置) 停止 = true;
                if (列車[n].次位置 == 列車[m].前位置) 停止 = true;
                if (列車[n].次位置 == 列車[m].次位置) 停止 = true;


                //            If 列車(n).point処理状況 = p処理.切替中 Then
                //                'ポイント切り替え中
                //                If 列車(n).現位置.方向<> 列車(m).現位置.方向 Then
                //                    If(列車(n).次位置.路線 = 列車(m).次位置.路線) And
                //                   (列車(n).次位置.区間 = 列車(m).次位置.区間) Then 停止 = True
                //                End If
                //            Else
                //                If(列車(n).次位置.路線 = 列車(m).次位置.路線) And
                //              (列車(n).次位置.区間 = 列車(m).次位置.区間) Then 停止 = True
                //            End If


                //            '次々位置が他の列車の現、前、次位置と同じなら減速

                if (列車[n].次々位置 == 列車[m].現位置) 減速 = true;
                if (列車[n].次々位置 == 列車[m].前位置) 減速 = true;
                if (列車[n].次々位置 == 列車[m].次位置) 減速 = true;


                //            If 停止 Then
                if (停止)
                {
                    //                If(n > m) And(列車(n).現位置.方向<> 列車(m).現位置.方向) Then
                    if (n > m && 列車[n].現位置.方向 != 列車[m].現位置.方向)
                    {
                        //                 向合停止 = True '方向逆で優先順位低いとき
                        //                    Return "向合停止"
                        return "向合停止";

                    }
                }
            }

            //    For Each p As 停止位置class In 車止位置
            //        If p.ポイント番号< 0 Then
            //            'ポイントのない停止位置
            //            If 列車(n).現位置 = p.位置 Then
            //                '現位置なら停止
            //                停止 = True
            //                Exit For
            //            ElseIf 列車(n).次位置 = p.位置 Then
            //                '次位置なら減速
            //                減速 = True
            //                Exit For
            //            End If
            //        End If
            //    Next




            //    '通過形ヤードで空き線なしは通過不可
            //    For Each p As YardClass In yard
            //        If p.type = 1 Then
            //            '通過形ヤードの時
            //            If p.空線.Length = 0 Then
            //                '空線なしの時
            //                If (列車(n).現位置 = p.進入位置) Or(列車(n).現位置 = p.進入位置2) Then
            //                   停止 = True
            //                    Exit For
            //                End If
            //            End If
            //        End If
            //    Next


            //    'Yard減速
            //    If 列車(n).yard処理状況 = y処理.Yardポイント切替完了 Then
            //        減速 = True
            //    End If


            if (停止) return "停止";
            if (減速) return "減速";

            return "安全";
        }



        private void button2_Click(object sender, EventArgs e)
        {
            //ポート切断処理
            timer1.Enabled = false;
            serialPort1.Close();
            comboBox1.Enabled = true;
            button1.Enabled = true;
            button2.Enabled = false;
        }

        private void 逆転_Click(object sender, EventArgs e)
        {
            int n = ((ContPanel)sender).index;

            label1.Text = $"{n} cont click!";

            列車[n].逆転 = Panel[n].逆転;

        }

        private void timer1_Tick(object sender, EventArgs e)
        {

            for (int n = 0; n < 列車数; n++)
            {

                列車[n].speed = 列車[n].設定speed;

                //'逆転フラグ確認
                if (列車[n].逆転)
                    if (列車[n].speed == 0)
                        if (列車[n].Power == 0)
                        {
                            //列車が停止したら逆転
                            列車[n].逆転処理();
                            列車[n].逆転 = false;
                            列車[n].区間変化 = true;//位置情報更新の為


                            //            '逆転ボタン戻し
                            Panel[n].逆転check = false;
                            //            Remcon(n).CheckBox1.Enabled = True

                        }

            }


            serialPort1.DiscardInBuffer();

            byte[] Txdata = new byte[Tx_Byte];

            //ヘッダーとして&H55,&HAAを送る
            Txdata[0] = 0x55;
            Txdata[1] = 0xAA;

            //列車[0].speed = Panel[0].Speed_Cont;

            //列車[0].speed_manual = true;

            for (int n = 0; n < 最大列車数; n++)//列車.Count
            {
                int p = n * TxData数 + 2;

                Txdata[p] = (byte)列車[n].speed;//speed data

                Txdata[p + 1] = 列車[n].現位置.Byte情報;
                Txdata[p + 2] = 列車[n].前位置.Byte情報;
                Txdata[p + 3] = 列車[n].次位置.Byte情報;
                Txdata[p + 4] = 列車[n].Status;

                //    Txdata(p + 5) = 列車(n).ポイント
                //    Txdata(p + 6) = 列車(n).yard


            }


            try
            {
                serialPort1.Write(Txdata, 0, Tx_Byte);//データ送信
            }
            catch (Exception ex)
            {
                //エラーのとき
                button2.PerformClick();//切断
                MessageBox.Show(ex.Message);
            }




        }

       
    }
}

ContPanel.csのコードを全て挿げ替えます。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace cs_pic32_remote
{
    //2022_0903
    public partial class ContPanel : UserControl
    {
        public int index;
        public bool 逆転;

        public event EventHandler 逆転_Click;


        public ContPanel(int id)
        {
            InitializeComponent();

            index = id;
        }

        private void ContPanel_Load(object sender, EventArgs e)
        {
            trackBar1.Maximum = 128;
            trackBar1.TickFrequency = 10;
            checkBox1.Text = "逆転";
            checkBox1.AutoSize = false;
            checkBox1.Height = pictureBox2.Height;
            checkBox1.Width = this.Width/3;

            pictureBox2.SizeMode = PictureBoxSizeMode.StretchImage;
            pictureBox2.ImageLocation = "up.png";

            //スピードメーター用
            pictureBox1.Image = new Bitmap(pictureBox1.Size.Width, pictureBox1.Size.Height);

        }


        public int Speed_Cont
        {
            get { return trackBar1.Value; }
            set { trackBar1.Value = value; }
        }

        public String 位置情報
        {
            set
            {
                String[] items =value.Split(',');

                label1.Text = $"{items[0]}\n{items[1]}";



                if (items[2] == "向:0")
                {
                    if (pictureBox2.ImageLocation == "down.png")
                        pictureBox2.ImageLocation = "up.png";
                }
                else
                {
                    if (pictureBox2.ImageLocation == "up.png")
                        pictureBox2.ImageLocation = "down.png";
                }
            }
        }

        //    Public WriteOnly Property Speed_Meter() As Integer  'Speed メーター
        public int Speed_Meter
        {
            set
            {
                int speed_max = 256;
                int sp = pictureBox1.Height - value * pictureBox1.Height / speed_max;

                Graphics g = Graphics.FromImage(pictureBox1.Image);
                g.Clear(Color.White);

                Pen pen = new Pen(Color.Red, 3);
                Brush brush = Brushes.Pink;
                Point p1 = new Point(0, sp);
                Point p2 = new Point(pictureBox1.Width, sp);
                Rectangle rect = new Rectangle(p1, new Size(pictureBox1.Width, pictureBox1.Height - sp));

                g.FillRectangle(brush, rect);

                //pictureBox1.Refresh();
            }
        }
        public int Power_Meter
        {
            set
            {
                int speed_max = 256;
                int sp = pictureBox1.Height - value * pictureBox1.Height / speed_max;

                Graphics g = Graphics.FromImage(pictureBox1.Image);

                Pen pen = new Pen(Color.Red, 3);
                Point p1 = new Point(0, sp);
                Point p2 = new Point(pictureBox1.Width, sp);

                g.DrawLine(pen, p1, p2);

                pictureBox1.Refresh();
            }
        }

        public String 信号
        {
            set
            {
                switch (value)
                {
                    case "緑":
                        label1.BackColor = Color.LightGreen;
                        break;
                    case "黄":
                        label1.BackColor = Color.Yellow;
                        break;
                    case "赤":
                        label1.BackColor = Color.LightPink;
                        break;
                    case "灰":
                        label1.BackColor = Color.LightGray;
                        break;
                }
            }
        }

        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            逆転 = checkBox1.Checked;
            逆転_Click(this, EventArgs.Empty);
        }

        public bool 逆転check
        {
            set { checkBox1.Checked = value; }
        }
    }
}

プロジェクトのフォルダーの bin  Debug に

   

この2つのpngを入れます。

実行して接続するとこうなります。最初の列車の位置は区間1と区間5です。

逆転を押すと、スピードを下げて0になった時に方向が逆転します。