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;
}
- 捕获所有异常
- 记录异常详情
- 显示红色错误消息
工作流程
- 验证输入 → 检查别名有效性
- 准备请求 → 构建gRPC请求对象
- 获取UI控件 → 准备结果显示
- 执行调用 → 发送gRPC请求到服务器
- 处理响应 → 检查操作是否成功
- 更新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() |
工作流程
- 验证输入 → 检查别名有效性
- 准备请求 → 构建gRPC请求对象(不含State)
- 获取UI控件 → 准备结果显示
- 执行调用 → 发送gRPC请求到服务器
- 处理响应 → 检查操作是否成功
- 显示状态 → 显示GPIO的当前电平状态
- 更新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_position和verbose这两个配置参数也保存为实例的属性。
# 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)
- 配置日志记录器:
logging.getLogger(self.__class__.__name__):获取一个日志记录器实例。self.__class__.__name__会得到类名(也就是 "GPIOService"),这使得日志输出时可以清晰地看到是哪个类产生的日志。if self.verbose::如果verbose标志为True,则将日志级别设置为logging.DEBUG。这意味着,只有在开启verbose模式时,那些用self.logger.debug(...)记录的详细调试信息才会被显示出来。
总结
总而言之,__init__ 函数就像是 GPIOService 对象的“岗前培训”。它确保在对象开始工作(即调用 SetOutput 或 GetInput)之前,所有必要的工具和配置(如 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 的类型提示,表示这个函数最终会返回一个布尔值(True或False),用于指示操作是否成功。
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
- 构建请求消息:
io_request = pb2.IORequest(): 创建一个IORequest消息对象。这个对象的结构是在test_interface.proto文件中定义的。io_request.session.CopyFrom(self.session_id): 将在__init__中准备好的session_id复制到请求中。io_request.dut_position = self.dut_position: 设置被测设备的位置。io_request.alias = alias.strip(): 设置要控制的 GPIO 别名。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_request和timeout参数传递过去。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.code是0,就记录一条成功信息(例如 "GPIO 'TEST_GPIO_0' set to: 1"),并返回True。 - 如果
code是其他值,说明服务器端发生了错误(例如,我们之前遇到的code=9INVALID_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 函数)提供了一个非常简洁和清晰的接口:只需提供 alias 和 state,就能得到一个明确的成功或失败的结果。
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 调用:调用
stub的GetInput方法,将请求发送到服务器。服务器会执行硬件读取操作,并将结果封装在响应中返回。
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
- 检查响应状态:
if hasattr(response, 'status') and response.status.code == 0:: 这是一个非常健壮(Robust)的检查。它首先用hasattr检查响应对象中是否存在status属性,以防止服务器返回不规范的响应时导致程序崩溃。只有在status属性存在的情况下,它才会去检查status.code是否为0(成功)。- 如果状态码为
0,说明服务器成功读取了 GPIO。 state = response.state if hasattr(response, 'state') else False: 接着,它从响应中提取state字段的值。这里同样使用了hasattr做了一层保护,如果state字段因某种原因缺失,它会默认返回False。self.logger.info(...): 记录成功获取的状态。return state: 将获取到的布尔值状态(True或False)返回给调用者。
- 处理失败情况:
else:: 如果status.code不是0,或者status属性本身就不存在,说明获取失败。self.logger.error(...): 记录一条失败日志。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 解释器就会从这个函数开始执行。
它的核心功能可以概括为以下几个步骤:
- 环境准备 (Setup)
- 连接服务器 (Connection)
- 实例化服务 (Instantiation)
- 执行测试用例 (Execution)
- 异常处理 (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。先调用
SetOutput将TEST_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 函数不仅是一个简单的函数,它更是一个自包含的、健壮的、用户友好的小型应用程序。它清晰地展示了如何正确地:
- 建立和验证 gRPC 连接。
- 实例化
GPIOService类。 - 使用
SetOutput和GetInput方法与硬件交互。 - 处理各种可能的运行时错误。
因此,它既是 GPIOService 类的一个实际用例,也是一个非常有用的诊断和测试工具。

浙公网安备 33010602011771号