2012-04-19 19 views
7

Tengo un dispositivo serie del que estoy tratando de leer la entrada. Le envié una cadena "ID \ r", y devuelve "ID XX \ r" (donde \ r es un retorno de carro ASCII, hex 0x0d).Entrada serial con buffer de línea

Dado que la opción eol en serial.readline ya no es compatible, estoy usando TextIOWrapper para leer desde el puerto serie y devolver una línea a la vez.

Mi problema es que, en lugar de devolver la cadena tan pronto como ve el carro, está esperando hasta el doble del tiempo de espera que establecí cuando abrí el puerto serie. Me gustaría devolver la cadena inmediatamente tan pronto como lea una línea completa, ya que puedo tener cientos de estos comandos para enviar al dispositivo y no quiero esperar el tiempo de espera cada vez. Si establezco el tiempo de espera en 0, no obtengo ningún resultado (probablemente porque mi script deja de esperar antes de que el dispositivo tenga la oportunidad de generar algo), y si configuro el tiempo de espera en Ninguno, el script bloquea para siempre.

Aquí es un simple script de prueba:

import serial 
import io 
import time 

ser = serial.Serial("/dev/ttyUSB0", baudrate=9600, 
        bytesize=8, parity='N', stopbits=1, 
        xonxoff=0, rtscts=1, timeout=5) 

sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser), 
         newline=None) 


sio.write(unicode("ID\r")) 
sio.flush() 

print "reading..." 

x = sio.readline() 

print len(x) 
print x 

El guión siempre tarda 10 segundos desde el momento en que dice "leer" hasta que se imprime la cadena "ID XX" que se lee desde el puerto serie.

Estoy seguro de que el dispositivo está enviando el retorno de carro, como yo he utilizado strace para ver las lecturas:

select(4, [3], [], [], {5, 0})   = 1 (in [3], left {4, 991704}) 
read(3, "I", 8192)      = 1 
select(4, [3], [], [], {5, 0})   = 1 (in [3], left {4, 999267}) 
read(3, "D", 8191)      = 1 
select(4, [3], [], [], {5, 0})   = 1 (in [3], left {4, 999420}) 
read(3, " ", 8190)      = 1 
select(4, [3], [], [], {5, 0})   = 1 (in [3], left {4, 999321}) 
read(3, "X", 8189)      = 1 
select(4, [3], [], [], {5, 0})   = 1 (in [3], left {4, 999355}) 
read(3, "X", 8188)      = 1 
select(4, [3], [], [], {5, 0})   = 1 (in [3], left {4, 999171}) 
read(3, "\r", 8187)      = 1 
select(4, [3], [], [], {5, 0})   = 0 (Timeout) 
select(4, [3], [], [], {5, 0})   = 0 (Timeout) 

se puede ver el 2 Seleccione() que dan los tiempos de espera de 10 segundos de retardo , pero también puede ver claramente que se lee el retorno de carro. Intenté establecer el parámetro de nueva línea en 'Ninguno' y '' (que debería permitir automáticamente \ r, \ n y \ r \ n), y en '\ r', pero con el mismo resultado cada vez.

También he intentado establecer el buffer_size en el BufferedRWPair() llame a '1' para evitar que la entrada de amortiguación, pero eso no hacía diferencia.

¿Alguna idea de lo que estoy haciendo mal?

Si no puedo hacer que esto funcione, mi siguiente paso será usar serial.read() para leer un carácter a la vez y hacer mi propia línea de almacenamiento en búfer, pero quería intentar hacerlo "bien" "camino con textiowrapper primero.

+0

¿Estás seguro de que la declaración de impresión no está activando el almacenamiento en búfer de salida? Intenta ejecutarlo con -u –

Respuesta

0

Esto sería difícil de depurar sin estar realmente allí para verlo. Pero mira si puedes usar mi módulo tty.

http://code.google.com/p/pycopia/source/browse/trunk/aid/pycopia/tty.py

Prueba el objeto SerialPort allí. Lo utilicé con éxito para interactuar con instrumentos seriales, donde "ese otro módulo de serie" tenía muchos problemas similares a los que describes. Este también puede decirle si tiene datos en el FIFO.

