GPIO Service

GPIO-C#

SetOutput
private void SetOutput(string alias, bool state)
        {
            if (string.IsNullOrEmpty(alias))
            {
                _logger.LogError("No alias for SetGPIO, unable to continue");
                return;
            }

            var clientGPIORequest = new TestInterfaceGPIOService.TestInterfaceGPIOServiceClient(Channel);
            uint dutPosition = 1;
            const string failedMessage = "Failed to set GPIO";
            var ioRequest = new IORequest()
            {
                Session = _sessionId,
                DutPosition = dutPosition,
                Alias = alias,
                State = state
            };

            var textBox = GetControl<TextBox>("GPIOResultTextBox");
            if (textBox == null)
            {
                _logger.LogError("Failed to find control with matching name GPIOResultTextBox");
                return;
            }
            try
            {
                _logger.LogInformation("GPIO request: {ioRequest}", ioRequest);
                var response = clientGPIORequest.SetOutput(ioRequest);

                if (response.Code != (int)StatusCode.OK)
                {
                    _logger.LogError(failedMessage + ", {status}", response);
                    textBox.Text = failedMessage;
                    textBox.BackColor = Color.Red;
                    return;
                }

                textBox.ResetBackColor();
                textBox.Text = $"{alias} set to: {(state ? 1 : 0)}";
            }
            catch (Exception exception)
            {
                _logger.LogError(exception, failedMessage);
                textBox.Text = failedMessage;
                textBox.BackColor = Color.Red;
            }
        }

这个SetOutput函数是一个用于设置GPIO输出状态的C#方法。让我详细解释它的功能和工作流程:

函数功能概述

这是一个GPIO控制函数,通过gRPC服务调用设置指定GPIO引脚的电平状态(高/低电平)。

详细功能解析

1. 参数说明

private void SetOutput(string alias, bool state)
  • alias: GPIO引脚的别名标识符(如"GPIO1"、"LED1"等)
  • state: 要设置的状态(true=高电平/1,false=低电平/0)

2. 参数验证

if (string.IsNullOrEmpty(alias))
{
    _logger.LogError("No alias for SetGPIO, unable to continue");
    return;
}
  • 检查别名是否为空,防止无效调用

3. gRPC客户端初始化

var clientGPIORequest = new TestInterfaceGPIOService.TestInterfaceGPIOServiceClient(Channel);
uint dutPosition = 1;
const string failedMessage = "Failed to set GPIO";
  • 创建GPIO服务的gRPC客户端
  • 设置设备位置(DUT position = 1)
  • 定义错误消息常量

4. 构建请求对象

var ioRequest = new IORequest()
{
    Session = _sessionId,      // 会话ID
    DutPosition = dutPosition, // 设备位置
    Alias = alias,            // GPIO别名
    State = state             // 目标状态
};

5. UI控件获取

var textBox = GetControl<TextBox>("GPIOResultTextBox");
if (textBox == null)
{
    _logger.LogError("Failed to find control with matching name GPIOResultTextBox");
    return;
}
  • 获取用于显示结果的文本框控件
  • 如果控件不存在则记录错误并返回

6. gRPC调用执行

_logger.LogInformation("GPIO request: {ioRequest}", ioRequest);
var response = clientGPIORequest.SetOutput(ioRequest);
  • 记录请求信息(用于调试)
  • 执行gRPC调用设置GPIO输出

7. 响应处理

if (response.Code != (int)StatusCode.OK)
{
    _logger.LogError(failedMessage + ", {status}", response);
    textBox.Text = failedMessage;
    textBox.BackColor = Color.Red;
    return;
}
  • 检查服务器返回的状态码
  • 如果失败:记录错误、显示红色错误消息

8. 成功处理

textBox.ResetBackColor();
textBox.Text = $"{alias} set to: {(state ? 1 : 0)}";
  • 重置文本框背景色
  • 显示成功消息(如:"GPIO1 set to: 1")

9. 异常处理

catch (Exception exception)
{
    _logger.LogError(exception, failedMessage);
    textBox.Text = failedMessage;
    textBox.BackColor = Color.Red;
}
  • 捕获所有异常
  • 记录异常详情
  • 显示红色错误消息

工作流程

  1. 验证输入 → 检查别名有效性
  2. 准备请求 → 构建gRPC请求对象
  3. 获取UI控件 → 准备结果显示
  4. 执行调用 → 发送gRPC请求到服务器
  5. 处理响应 → 检查操作是否成功
  6. 更新UI → 显示结果状态

技术特点

错误处理机制

  • 参数验证:防止空别名
  • 控件检查:确保UI控件存在
  • 状态码检查:验证服务器响应
  • 异常捕获:处理网络和系统错误

用户反馈

  • 颜色编码:红色表示错误,默认色表示成功
  • 详细消息:显示具体的GPIO状态信息
  • 日志记录:便于调试和故障排查

常量使用

const string failedMessage = "Failed to set GPIO";
  • 使用常量避免硬编码字符串
  • 便于维护和国际化

使用示例

// 设置GPIO1为高电平
SetOutput("GPIO1", true);

// 设置LED1为低电平  
SetOutput("LED1", false);

应用场景

这个函数通常用于:

  • 硬件测试:控制测试设备上的GPIO引脚
  • LED控制:控制指示灯状态
  • 继电器控制:开关外部设备
  • 信号生成:产生特定的数字信号

这个函数实现了完整的GPIO控制流程,包括错误处理、用户反馈和日志记录,是硬件控制应用的典型实现。

GetInput
private void GetInput(string alias)
        {
            if (string.IsNullOrEmpty(alias))
            {
                _logger.LogError("No alias for GetInput, unable to continue");
                return;
            }

            var clientGPIORequest = new TestInterfaceGPIOService.TestInterfaceGPIOServiceClient(Channel);
            uint dutPosition = 1;
            const string failedMessage = "Failed to get GPIO";
            var ioRequest = new IORequest()
            {
                Session = _sessionId,
                DutPosition = dutPosition,
                Alias = alias
            };

            var textBox = GetControl<TextBox>("GPIOResultTextBox");
            if (textBox == null)
            {
                _logger.LogError("Failed to find control with matching name GPIOResultTextBox");
                return;
            }
            try
            {
                _logger.LogInformation("GPIO request: {ioRequest}", ioRequest);
                var response = clientGPIORequest.GetInput(ioRequest);

                if (response.Status.Code != (int)StatusCode.OK)
                {
                    _logger.LogError(failedMessage + ", {status}", response);
                    textBox.Text = failedMessage;
                    textBox.BackColor = Color.Red;
                    return;
                }

                textBox.ResetBackColor();
                textBox.Text = $"{alias}: {(response.State ? 1 : 0)}";
            }
            catch (Exception exception)
            {
                _logger.LogError(exception, failedMessage);
                textBox.Text = failedMessage;
                textBox.BackColor = Color.Red;
            }
        }
    }
}

这个GetInput函数是一个用于读取GPIO输入状态的C#方法。让我详细解释它的功能和工作流程:

函数功能概述

这是一个GPIO状态读取函数,通过gRPC服务调用获取指定GPIO引脚的当前电平状态(高/低电平)。

详细功能解析

1. 参数说明

private void GetInput(string alias)
  • alias: GPIO引脚的别名标识符(如"GPIO1"、"BUTTON1"等)

2. 参数验证

if (string.IsNullOrEmpty(alias))
{
    _logger.LogError("No alias for GetInput, unable to continue");
    return;
}
  • 检查别名是否为空,防止无效调用
  • 记录错误日志并提前返回

3. gRPC客户端初始化

var clientGPIORequest = new TestInterfaceGPIOService.TestInterfaceGPIOServiceClient(Channel);
uint dutPosition = 1;
const string failedMessage = "Failed to get GPIO";
  • 创建GPIO服务的gRPC客户端
  • 设置设备位置(DUT position = 1)
  • 定义错误消息常量

4. 构建请求对象

var ioRequest = new IORequest()
{
    Session = _sessionId,      // 会话ID
    DutPosition = dutPosition, // 设备位置
    Alias = alias             // GPIO别名
};
  • 注意:与SetOutput不同,这里不需要设置State字段,因为是读取操作

5. UI控件获取

var textBox = GetControl<TextBox>("GPIOResultTextBox");
if (textBox == null)
{
    _logger.LogError("Failed to find control with matching name GPIOResultTextBox");
    return;
}
  • 获取用于显示结果的文本框控件
  • 如果控件不存在则记录错误并返回

6. gRPC调用执行

_logger.LogInformation("GPIO request: {ioRequest}", ioRequest);
var response = clientGPIORequest.GetInput(ioRequest);
  • 记录请求信息(用于调试)
  • 执行gRPC调用获取GPIO输入状态

7. 响应处理

if (response.Status.Code != (int)StatusCode.OK)
{
    _logger.LogError(failedMessage + ", {status}", response);
    textBox.Text = failedMessage;
    textBox.BackColor = Color.Red;
    return;
}
  • 检查服务器返回的状态码
  • 如果失败:记录错误、显示红色错误消息

8. 成功处理

textBox.ResetBackColor();
textBox.Text = $"{alias}: {(response.State ? 1 : 0)}";
  • 重置文本框背景色
  • 显示GPIO状态(如:"GPIO1: 1" 或 "BUTTON1: 0")

9. 异常处理

catch (Exception exception)
{
    _logger.LogError(exception, failedMessage);
    textBox.Text = failedMessage;
    textBox.BackColor = Color.Red;
}
  • 捕获所有异常
  • 记录异常详情
  • 显示红色错误消息

与SetOutput函数的对比

特性 GetInput (读取) SetOutput (设置)
操作类型 读取状态 设置状态
请求参数 不需要State字段 需要State字段
响应处理 检查response.Status.Code 检查response.Code
结果显示 显示读取的状态值 显示设置的状态值
gRPC方法 GetInput() SetOutput()

工作流程

  1. 验证输入 → 检查别名有效性
  2. 准备请求 → 构建gRPC请求对象(不含State)
  3. 获取UI控件 → 准备结果显示
  4. 执行调用 → 发送gRPC请求到服务器
  5. 处理响应 → 检查操作是否成功
  6. 显示状态 → 显示GPIO的当前电平状态
  7. 更新UI → 显示结果状态

技术特点

错误处理机制

  • 参数验证:防止空别名
  • 控件检查:确保UI控件存在
  • 状态码检查:验证服务器响应
  • 异常捕获:处理网络和系统错误

用户反馈

  • 颜色编码:红色表示错误,默认色表示成功
  • 状态显示:显示具体的GPIO状态(1/0)
  • 日志记录:便于调试和故障排查

使用示例

// 读取GPIO1的输入状态
GetInput("GPIO1");

// 读取按钮状态
GetInput("BUTTON1");

应用场景

这个函数通常用于:

  • 按钮检测:读取按钮是否被按下
  • 传感器状态:读取传感器输出信号
  • 开关检测:检测开关位置状态
  • 信号监控:监控外部设备的状态
  • 硬件测试:验证GPIO输入功能

响应数据结构

从服务器返回的响应包含:

  • Status.Code: 操作状态码
  • State: GPIO的实际电平状态(true/false)

这个函数实现了完整的GPIO状态读取流程,是硬件状态监控应用的典型实现。

GPIO-python

init

__init__
def __init__(self, channel: grpc.Channel, session_id: Optional[type_pb2.SessionId] = None, 
                 dut_position: int = 1, verbose: bool = False):
        """
        Initialize GPIO service with existing gRPC channel
        
        Args:
            channel: Existing gRPC channel (already connected)
            session_id: Optional session ID for the connection
            dut_position: Device under test position (default: 1)
            verbose: Enable verbose logging
        """
        self.channel = channel
        self.gpio_stub = pb2_grpc.TestInterfaceGPIOServiceStub(channel)
        self.dut_position = dut_position
        self.verbose = verbose
        
        # Use provided session or create a default one
        if session_id:
            self.session_id = session_id
        else:
            self.session_id = self._create_default_session()
        
        self.logger = logging.getLogger(self.__class__.__name__)
        if self.verbose:
            self.logger.setLevel(logging.DEBUG)

这个函数是 GPIOService 类的构造函数(Constructor)。在 Python 中,每当您创建一个类的新实例(例如,gpio_service = GPIOService(...))时,__init__ 方法就会被自动调用。它的核心职责是初始化这个新创建的对象,为它设置好所有必需的初始状态和属性。

让我们逐行来分析它的功能:

函数签名

def __init__(self, channel: grpc.Channel, session_id: Optional[type_pb2.SessionId] = None, 
             dut_position: int = 1, verbose: bool = False):
  • self: 这是 Python 类方法中的一个约定俗成的参数,它代表对象实例本身。通过 self,我们可以在方法内部访问和修改对象的属性(例如 self.channel)。
  • channel: grpc.Channel: 这是一个必需的参数。它要求在创建 GPIOService 对象时,必须提供一个已经建立好的 grpc.Channel。这个 channel 是客户端与服务器之间通信的“管道”,它封装了连接的地址、端口和安全设置等信息。
  • session_id: Optional[type_pb2.SessionId] = None: 这是一个可选参数。它允许调用者传入一个 SessionId 对象。如果提供了,就使用它;如果没有提供(默认为 None),__init__ 函数会自己创建一个默认的会话 ID。
  • dut_position: int = 1: 这是另一个可选参数,代表“被测设备(Device Under Test)”的位置。它有一个默认值 1
  • verbose: bool = False: 这个布尔值参数用于控制是否开启详细日志(Verbose Logging)。默认是关闭的(False)。

