Problemas al enviar una variable char a Arduino desde Python 3

publicado por: Anonymous

En python 2.7 no tenia ningun problema en enviar variables de tipo char, pero por problemas al usar Pyrebase necesito usar Python 3.5, el problema surge al enviar los datos al Arduino, no puedo solucionarlo. El arduino deberia recibir datos de tipo char. Aclaro que estoy trabajando en Raspbian. Les dejo el codigo y el error. gracias de antemano.
Codigo:

import pyrebase

import serial

puerto = serial.Serial('/dev/ttyACM0', 9600)

config = {
  "apiKey": "Axxxxxxxxxxxxxxxxxxxxxxxxx",
  "authDomain": "pxxxxxxxxxx.firebaseapp.com",
  "databaseURL": "https://pxxxxxxxxxxxxxx.firebaseio.com",
  "storageBucket": "pxxxxxxxxxx.appspot.com"
}

puerto = serial.Serial('/dev/ttyACM0', 9600, timeout=1)

firebase = pyrebase.initialize_app(config)

db = firebase.database()

res = db.child("cambio").child("valor1").get()

while True:
    res = db.child("cambio").child("valor1").get()

    if (res == "True"):
        puerto.write('p')
    else:
        puerto.write('q')

Error:

Traceback (most recent call last):
  File "pyreb.py", line 30, in <module>
    puerto.write('q')
  File "/usr/lib/python3/dist-packages/serial/serialposix.py", line 518, in write
    d = to_bytes(data)
  File "/usr/lib/python3/dist-packages/serial/serialutil.py", line 63, in to_bytes
    raise TypeError('unicode strings are not supported, please encode to bytes: {!r}'.format(seq))
TypeError: unicode strings are not supported, please encode to bytes: 'q'

solución

TL;DR: Tienes que invocar el método encode() de tu cadena antes de poder enviarla.

Explicación larga de los cómos y por qués:

Cadenas en Python 2

En python 2, el tipo str representa en realidad una cadena de bytes, más que de caracteres. Lo que ocurría es que cada vez que tu programa usaba una cadena de caracteres, implícitamente el intérprete la manejaba como cadena de bytes, convirtiéndola sobre la marcha.

El problema es que para convertir un carácter a un byte, esto sólo puede hacerse con ciertas codificaciones, como ASCII (pero está restringida al alfabeto inglés), o las normas ISO-8859 (pero hay diferentes normas según el alfabeto usado).

Unicode resuelve el problema rompiendo la equivalencia (1 carácter) -> (1 byte). En Unicode cada carácter ya no es un solo byte. Por ejemplo, usando UTF-8 un carácter puede ocupar 1 byte (si es un ascii, es decir, un código inferior a 128), ó 2 bytes (si es un código entre 128 y 2048), ó 3 bytes (si es un código entre 2048 y 65536), ó 4 bytes (si es un código mayor de 65536).

Python2 “decidía” por tí que codificación usar a la hora de convertir tus cadenas de caracteres en bytes. Una vez decidido, siempre trabajaba con los bytes. Esto podía dar lugar a sorpresas, como esta:

texto = "año"
print len(texto)

y la respuesta podría ser 3 si la codificación elegida era ISO-8859-1 (en la que cada carácter, incluída la ñ, ocupa un solo byte). Pero también podría ser 4, si la codificación elegida era UTF-8 (pues en ésta la ñ, al ser un código mayor de 128, ocupa dos bytes). En realidad python no elige qué codificación usar al azar, sino que la toma de un comentario (obligatorio) que debías poner al inicio del fichero, del tipo: # coding: utf-8 por ejemplo.

Ahora bien, cuando estás haciendo entrada/salida, es posible que los bytes que quieras enviar a la salida no sigan la misma codificación que tu código fuente.

Por ejemplo, tú has escrito el programa python en UTF-8, y por tanto has puesto el comentario # coding: utf-8 al inicio. Después tu programa inicializa la variable texto = "año", lo que implica que texto será una cadena de 4 bytes. Finalmente envías la variable texto a un dispositivo de salida, como por ejemplo un display de la arduino.

¿Qué codificación está esperando ese display? Va a recibir 4 bytes (en concreto sus valores serían: 61, C3, B1, 6F, en hexadecimal). Lo que mostrará el display depende de qué codificación esté esperando:

  • Si el display “entiende” utf-8, decodificará correctamente la secuencia y mostrará "año".
  • Pero si el display espera ISO-8859-1, entonces cada byte representa una letra diferente y mostraría: año (igual has visto esto alguna vez…)

Cadenas en Python3

En python3 se decidió terminar con las conversiones implícitas de caracteres en bytes.

Internamente, python almacena todos los caracteres como “puntos unicode” y no como bytes. Cada carácter da lugar a un solo punto unicode. Así se evitan los problemas antes vistos al calcular len("año"), que podría dar 3 ó 4 según usáramos ISO-8859-1 o UTF-8 (incluso podría dar 6 si usáramos UTF-16, pues en esta codificación cada carácter son dos bytes). En python 3 len("año") siempre da 3, lo que simplifica montones de cosas cuando queremos ajustar la longitud de lo que se muestra a un cierto ancho, por ejemplo.

Pero cuando queramos hacer entrada/salida de una cadena, será necesario convertirla a bytes explícitamente. Es decir, python no lo hará automáticamente por nosotros.

Esto tiene la desventaja de que nuestros programas python2 dejan de ser compatibles, pues hay que hacer explícitas todas las conversiones (también las de entrada, pues cuando leemos algo de un dispositivo leeremos bytes, y si queremos tratarlo como cadena habrá que convertirlo a char). Pero tiene la ventaja de que tenemos control total sobre la codificación usada, especialmente importante cuando nuestros diferentes dispositivos de entrada/salida no usan todos la misma codificación.

En el ejemplo anterior, si sabemos que nuestro display espera ISO-8859-1, aunque nuestro editor haya guardado el texto en UTF-8, eso no importa, ya que lo que guarda python en la variable texto es 3 puntos unicode. Cuando lo vayamos a enviar al display lo “codificamos” a una secuencia de bytes, especificando qué codificación usa el display, por ejemplo así:

codificado = texto.encode("iso-8859-1")

o así:

codificado = texto.encode("latin1")

o, si usara utf-8, así:

codificado = texto.encode("utf8")

Ahora tenemos dos tipos diferentes. texto es de tipo str (que ya no es una secuencia de bytes sino una secuencia de puntos unicode, insisto), y codificado que es de tipo bytes y sería el equivalente a las antiguas cadenas de python2.

Si imprimes por la consola (con print) el valor de codificado, verás que pone una b delante, para indicar que es una cadena de bytes. Por ejemplo:

>>> codificado = texto.encode("iso-8859-1")
>>> print(codificado)
b'axf1o'

La b indica que es una cadena de bytes, y luego vemos la representación de esos bytes. Si el byte en cuestión puede representarse como ASCII (como en la a o la o) lo mostrará “tal cual”. Si no, mostrará x seguido del código hexadecimal del byte (en este caso vemos que la ñ se ha codificado con un byte de valor f1).

Esa secuencia de bytes ya podemos usarla en una operación de entrada/salida, como por ejemplo enviarlo a un fichero, o por un socketo, o por un puerto serie. Eso sí, ten en cuenta si quien está “al otro lado” de la comunicación va a entender el encoding que estás usando. Al menos, al tener que usar encode() de forma explícita, python3 nos obliga a ser conscientes de este problema y a programarlo adecuadamente.

Respondido por: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *