以下是为《基于C#的工业机器人上位机控制程序开发(搭配松下PLC + MC协议)》这篇文章补充的更多实用代码示例,覆盖工业现场最常遇到的功能模块。这些代码基于 .NET 8/9 + 自封装 MC 协议客户端,2026年主流工业做法。

1. 完整的心跳 + 自动重连机制(工业必备)

public class PanasonicMCClientWithReconnect : IDisposable
{
    private readonly string ip;
    private readonly int port = 5000;
    private TcpClient client;
    private NetworkStream stream;
    private CancellationTokenSource cts = new();
    private Task heartbeatTask;

    public event Action<string> OnConnectionStatusChanged;

    public PanasonicMCClientWithReconnect(string ipAddress)
    {
        ip = ipAddress;
        StartHeartbeat();
    }

    private void StartHeartbeat()
    {
        heartbeatTask = Task.Run(async () =>
        {
            while (!cts.IsCancellationRequested)
            {
                try
                {
                    if (client == null || !client.Connected)
                    {
                        await ReconnectAsync();
                    }
                    else
                    {
                        // 心跳:读一个固定寄存器 D0(假设始终为固定值)
                        var data = await ReadWordDevicesAsync("D0", 1);
                        if (data == null || data.Length == 0)
                            throw new Exception("心跳失败");
                    }
                }
                catch
                {
                    OnConnectionStatusChanged?.Invoke("断开,正在重连...");
                    await Task.Delay(2000);
                }

                await Task.Delay(5000); // 每5秒心跳一次
            }
        });
    }

    private async Task ReconnectAsync()
    {
        try
        {
            client?.Dispose();
            client = new TcpClient();
            await client.ConnectAsync(ip, port);
            stream = client.GetStream();
            OnConnectionStatusChanged?.Invoke("已连接");
        }
        catch
        {
            OnConnectionStatusChanged?.Invoke("连接失败");
        }
    }

    public async Task<ushort[]> ReadWordDevicesAsync(string device, int count)
    {
        if (client == null || !client.Connected) return null;

        // 原有读逻辑...
        // 这里省略,参考上一篇完整封装
        return new ushort[count]; // 简化示例
    }

    public void Dispose()
    {
        cts.Cancel();
        heartbeatTask?.Wait(1000);
        stream?.Dispose();
        client?.Dispose();
    }
}

2. 批量读取机器人多状态(推荐方式,减少通信次数)

// 假设PLC中机器人状态集中存储在 D1000 ~ D1019
private async Task UpdateRobotMultiStatus()
{
    try
    {
        var data = await mc.ReadWordDevicesAsync("D1000", 20);

        // 示例映射(根据你的PLC程序调整地址)
        int robotRunning    = data[0];     // 0=停止 1=运行
        int currentProgNo   = data[1];     // 当前程序号
        int alarmCode       = data[2];     // 主报警码
        int servoOn         = data[3];     // 伺服使能状态
        int estopActive     = data[4];     // 急停信号
        float currentSpeed  = data[5] / 10f; // 速度百分比(放大10倍存储)
        int posX            = data[6];     // 当前X位置(脉冲或mm)
        int posY            = data[7];
        int posZ            = data[8];
        int posR            = data[9];

        this.Invoke((MethodInvoker)delegate
        {
            lblRunning.Text     = robotRunning == 1 ? "运行中" : "停止";
            lblProgNo.Text      = $"程序:{currentProgNo:D3}";
            lblAlarmCode.Text   = alarmCode == 0 ? "无报警" : $"报警码:{alarmCode:X4}";
            lblServo.Text       = servoOn == 1 ? "伺服ON" : "伺服OFF";
            lblEstop.Text       = estopActive == 1 ? "急停触发" : "正常";
            lblSpeed.Text       = $"{currentSpeed:F1}%";
            lblPosition.Text    = $"X:{posX} Y:{posY} Z:{posZ} R:{posR}";
        });
    }
    catch (Exception ex)
    {
        lblStatus.Text = "读取状态失败:" + ex.Message;
    }
}

3. 手动点动(Jog)控制(带速度调节 + 安全检查)

