You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何控制Numato以太网继电器模块?C# WinForms命令无响应问题

Numato以太网继电器Telnet控制无响应问题排查与修复

问题描述

我开发了一个C# WinForms应用,用于控制8通道Numato以太网继电器模块。使用一个极简Telnet库,通过默认凭据(IP:169.254.1.1,端口:23,用户名:admin,密码:admin)能成功建立连接,但发送relay on 0这类控制命令后,设备无任何反应。以下是完整代码:

Telnet库代码

// minimalistic telnet implementation
// conceived by Tom Janssens on 2007/06/06  for codeproject
//
// http://www.corebvba.be


using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;

namespace MinimalisticTelnet
{
    enum Verbs {
        WILL = 251,
        WONT = 252,
        DO = 253,
        DONT = 254,
        IAC = 255
    }

    enum Options
    {
        SGA = 3
    }

    class TelnetConnection
    {
        TcpClient tcpSocket;

        int TimeOutMs = 100;

        public TelnetConnection(string Hostname, int Port)
        {
            tcpSocket = new TcpClient(Hostname, Port);

        }

        public string Login(string Username,string Password,int LoginTimeOutMs)
        {
            int oldTimeOutMs = TimeOutMs;
            TimeOutMs = LoginTimeOutMs;
            string s = Read();
            if (!s.TrimEnd().EndsWith(":"))
               throw new Exception("Failed to connect : no login prompt");
            WriteLine(Username);

            s += Read();
            if (!s.TrimEnd().EndsWith(":"))
                throw new Exception("Failed to connect : no password prompt");
            WriteLine(Password);

            s += Read();
            TimeOutMs = oldTimeOutMs;
            
            return s;
        }

        public void WriteLine(string cmd)
        {
            Write(cmd + "\n");
            
        }

        public void Write(string cmd)
        {
            if (!tcpSocket.Connected) return;
            byte[] buf = System.Text.ASCIIEncoding.ASCII.GetBytes(cmd.Replace("\0xFF","\0xFF\0xFF"));
            tcpSocket.GetStream().Write(buf, 0, buf.Length);
        }

        public string Read()
        {
            if (!tcpSocket.Connected) return null;
            StringBuilder sb=new StringBuilder();
            do
            {
                ParseTelnet(sb);
                System.Threading.Thread.Sleep(TimeOutMs);
            } while (tcpSocket.Available > 0);
            return sb.ToString();
        }

        public bool IsConnected
        {
            get { return tcpSocket.Connected; }
        }

        void ParseTelnet(StringBuilder sb)
        {
            while (tcpSocket.Available > 0)
            {
                int input = tcpSocket.GetStream().ReadByte();
                switch (input)
                {
                    case -1 :
                        break;
                    case (int)Verbs.IAC:
                        // interpret as command
                        int inputverb = tcpSocket.GetStream().ReadByte();
                        if (inputverb == -1) break;
                        switch (inputverb)
                        {
                            case (int)Verbs.IAC: 
                                //literal IAC = 255 escaped, so append char 255 to string
                                sb.Append(inputverb);
                                break;
                            case (int)Verbs.DO: 
                            case (int)Verbs.DONT:
                            case (int)Verbs.WILL:
                            case (int)Verbs.WONT:
                                // reply to all commands with "WONT", unless it is SGA (suppres go ahead)
                                int inputoption = tcpSocket.GetStream().ReadByte();
                                if (inputoption == -1) break;
                                tcpSocket.GetStream().WriteByte((byte)Verbs.IAC);
                                if (inputoption == (int)Options.SGA )
                                    tcpSocket.GetStream().WriteByte(inputverb == (int)Verbs.DO ? (byte)Verbs.WILL:(byte)Verbs.DO); 
                                else
                                    tcpSocket.GetStream().WriteByte(inputverb == (int)Verbs.DO ? (byte)Verbs.WONT : (byte)Verbs.DONT); 
                                tcpSocket.GetStream().WriteByte((byte)inputoption);
                                break;
                            default:
                                break;
                        }
                        break;
                    default:
                        sb.Append( (char)input );
                        break;
                }
            }
        }
    }
}

窗体加载连接代码

using System;
using System.Net.Sockets;
using System.Windows.Forms;
using MinimalisticTelnet;  


TelnetConnection tc;
private void Form1_Load(object sender, EventArgs e)
{
    tc = new TelnetConnection("169.254.1.1", 23);
    string s = tc.Login("admin", "admin", 100);
    textBox1.Text = s;
}

按钮发送命令代码

private void button1_Click(object sender, EventArgs e)
{                       
    if (tc.IsConnected)
    {
        tc.WriteLine("relay on 0");
        textBox2.Text = tc.Read();                                
    }                     
}

排查与修复方案

1. 修正命令结束符

Numato设备要求命令以**回车(\r)**而非换行(\n)作为结束标记,当前代码使用的\n可能导致设备无法识别命令。

修改TelnetConnection类的WriteLine方法:

public void WriteLine(string cmd)
{
    Write(cmd + "\r");
}

2. 增加命令处理延迟

设备处理命令需要短暂时间,发送后立即读取可能无法捕获响应,需增加延迟:

private void button1_Click(object sender, EventArgs e)
{                       
    if (tc.IsConnected)
    {
        tc.WriteLine("relay on 0");
        System.Threading.Thread.Sleep(200); // 等待设备处理命令
        textBox2.Text = tc.Read();                                
    }                     
}

3. 跳过Telnet选项协商(备选方案)

部分Numato设备对Telnet选项协商兼容性较差,可直接使用TcpClient发送命令:

TcpClient client;
NetworkStream stream;

private void Form1_Load(object sender, EventArgs e)
{
    client = new TcpClient("169.254.1.1", 23);
    stream = client.GetStream();
    
    // 读取登录提示符
    byte[] buffer = new byte[1024];
    int bytesRead = stream.Read(buffer, 0, buffer.Length);
    
    // 发送用户名
    byte[] username = Encoding.ASCII.GetBytes("admin\r");
    stream.Write(username, 0, username.Length);
    stream.Read(buffer, 0, buffer.Length);
    
    // 发送密码
    byte[] password = Encoding.ASCII.GetBytes("admin\r");
    stream.Write(password, 0, password.Length);
    bytesRead = stream.Read(buffer, 0, buffer.Length);
    textBox1.Text = Encoding.ASCII.GetString(buffer, 0, bytesRead);
}

private void button1_Click(object sender, EventArgs e)
{
    if (client.Connected)
    {
        byte[] command = Encoding.ASCII.GetBytes("relay on 0\r");
        stream.Write(command, 0, command.Length);
        
        System.Threading.Thread.Sleep(200);
        byte[] buffer = new byte[1024];
        int bytesRead = stream.Read(buffer, 0, buffer.Length);
        textBox2.Text = Encoding.ASCII.GetString(buffer, 0, bytesRead);
    }
}

4. 验证通道编号

确认设备通道编号规则:部分Numato型号通道从1开始而非0,尝试发送relay on 1测试。

内容的提问来源于stack exchange,提问作者Mark Ryan Morales

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 07:35:21