函数体详解

# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
self.channel = channel
  • 存储 gRPC 通道:将传入的 channel 对象保存为实例的一个属性,以便在对象的其他方法中可以重复使用它。
# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
self.gpio_stub = pb2_grpc.TestInterfaceGPIOServiceStub(channel)
  • 创建 gRPC Stub(存根):这是最关键的一步。它使用传入的 channel 来实例化一个 TestInterfaceGPIOServiceStub 对象。这个 stub 对象就是我们前面讨论过的客户端“代理”或“遥控器”。之后,所有对服务器的 gRPC 调用(如 SetOutput, GetInput)都将通过这个 self.gpio_stub 来发起。
# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
self.dut_position = dut_position
self.verbose = verbose
  • 保存配置参数:将 dut_positionverbose 这两个配置参数也保存为实例的属性。
# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
if session_id:
    self.session_id = session_id
else:
    self.session_id = self._create_default_session()
  • 处理会话 ID:这里检查 session_id 是否被提供。
    • 如果提供了,就直接使用它。
    • 如果没有提供,就调用 _create_default_session() 这个内部辅助方法来生成一个默认的会.
# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
self.logger = logging.getLogger(self.__class__.__name__)
if self.verbose:
    self.logger.setLevel(logging.DEBUG)
  • 配置日志记录器
    1. logging.getLogger(self.__class__.__name__):获取一个日志记录器实例。self.__class__.__name__ 会得到类名(也就是 "GPIOService"),这使得日志输出时可以清晰地看到是哪个类产生的日志。
    2. if self.verbose::如果 verbose 标志为 True,则将日志级别设置为 logging.DEBUG。这意味着,只有在开启 verbose 模式时,那些用 self.logger.debug(...) 记录的详细调试信息才会被显示出来。

总结

总而言之,__init__ 函数就像是 GPIOService 对象的“岗前培训”。它确保在对象开始工作(即调用 SetOutputGetInput)之前,所有必要的工具和配置(如 gRPC 通道、Stub、会话 ID、日志记录器等)都已准备就绪并被妥善地存放在 self 中。这种设计使得 GPIOService 类的结构清晰,易于使用和维护。

SetOutput

SetOutput
def SetOutput(self, alias: str, state: bool, timeout: float = 10.0) -> bool:
        """
        Set GPIO output state
        
        Args:
            alias: GPIO alias/identifier (e.g., "GPIO1", "LED1")
            state: Desired output state (True = high/1, False = low/0)
            timeout: RPC timeout in seconds
            
        Returns:
            bool: True if successful, False otherwise
        """
        if not alias or not alias.strip():
            self.logger.error(f"Invalid GPIO alias: '{alias}'")
            return False
        
        try:
            # Build IO request
            io_request = pb2.IORequest()
            io_request.session.CopyFrom(self.session_id)
            io_request.dut_position = self.dut_position
            io_request.alias = alias.strip()
            io_request.state = state
            
            if self.verbose:
                self.logger.debug(f"GPIO SetOutput request; {json.dumps(MessageToDict(io_request))}")
            
            # Execute gRPC call
            response = self.gpio_stub.SetOutput(io_request, timeout=timeout)
            
            # Check response code (matching C# StatusCode.OK which is 0)
            if response.code == 0:
                self.logger.info(f"GPIO '{alias}' set to: {1 if state else 0}")
                return True
            else:
                self.logger.error(f"Failed to set GPIO '{alias}': code={response.code}")
                return False
                
        except grpc.RpcError as e:
            self.logger.error(f"gRPC error setting GPIO '{alias}': {e.details()}")
            if self.verbose:
                self.logger.debug(f"Error code: {e.code()}")
            return False
        except Exception as e:
            self.logger.error(f"Unexpected error setting GPIO '{alias}': {e}")
            if self.verbose:
                import traceback
                traceback.print_exc()
            return False

这个函数是 GPIOService 客户端的核心功能之一,它的主要职责是向 gRPC 服务器发送一个请求,来设置指定 GPIO 引脚的输出状态(高电平或低电平)

整个过程可以分为三个主要步骤:准备请求发送请求处理响应

函数签名