private float jogSpeedPercent = 10.0f; // 默认10%

private async void BtnJogAxis_MouseDown(object sender, MouseEventArgs e)
{
    if (!await CheckSafetyInterlock()) return;

    Button btn = sender as Button;
    string axis = btn.Tag?.ToString(); // "X+", "X-", "Y+", "Y-" 等

    // 写点动方向和速度
    await mc.WriteWordDeviceAsync("D300", (ushort)(jogSpeedPercent * 10)); // D300 = 速度(放大10倍)
    
    ushort directionCode = axis switch
    {
        "X+" => 1,
        "X-" => 2,
        "Y+" => 3,
        "Y-" => 4,
        "Z+" => 5,
        "Z-" => 6,
        _ => 0
    };

    await mc.WriteWordDeviceAsync("D301", directionCode); // D301 = 方向码

    // 启动点动(M200 = 1 上升沿)
    await mc.WriteBitDeviceAsync("M200", true);
}

private async void BtnJogAxis_MouseUp(object sender, MouseEventArgs e)
{
    // 停止点动
    await mc.WriteBitDeviceAsync("M200", false);
    await mc.WriteWordDeviceAsync("D301", 0); // 清方向
}

4. 报警历史记录与复位(带时间戳 + SQLite)

private async void LogAlarm(int alarmCode)
{
    if (alarmCode == 0) return;

    string desc = GetAlarmDescription(alarmCode); // 自建报警码表

    using var conn = new SQLiteConnection("Data Source=robot_alarms.db");
    await conn.OpenAsync();

    using var cmd = conn.CreateCommand();
    cmd.CommandText = @"
        INSERT INTO AlarmLog (Timestamp, Code, Description)
        VALUES (@ts, @code, @desc)";
    cmd.Parameters.AddWithValue("@ts", DateTime.Now);
    cmd.Parameters.AddWithValue("@code", alarmCode);
    cmd.Parameters.AddWithValue("@desc", desc ?? "未知报警");
    await cmd.ExecuteNonQueryAsync();

    // UI显示最新报警
    this.Invoke(() => lblLatestAlarm.Text = $"最新报警:{alarmCode:X4} - {desc}");
}

// 报警复位按钮
private async void BtnResetAlarm_Click(object sender, EventArgs e)
{
    if (!await CheckSafetyInterlock()) return;

    // 写复位标志 M300 = 1 → 上升沿触发
    await mc.WriteBitDeviceAsync("M300", true);
    await Task.Delay(200);
    await mc.WriteBitDeviceAsync("M300", false);

    MessageBox.Show("报警已复位");
}

5. 产量/节拍统计(每周期记录一次)

private int cycleCount = 0;
private DateTime lastCycleTime = DateTime.Now;

private async void TimerCycle_Tick(object sender, EventArgs e)
{
    var data = await mc.ReadWordDevicesAsync("D500", 1); // 假设D500为周期完成计数器
    int currentCycle = data[0];

    if (currentCycle > cycleCount)
    {
        cycleCount = currentCycle;
        var now = DateTime.Now;
        double cycleTimeSec = (now - lastCycleTime).TotalSeconds;
        lastCycleTime = now;

        this.Invoke(() =>
        {
            lblCycleCount.Text = $"已完成周期:{cycleCount}";
            lblCycleTime.Text = $"本周期耗时:{cycleTimeSec:F2} 秒";
            lblUPH.Text = cycleTimeSec > 0 ? $"UPH:{3600 / cycleTimeSec:F1}" : "--";
        });

        // 可存入数据库做日报表
    }
}

这些代码片段覆盖了工业机器人上位机最核心的几个模块:心跳重连、批量状态刷新、手动点动、安全联锁、报警管理、节拍统计

如果你希望进一步补充某个具体功能(例如:

  • 完整的报警码表 + 描述映射
  • 多机器人同时监控(多实例 MCClient)
  • 机器人位置实时曲线图(ScottPlot)
  • 触摸屏适配 + 高DPI处理
  • 离线模式 + 数据缓存

请告诉我,我可以继续给出更针对性的完整示例代码。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