JSONなどのモジュールを追加(RPiベアメタルMicroPython)

Raspberry Pi版MicroPythonについてMicroPythonフォーラムで紹介したところ、「rshellが使えるかもね~」という反応がありました。

rshellについては使ったことが無かったので、調べてみたところbinasciiモジュールが必要なようです。
そこで、binasciiモジュールと、そのほかいくつかのモジュールを新たに組み込みました。

モジュールの組み込みには、開発は必要ないのですが、ちゃんと動作することは確認が必要なので、これまでは後回しにしていました。
しかし、MicroPythonのテストが自動で行えるようになったので、モジュールの組み込みを割と気軽に行えるようになりました。

今回有効にした主なモジュールはubinascii、uctypes、ujson、urandom、ureなどです。

このうち、ujsonについてちょっと手間取ったのでメモしておきます。

(1)UNICODEサポートを有効にする

ujsonはUNICODEのサポートを前提とします。ujsonのテスト(/micropython/tests/extmod/ujson_loads.py)には

my_print(json.loads('"abc\\uabcd"'))

というコードが含まれており、\uabcdは0xABCDという文字コードに変換されなければなりません。(ただしUTF-8ではこの文字は0xEA 0xAF 0x8D (eaaf8d)というバイト列にエンコードされます)
そのためにはMICROPY_PY_BUILTINS_STR_UNICODEを有効にする必要があります。
有効にしていない場合、エラーにはなりませんがマルチバイトの文字が1バイトのデータになってしまいます。

(2)MICROPY_STREAMS_NON_BLOCKを有効にする
[2018/7/30追記:この項目の内容をIssuesに申告したところ、テストコードが修正されました。従って、以下の事象はもう起こりません。]
ujsonのテスト(/micropython/tests/extmod/ujson_dump_iobase.py)には以下のようなコードが含まれています:

class S(io.IOBase):
    def __init__(self):
        self.buf = ''
    def write(self, buf):
        if type(buf) == bytearray:
            # uPy passes a bytearray, CPython passes a str
            buf = str(buf, 'ascii')
        self.buf += buf

s = S()
json.dump([123, {}], s) 
print(s.buf)

このwriteメソッドの最後のところで、書き込んだバイト数をreturnしていません。
そのため、writeは常にNoneを返し、その結果EAGAIN(書き込めなかったので後で再度書き込んでほしい、というエラー)になります。

ですので、このテストはjson.dump()のところで必ずエラーになってしまうのですが、unixやstm32のMicroPythonではエラーになりませんでした。
コードを追ってみた結果、オプションMICROPY_STREAMS_NON_BLOCKを1に設定することで、このエラーを回避できることが分かりました。
これはpy/stream.hの中で以下のように定義されているためです。

#if MICROPY_STREAMS_NON_BLOCK
#define mp_is_nonblocking_error(errno) ((errno) == MP_EAGAIN || (errno) == MP_E\
WOULDBLOCK)
#else
#define mp_is_nonblocking_error(errno) (0)
#endif

これは、blocking I/Oで書き込みに失敗した場合は故障などの可能性が高いですが、non blocking I/Oなら一時的に書き込み不可なケースは有り得て、書き込みに失敗しても再チャレンジすればいいので、エラーで中止する必要はない、ということのように思われます。

ただ、そもそもwriteに対してNoneが返るのは、ストリームが書き込み可能ではなく、書き込みが行えなかった場合なので、このテストのように常にnoneを返すのは間違っている気がします。

最後に、現時点でのテスト結果をコピペしておきます。

相変わらずprint_exceptionでfailしていますが、これもよく調べたらunix portではpassするので、何かオプションを変更すれば通るような気がします。

MicroPythonのオプションは必ずしも独立ではなく、いろいろ依存関係あるので(コンパイルできないわけではないですが)、整理する方法が欲しいですね。

$ ./run-tests --target minimal --device /dev/ttyUSB0 -b 115200
(略)
554 tests performed (15283 individual testcases)
553 tests passed
82 tests skipped: builtin_compile builtin_override builtin_pow3 builtin_pow3_intbig builtin_range_binop builtin_round_int builtin_round_intbig bytes_partition class_delattr_setattr class_inplace_op class_notimpl class_reverse_op exception_chain fun_name generator_name io_buffered_writer namedtuple_asdict parser slice_attrs string_center string_partition string_rpartition string_splitlines subclass_native_call subclass_native_init sys_getsizeof btree1 machine1 machine_pinbase machine_pulse machine_signal ubinascii_crc32 ucryptolib_aes128_cbc ucryptolib_aes128_ecb ucryptolib_aes128_ecb_enc ucryptolib_aes128_ecb_inpl ucryptolib_aes128_ecb_into ucryptolib_aes256_cbc ucryptolib_aes256_ecb uhashlib_sha1 uhashlib_sha256 urandom_extra ure_groups ure_span ussl_basic uzlib_decompio uzlib_decompio_gz uzlib_decompress websocket_basic cmath_fun_special float2int_doubleprec_intbig float_divmod float_parse_doubleprec math_domain_special math_fun_special heapalloc_bytesio2 meminfo memstats native_closure native_const_intbig native_misc opt_level viper_addr viper_args viper_binop_arith viper_binop_comp viper_binop_comp_imm viper_binop_divmod viper_binop_multi_comp viper_cond viper_error viper_import viper_misc viper_misc_intbig viper_ptr16_load viper_ptr16_store viper_ptr32_load viper_ptr32_store viper_ptr8_load viper_ptr8_store viper_subscr rge_sm
1 tests failed: print_exception

コメント