def SetOutput(self, alias: str, state: bool, timeout: float = 10.0) -> bool:
  • alias: str: 要控制的 GPIO 的别名,例如 "TEST_GPIO_0"。这是一个字符串。
  • state: bool: 期望设置的状态。True 通常代表高电平 (1),False 代表低电平 (0)。
  • timeout: float = 10.0: RPC(远程过程调用)的超时时间,单位是秒。如果服务器在 10 秒内没有响应,调用就会失败。这是一个可选参数。
  • -> bool: 这是 Python 的类型提示,表示这个函数最终会返回一个布尔值(TrueFalse),用于指示操作是否成功。

1. 准备请求 (Request Preparation)

在这一阶段,函数会构建一个符合 .proto 文件定义的 IORequest 消息。

# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
if not alias or not alias.strip():
    self.logger.error(f"Invalid GPIO alias: '{alias}'")
    return False
  • 输入验证:首先,它会检查传入的 alias 是否有效。如果 alias 是空的或者只包含空格,它会记录一个错误并立即返回 False,因为这是一个无效的请求。.strip() 用于去除字符串两端的空格。
# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
try:
    # Build IO request
    io_request = pb2.IORequest()
    io_request.session.CopyFrom(self.session_id)
    io_request.dut_position = self.dut_position
    io_request.alias = alias.strip()
    io_request.state = state
  • 构建请求消息
    1. io_request = pb2.IORequest(): 创建一个 IORequest 消息对象。这个对象的结构是在 test_interface.proto 文件中定义的。
    2. io_request.session.CopyFrom(self.session_id): 将在 __init__ 中准备好的 session_id 复制到请求中。
    3. io_request.dut_position = self.dut_position: 设置被测设备的位置。
    4. io_request.alias = alias.strip(): 设置要控制的 GPIO 别名。
    5. io_request.state = state: 设置期望的状态。
# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
if self.verbose:
    self.logger.debug(f"GPIO SetOutput request; {json.dumps(MessageToDict(io_request))}")
  • 详细日志:如果 verbose 模式开启,这里会将构建好的 io_request 对象转换成一个 JSON 字符串并记录下来。这对于调试非常有用,可以清晰地看到发送给服务器的确切内容。

2. 发送请求 (Request Execution)

# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
# Execute gRPC call
response = self.gpio_stub.SetOutput(io_request, timeout=timeout)
  • 执行 gRPC 调用:这是与服务器交互的时刻。它调用 self.gpio_stub(在 __init__ 中创建的客户端代理)的 SetOutput 方法,将我们精心构建的 io_requesttimeout 参数传递过去。gRPC 框架会处理底层的网络通信,并将服务器的响应返回,存放在 response 变量中。

3. 处理响应 (Response Handling)

服务器完成操作后会返回一个 IOResponse 消息,客户端需要检查这个响应来判断操作是否成功。

# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
# Check response code (matching C# StatusCode.OK which is 0)
if response.code == 0:
    self.logger.info(f"GPIO '{alias}' set to: {1 if state else 0}")
    return True
else:
    self.logger.error(f"Failed to set GPIO '{alias}': code={response.code}")
    return False
  • 检查响应码:我们之前分析过,code=0 代表成功。
    • 如果 response.code0,就记录一条成功信息(例如 "GPIO 'TEST_GPIO_0' set to: 1"),并返回 True
    • 如果 code 是其他值,说明服务器端发生了错误(例如,我们之前遇到的 code=9 INVALID_ARGUMENT)。此时,记录一条包含错误码的错误信息,并返回 False

异常处理 (Exception Handling)

网络通信和远程调用总是有可能失败的,所以一个健壮的函数必须处理这些异常情况。

# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
except grpc.RpcError as e:
    # ...
    return False
except Exception as e:
    # ...
    return False
  • except grpc.RpcError as e: 这个 try...except 块专门捕获 gRPC 相关的错误。例如,如果服务器无法连接、网络中断、或者超时,就会触发 RpcError。在这种情况下,它会记录 gRPC 提供的详细错误信息(e.details())并返回 False
  • except Exception as e: 这是一个更通用的异常捕获,用于处理其他任何意料之外的错误,以确保程序不会因为未处理的异常而崩溃。

总结

SetOutput 函数是一个设计良好、功能完整的客户端方法。它封装了从构建请求、发送请求、解析响应到处理各种异常的全部逻辑,为上层调用者(例如 main 函数)提供了一个非常简洁和清晰的接口:只需提供 aliasstate,就能得到一个明确的成功或失败的结果。

GetInput

GetInput
def GetInput(self, alias: str, timeout: float = 10.0) -> Optional[bool]:
        """
        Get GPIO input state
        
        Args:
            alias: GPIO alias/identifier (e.g., "GPIO1", "BUTTON1")
            timeout: RPC timeout in seconds
            
        Returns:
            Optional[bool]: GPIO state (True/False) if successful, None if failed
        """
        if not alias or not alias.strip():
            self.logger.error(f"Invalid GPIO alias: '{alias}'")
            return None
        
        try:
            # Build IO request (state field not needed for GetInput)
            io_request = pb2.IORequest()
            io_request.session.CopyFrom(self.session_id)
            io_request.dut_position = self.dut_position
            io_request.alias = alias.strip()
            
            if self.verbose:
                self.logger.debug(f"GPIO GetInput request; {json.dumps(MessageToDict(io_request))}")
            
            # Execute gRPC call
            response = self.gpio_stub.GetInput(io_request, timeout=timeout)
            
            # Check response status (matching C# response.Status.Code which is 0)
            if hasattr(response, 'status') and response.status.code == 0:
                state = response.state if hasattr(response, 'state') else False
                self.logger.info(f"GPIO '{alias}' state: {1 if state else 0}")
                return state
            else:
                self.logger.error(f"Failed to get GPIO '{alias}' state")
                return None
                
        except grpc.RpcError as e:
            self.logger.error(f"gRPC error getting GPIO '{alias}' state: {e.details()}")
            if self.verbose:
                self.logger.debug(f"Error code: {e.code()}")
            return None
        except Exception as e:
            self.logger.error(f"Unexpected error getting GPIO '{alias}' state: {e}")
            if self.verbose:
                import traceback
                traceback.print_exc()
            return None

这个函数与 SetOutput 互为补充,是 GPIOService 客户端的另一个核心功能。它的职责是向 gRPC 服务器发送一个请求,来读取指定 GPIO 引脚的当前输入状态

它的整体逻辑与 SetOutput 非常相似,也分为准备请求发送请求处理响应三个步骤,但在这三个步骤的细节上有一些关键的区别。

函数签名

def GetInput(self, alias: str, timeout: float = 10.0) -> Optional[bool]:
  • alias: str: 要读取的 GPIO 的别名。
  • timeout: float = 10.0: RPC 调用的超时时间。
  • -> Optional[bool]: 这是与 SetOutput 的一个重要区别。返回类型是 Optional[bool],这意味着它有三种可能的返回值:
    • True: 成功读取,且引脚状态为高电平。
    • False: 成功读取,且引脚状态为低电平。
    • None: 读取操作失败

使用 None 来表示失败,可以清晰地区分“成功读取到低电平(False)”和“读取失败”这两种完全不同的情况。

1. 准备请求 (Request Preparation)

# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
if not alias or not alias.strip():
    self.logger.error(f"Invalid GPIO alias: '{alias}'")
    return None
  • 输入验证:与 SetOutput 一样,首先检查 alias 是否有效。如果无效,记录错误并返回 None
# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
# Build IO request (state field not needed for GetInput)
io_request = pb2.IORequest()
io_request.session.CopyFrom(self.session_id)
io_request.dut_position = self.dut_position
io_request.alias = alias.strip()
  • 构建请求消息:这里是第一个关键区别。在为 GetInput 构建 IORequest 时,我们不需要设置 state 字段。因为我们是去“获取”状态,而不是“设置”状态,所以请求中不需要包含状态信息。

2. 发送请求 (Request Execution)

# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
# Execute gRPC call
response = self.gpio_stub.GetInput(io_request, timeout=timeout)
  • 执行 gRPC 调用:调用 stubGetInput 方法,将请求发送到服务器。服务器会执行硬件读取操作,并将结果封装在响应中返回。

3. 处理响应 (Response Handling)

这是 GetInput 中最复杂,也是与 SetOutput 区别最大的部分。GetInput 的响应消息(IOResponse)不仅包含状态码,还包含了读取到的 GPIO 状态。

# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
# Check response status (matching C# response.Status.Code which is 0)
if hasattr(response, 'status') and response.status.code == 0:
    state = response.state if hasattr(response, 'state') else False
    self.logger.info(f"GPIO '{alias}' state: {1 if state else 0}")
    return state