Déjame ahora cómo va.

0

Gracias por el código Keith, pero quería mantener este código algo portátil, así que me gustaría seguir con el paquete "serie" predeterminado.

Además, como todavía estoy aprendiendo Python, quería intentar aprender a utilizar TextIOWrapper de la manera en que estaba destinado.

Dejé de intentar hacer el trabajo serial.readline(), así que por ahora solo usaré una función simple "readLine" para leer un carácter a la vez y buscaré un terminador de retorno de carro. Aunque si me encuentro con más extravagancia en serie, puedo volver a usar su código.

Gracias!

def readLine(ser): 
    str = "" 
    while 1: 
     ch = ser.read() 
     if(ch == '\r' or ch == ''): 
      break 
     str += ch 

    #"print "str = " + str 

    return str 
6

Perdí unas horas en esto hoy. Resultó que io.BufferedReader lee hasta que llene el búfer y luego pasa a la memoria intermedia io.TextIOWrapper. El tamaño del búfer predeterminado es 8192, por lo que dependiendo de su dispositivo, esto puede llevar un tiempo.

El ejemplo de código correcta debe ser:

# buffer size is 1 byte, so directly passed to TextIOWrapper 
sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser, 1), encoding='ascii') 
print sio.readline()[:-1] 
2

Advertencia: Estoy usando Python 3.4 en un Mac para que su experiencia puede variar, aunque creo que con TextIOWrapper portado a Python 2.7, la situación en Python 2.7 (y otros sistemas operativos) serán esencialmente los mismos que describo a continuación.

El principal problema es que la propia io.TextIOWrapper utiliza un mecanismo de almacenamiento en búfer, controlada por el atributo _CHUNK_SIZE indocumentado. Esto es bastante desagradable. Entonces tiene dos opciones:

  1. Use un tiempo de espera mientras lo intenta. Esto es lo que se insinúa en la documentación de readline en la página de documentación fiscal. Sin embargo, si usa un valor grande (como lo hizo), cuando no hay suficientes datos para llenar el búfer de TextIOWrapper, su código se bloqueará hasta que se alcance el tiempo de espera. Esto es lo que está experimentando en esencia (no ir después de eso que usted tiene que esperar el doble del valor de tiempo de espera, pero creo que esto podría ser resuelto examinado la aplicación de TextIOWrapper y en última instancia, es irrelevante a su pregunta).
  2. La segunda opción es cambiar _CHUNK_SIZE a 1. En su caso, basta con añadir la línea

    sio._CHUNK_SIZE = 1 
    

    a su código justo después de inicializar sio. Esto tiene el efecto quizás desagradable de que el almacenamiento en memoria dentro de TextIOWrapper se apagará (esto se usa para la decodificación incremental de la entrada). Si el rendimiento no es un problema, esta es la solución más simple. Si el rendimiento es un problema, puede establecer un valor bajo de tiempo de espera, sin tocar _CHUNK_SIZE. Sin embargo, en este caso, estar preparado para obtener una cadena vacía de readline() (si el dispositivo que envía una línea en blanco, que vendrá a través como '\ n', por lo que se puede distinguir de la cadena vacía que se obtendrá cuando una lectura se queda sin el tiempo asignado).

Hay otro problema con el código: Cuando sio se eliminará, el método de cierre del Ser se llama dos veces, lo que resultará en una excepción cuando el programa va a estar a punto de terminar (al menos esto es qué sucede si pruebo tu código en mi computadora). Debería crear (parece) instancias de serial.Serial y pasarlas a BufferedRWPair.

También he creado una clase contenedora basada en TextIOWrapper, que también podría publicar, si hay interés, simplemente no quería desechar la respuesta con un código adicional que, estrictamente hablando, no es necesario.

PS: En el mientras tanto, he experimentado con el código en Ubuntu. Mientras estaba en mi Mac, no vi la necesidad de establecer el tamaño del búfer de io.BufferedRWPair en 1, en Ubuntu tuve que hacer esto también, además de configurar _CHUNK_SIZE en 1.

Cuestiones relacionadas