1

我正在使用 cwiid 库,这是一个用 C 编写的库,但在 python 中使用。该库允许我使用 Wiimote 来控制机器人上的一些电机。该代码在没有监视器、键盘或鼠标的嵌入式设备上作为守护程序运行。

当我尝试初始化对象时:

import cwiid

while True:
    try:
        wm = cwiid.Wiimote()
    except RuntimeError:
        # RuntimeError exception thrown if no Wiimote is trying to connect

        # Wait a second
        time.sleep(1)

        # Try again
        continue

99% 的时间,一切正常,但有时,库会进入某种奇怪的状态,调用cwiid.Wiimote()导致库将“套接字连接错误(控制通道)”写入标准错误,并且 python 抛出异常. 发生这种情况时,每次后续调用都会cwiid.Wiimote()导致将相同的内容写入 stderr,并引发相同的异常,直到我重新启动设备。

我想要做的是检测到这个问题,并让 python 自动重启设备。

如果 cwiid 库处于怪异状态,它抛出的异常类型也是RuntimeError,这与连接超时异常(这很常见)没有什么不同,所以我似乎无法以这种方式区分它。我要做的是在运行后立即读取 stderrcwiid.Wiimote()以查看是否出现消息“套接字连接错误(控制通道)”,如果出现,则重新启动。

到目前为止,我可以使用一些os.dup()os.dup2()方法重定向stderr以防止消息出现,但这似乎并不能帮助我阅读stderr。

如果您使用子进程运行某些东西,则大多数在线示例都涉及读取 stderr,这在这种情况下不适用。

我怎么能去阅读 stderr 来检测写入它的消息?

我想我正在寻找的是这样的:

while True:
    try:
        r, w = os.pipe()
        os.dup2(sys.stderr.fileno(), r)

        wm = cwiid.Wiimote()
    except RuntimeError:
        # RuntimeError exception thrown if no Wiimote is trying to connect

        if ('Socket connect error (control channel)' in os.read(r, 100)):
            # Reboot

        # Wait a second
        time.sleep(1)

        # Try again
        continue

这似乎并不像我认为的那样起作用。

4

2 回答 2

1

作为与 stderr 战斗的替代方法,在放弃之前快速连续重试几次(应该处理连接错误)如何:

while True:
    for i in range(50):  # try 50 times
        try:
            wm = cwiid.Wiimote()
            break        # break out of "for" and re-loop in "while"
        except RuntimeError:
            time.sleep(1)
    else:
        raise RuntimeError("permanent Wiimote failure... reboot!")
于 2016-12-01T00:49:27.803 回答
0

subprocess幕后,除了 dups 之外,还使用匿名管道来重定向子进程输出。要让进程读取自己的标准错误,您需要手动执行此操作。它涉及获取匿名管道、将标准错误重定向到管道的输入、运行有问题的标准错误写入操作、从管道的另一端读取输出以及清理所有内容。这一切都很繁琐,但我认为我在下面的代码中得到了它。

您的调用的以下包装器cwiid.Wiimote将返回一个元组,该元组由函数调用返回的结果(None如果是RuntimeError)和生成的 stderr 输出(如果有)组成。请参阅该tests功能,了解它应该如何在各种条件下工作。cwiid.Wiimote我尝试调整您的示例循环,但不太明白调用成功时会发生什么。在您的示例代码中,您只需立即重新循环。

编辑:哎呀!修复了在example_loop()whereWiimote被调用而不是作为参数传递的错误。

import time

import os
import fcntl

def capture_runtime_stderr(action):
    """Handle runtime errors and capture stderr"""
    (r,w) = os.pipe()
    fcntl.fcntl(w, fcntl.F_SETFL, os.O_NONBLOCK)
    saved_stderr = os.dup(2)
    os.dup2(w, 2)
    try:
        result = action()
    except RuntimeError:
        result = None
    finally:
        os.close(w)
        os.dup2(saved_stderr, 2)
        with os.fdopen(r) as o:
            output = o.read()
    return (result, output)

## some tests

def return_value():
    return 5

def return_value_with_stderr():
    os.system("echo >&2 some output")
    return 10

def runtime_error():
    os.system("echo >&2 runtime error occurred")
    raise RuntimeError()

def tests():
    print(capture_runtime_stderr(return_value))
    print(capture_runtime_stderr(return_value_with_stderr))
    print(capture_runtime_stderr(runtime_error))
    os.system("echo >&2 never fear, stderr is back to normal")

## possible code for your loop

def example_loop():
    while True:
        (wm, output) = capture_runtime_stderr(cmiid.Wiimote)
        if wm == None:
            if "Socket connect error" in output:
                raise RuntimeError("library borked, time to reboot")
            time.sleep(1)
            continue
        ## do something with wm??
于 2016-12-01T18:01:52.643 回答