else:
    self.logger.error(f"Failed to get GPIO '{alias}' state")
    return None
  • 检查响应状态
    1. if hasattr(response, 'status') and response.status.code == 0:: 这是一个非常健壮(Robust)的检查。它首先用 hasattr 检查响应对象中是否存在 status 属性,以防止服务器返回不规范的响应时导致程序崩溃。只有在 status 属性存在的情况下,它才会去检查 status.code 是否为 0(成功)。
    2. 如果状态码为 0,说明服务器成功读取了 GPIO。
    3. state = response.state if hasattr(response, 'state') else False: 接着,它从响应中提取 state 字段的值。这里同样使用了 hasattr 做了一层保护,如果 state 字段因某种原因缺失,它会默认返回 False
    4. self.logger.info(...): 记录成功获取的状态。
    5. return state: 将获取到的布尔值状态(TrueFalse)返回给调用者。
  • 处理失败情况
    1. else:: 如果 status.code 不是 0,或者 status 属性本身就不存在,说明获取失败。
    2. self.logger.error(...): 记录一条失败日志。
    3. return None: 返回 None,明确地告诉调用者本次操作失败了。

异常处理 (Exception Handling)

异常处理部分与 SetOutput 的逻辑完全相同,都是捕获 grpc.RpcError 和通用的 Exception,在发生任何网络或意外错误时,记录日志并返回 None

总结

GetInput 函数同样是一个封装良好、逻辑严谨的客户端方法。它通过 Optional[bool] 的返回类型,清晰地区分了“成功读取到 False”和“读取失败”这两种情况。在处理响应时,它通过多次使用 hasattr 进行防御性编程,确保了即使在服务器响应不完全规范的情况下,客户端代码也不容易出错,增强了程序的健壮性。

main

main
def main():
    """
    Example usage of GPIOService
    """
    import time
    
    # Configure logging
    logging.basicConfig(level=logging.INFO, format='%(asctime)s; %(levelname)s; %(name)s; %(message)s')
    
    # Example server address
    server_addr = "192.168.2.61:50050"
    
    try:
        print("GPIO Service Example")
        print("=" * 50)
        print(f"Connecting to server: {server_addr}")
        
        # Create channel (this would normally be provided by the main application)
        channel = grpc.insecure_channel(server_addr)
        
        # Wait for channel to be ready with better error handling
        try:
            grpc.channel_ready_future(channel).result(timeout=10)
            print("✓ Connection established successfully")
        except grpc.FutureTimeoutError:
            print("✗ Connection timeout - server may not be running")
            print("Please ensure the gRPC server is running on localhost:50050")
            print("You can start the server with: ./grpc_server")
            return
        except Exception as e:
            print(f"✗ Connection failed: {e}")
            return
        
        # Create GPIO service
        gpio_service = GPIOService(channel, verbose=True)
        
        # Simple connection test first
        print("\nTesting basic connection...")
        
        # --- Test Cases ---
        
        # 1. Toggle TEST_GPIO_0
        print("\n1. Testing SetOutput with TEST_GPIO_0 (toggle ON -> OFF)")
        print("-" * 20)
        success_on = gpio_service.SetOutput("TEST_GPIO_0", True)
        print(f"  Set ON: {'Success' if success_on else 'Failed'}")
        time.sleep(1)
        success_off = gpio_service.SetOutput("TEST_GPIO_0", False)
        print(f"  Set OFF: {'Success' if success_off else 'Failed'}")

        # 2. Test another output
        print("\n2. Testing SetOutput with TEST_GPIO_1")
        print("-" * 20)
        success_gpio1 = gpio_service.SetOutput("TEST_GPIO_1", True)
        print(f"  Set ON: {'Success' if success_gpio1 else 'Failed'}")

        # 3. Test an input
        print("\n3. Testing GetInput with TEST_GPIO_4")
        print("-" * 20)
        input_state = gpio_service.GetInput("TEST_GPIO_4")
        if input_state is not None:
            print(f"  Input State: {input_state}")
        else:
            print("  Failed to get input state.")

        print("\n" + "=" * 50)
        print("All tests completed.")
        
    except Exception as e:
        print(f"\n✗ Example failed: {e}")
        import traceback
        traceback.print_exc()

这个 main 函数是整个 gpio_service.py 脚本的入口点和示例演示。当您在命令行中直接运行 python3 gpio_service.py 时,Python 解释器就会从这个函数开始执行。

它的核心功能可以概括为以下几个步骤:

  1. 环境准备 (Setup)
  2. 连接服务器 (Connection)
  3. 实例化服务 (Instantiation)
  4. 执行测试用例 (Execution)
  5. 异常处理 (Error Handling)

让我们逐块来分析代码:

1. 环境准备 (Setup)

# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
import time

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s; %(levelname)s; %(name)s; %(message)s')

# Example server address
server_addr = "192.168.2.61:50050"
  • import time: 导入 time 模块,主要是为了在测试中加入 time.sleep(1) 这样的延时,以便观察 GPIO 状态的变化。
  • logging.basicConfig(...): 配置全局日志系统。这里设置了日志的最低显示级别为 INFO,并定义了我们之前讨论过的、以分号分隔的日志输出格式。
  • server_addr = ...: 定义了 gRPC 服务器的 IP 地址和端口,这是客户端需要连接的目标。

2. 连接服务器 (Connection)

# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
# Create channel (this would normally be provided by the main application)
channel = grpc.insecure_channel(server_addr)

# Wait for channel to be ready with better error handling
try:
    grpc.channel_ready_future(channel).result(timeout=10)
    print("✓ Connection established successfully")
except grpc.FutureTimeoutError:
    print("✗ Connection timeout - server may not be running")
    # ...
    return
  • channel = grpc.insecure_channel(server_addr): 创建一个到服务器的 gRPC 通道 (Channel)insecure 表示这是一个未加密的连接。这个 channel 对象是所有后续通信的基础。
  • grpc.channel_ready_future(channel).result(timeout=10): 这是一个非常关键且健壮的连接检查步骤。它不会立即假设连接成功,而是会等待最多 10 秒,看通道是否能真正进入“就绪”状态。
    • 如果 10 秒内成功连接,程序继续执行。
    • 如果 10 秒后仍然无法连接(FutureTimeoutError),它会打印出非常友好的错误提示,告诉用户服务器可能没有运行,并提前退出程序 (return)。这避免了在连接失败时出现后续更复杂的错误。

3. 实例化服务 (Instantiation)

# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
# Create GPIO service
gpio_service = GPIOService(channel, verbose=True)
  • 在确认连接成功后,它创建了我们 GPIOService 类的一个实例(Instance)
  • 它将已经就绪的 channel 传递给构造函数。
  • 它设置 verbose=True,这意味着 GPIOService 内部的 DEBUG 级别的日志(例如我们看到的 JSON 请求体)将会被打印出来。

4. 执行测试用例 (Execution)

这是 main 函数的主体部分,它按顺序调用 gpio_service 对象的不同方法来演示和测试其功能。

# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
# 1. Toggle TEST_GPIO_0
success_on = gpio_service.SetOutput("TEST_GPIO_0", True)
time.sleep(1)
success_off = gpio_service.SetOutput("TEST_GPIO_0", False)

# 2. Test another output
success_gpio1 = gpio_service.SetOutput("TEST_GPIO_1", True)

# 3. Test an input
input_state = gpio_service.GetInput("TEST_GPIO_4")
  • 测试 1:演示了如何开关一个 GPIO。先调用 SetOutputTEST_GPIO_0 设置为 True(开),等待 1 秒,再调用 SetOutput 将其设置为 False(关)。
  • 测试 2:演示了如何控制另一个 GPIO (TEST_GPIO_1)。
  • 测试 3:演示了如何使用 GetInput 来读取一个 GPIO (TEST_GPIO_4) 的状态,并对返回结果进行判断(if input_state is not None:)。

5. 异常处理 (Error Handling)

# file: /mnt/c/Users/ezhuzix/test-interface-client/gpio_service.py
except Exception as e:
    print(f"\n✗ Example failed: {e}")
    import traceback
    traceback.print_exc()
  • 整个 main 函数的主体逻辑都被一个大的 try...except 块包围。这确保了如果在执行过程中的任何一步(从连接到测试)发生了任何意料之外的错误,程序都不会直接崩溃。
  • 它会捕获这个异常,打印出一条失败信息,并使用 traceback.print_exc() 打印出完整的错误堆栈,这对于定位和修复问题至关重要。

总结

main 函数不仅是一个简单的函数,它更是一个自包含的、健壮的、用户友好的小型应用程序。它清晰地展示了如何正确地:

  1. 建立和验证 gRPC 连接。
  2. 实例化 GPIOService 类。
  3. 使用 SetOutputGetInput 方法与硬件交互。
  4. 处理各种可能的运行时错误。

因此,它既是 GPIOService 类的一个实际用例,也是一个非常有用的诊断和测试工具

posted @ 2026-03-13 15:34  mo686  阅读(4)  评论(0)    收藏  举报