Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

バイト列の扱い方について #635

Open
sktometometo opened this issue Jun 27, 2023 · 17 comments
Open

バイト列の扱い方について #635

sktometometo opened this issue Jun 27, 2023 · 17 comments

Comments

@sktometometo
Copy link

euslisp で Python3 の bytes 型のようにバイト列を扱う場合、どのような表現方法があるでしょうか?

また、バイト列をパースして数値や文字列へパースしたり、また数値や文字列をバイト列へエンコードする際に、Pythonでのstructモジュールに当たるようなユーティリティ関数やライブラリがあれば使いたいです。

@sktometometo sktometometo changed the title byte array の扱い方について バイト列の扱い方について Jun 27, 2023
@sktometometo
Copy link
Author

sktometometo commented Jun 27, 2023

数値のlistとしてのバイト列表現 <-> 文字列 の変換としては以下のようにかける

$ (coerce "あいう" cons)
(227 129 130 227 129 132 227 129 134)
$ (coerce '(227 129 130 227 129 132 227 129 134) string)
"あいう"

@sktometometo
Copy link
Author

文字列のバイト列が所定の長さになるように0パディングするには以下のようにかける

4.irteusgl$ (setq byte-array (coerce "あいう" cons))
(227 129 130 227 129 132 227 129 134)
5.irteusgl$ (setq target-length 64)
64
6.irteusgl$ (concatenate cons byte-array (make-list (- target-length (length byte-array)) :initial-element 0))
(227 129 130 227 129 132 227 129 134 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)

@sktometometo
Copy link
Author

rosのmessage型でuint8[]に渡す場合、stringで渡すようになっている。
たとえば audio_common_msgs/AudioData のような場合、 data フィールドは string で渡されるし、msg を作るときに cons でなくて string で渡さないと適切にパースされなさそう。

@sktometometo
Copy link
Author

@k-okada Is there any files related to byte array parsing and generating? If so, I would like to add some utility functions to there like

(defun padding-byte-array (byte-array target-length)
  (concatenate cons byte-array (make-list (- target-length (length byte-array)) :initial-element 0)))

according to the review in jsk-ros-pkg/jsk_robot#1830

@k-okada
Copy link
Member

k-okada commented Jun 27, 2023

最初のコメントしか見ていないけど,こういうこと?

$ setq s "    "
"    "
$ setq b 12.34
12.34
$ (sys::poke b s 0 :float)
12.34
$ (dotimes (i (length s) (format t "~%")) (format t "~x" (elt s i)))
a4704541

use https://www.scadacore.com/tools/programming-calculators/online-hex-converter/ to convert hex to float

@sktometometo
Copy link
Author

そんな感じのことがしたいのですが、バイト列をパースする際にバイトオーダー(LSBかMSBか)やsigned or unsigned, 精度等を指定してパースをしたいと考えていて、かつこのようなバイト列と数値との変換はeuslispの数値表現とは必ずしも一致しないために何らかのパース処理が必要かと考えています。

バイナリデータの読み込みを行う際にはファイルからにせよ他のプロセスからにせよこのような類の処理が必要になるのでなにかしらすでに実装されていればその部分の処理を利用したり、追加・変更したいと思っていて、既存のeuslispにはそのようなライブラリが存在するでしょうか?

あと、この例における 12.34 のバイト列表現である "\244" はjmanual の 二章に以下にあるバイトにおける表現という理解で正しいですか?

他の Lisp と同様に、型の決まったデータオブジェクトは変数ではない。どの変数もその値として、どんなオ
ブジェクトも持つことができる。変数にオブジェクトの型を宣言することが可能であるが、一般的にコンパイ
ラで高速なコードを生成するための情報としてのみ使用される。数値は、ポインタの中で直接値として表現さ
れ、そのほかは、ポインタにより参照されるオブジェクトにより表現される。 Sun4 で実行する場合、ポイン
タおよび数値は図 2 で描かれているように long word で表現される。ポインタの LSB の 2 ビットは、ポイン
タ・ integer ・ float を識別するための tag ビットとして使用されている。ポインタは tag ビットが全てゼロであ
り、オブジェクトのアドレスとして 32 ビット全て使用できるので、 EusLisp は 4GB 以上のアドレス空間を利
用することができる

@k-okada
Copy link
Member

k-okada commented Jun 27, 2023

% 2個上の例題間違えていたので直しました.
これは,
https://github.com/euslisp/EusLisp/blob/9b1a896af3dad4a245d97d745130939ca2814f6b/lisp/c/sysfunc.c#L642-L676
なので,普通にCPU上のバイト列なんだとおもいます.jmanual の記述はC言語内でLispオブジェクトの表現の説明なので,CPU上のバイト列とは違うのかな.

で,peek/poke は結局eusの型でしか出てこないので,float/double やbyte/int/long は対応できるけど,singned / unsigned は対応出来なそうですね.またバイトオーダもCPU依存になりそう.

とは言え,バイトオーダはLSBでsigned で,と運用する(少なくともeusが絡む場合は)としちゃえば,問題ないんじゃない.と思うのと,このパースするための情報はどうやって渡すの?別途2つのシステム間でフォーマット情報を受け渡しするの?

@sktometometo
Copy link
Author

これは,
https://github.com/euslisp/EusLisp/blob/9b1a896af3dad4a245d97d745130939ca2814f6b/lisp/c/sysfunc.c#L642-L676
なので,普通にCPU上のバイト列なんだとおもいます.jmanual の記述はC言語内でLispオブジェクトの表現の説明なので,CPU上のバイト列とは違うのかな.

なるほど、理解しました。smart_device_ros (まだ名前変えてない) で絡むマシンは ESP32 も x86_64 も LSBっぽいので結局そのままでいいかもです。

パース情報はこのリポジトリでは C用、Python用で パーサ/シリアライザ を個別に書いています。十分にながければStringでJSONみたいなやつ渡してしまいたいのですが組み込み側で簡単なふうに処理を書いてしまいました。

@k-okada
Copy link
Member

k-okada commented Jun 28, 2023 via email

@sktometometo
Copy link
Author

ここでは ros-drivers/rosserial#569 は使用していないです。ESP32は本体と優先で接続して、ESP32が他のデバイスと無線通信するインターフェースになっているので、

イメージ的には

PC -- USB -- ESP32 interface デバイス (このスケッチ を書き込んだM5Stack) .... ESP_NOW ... ESP32 other device

みたいな接続になっています。

@k-okada
Copy link
Member

k-okada commented Jun 28, 2023

あ,そこではなくて,rosserial はマイコンとPCの間の通信はどうしているか?ということでした.
https://github.com/ros-drivers/rosserial/blob/noetic-devel/rosserial_client/src/rosserial_client/make_library.py
とかみると,ROSのシリアライズを行っているね.

手書きでパーサを書いてよいのは,基本は送られるデータ型が決まっている場合で,それ以外は使わないで運用してください.という場合が良いです.手書きでバーサを書くけど仕様を決めてあるので後はユーザで任意のデータの送受信をしてください,は結局使われないです.
なので,ROSのメッセージなりJsonなりで書いておくのが良いです.

あるいは,std_msgs だけは対応します,とか,自分で簡易的なフォーマット決めて最大4個のリストで,ネストは許さなくて,文字列は255固定,とか,で,"isfd"+binary(123)+string("hello")+binary(123.4)+binary(24555.4544) みたいなストリングを送るとか,

と思ったけど,使っているのは
https://github.com/sktometometo/esp_now_ros/blob/c0807250bfb6671aa8d5045378820ea451a5c7dc/python/esp_now_ros/packet_parser.py
なのかな.ここでパースしていれば,eusとepsの通信はrosのメッセージ経由でbinaryをパースする必要はないのでは.

ちなみに,こういう風にパケットのデザインをしている場合はちゃんとその情報をhttps://tldp.org/LDP/tlk/net/net.html みたいに図解しておきましょう.

@sktometometo
Copy link
Author

sktometometo commented Jun 28, 2023

JSONか所定のフォーマットを前に記述するやり方でやってみます。
以下はESP_NOWのフレームを説明する図なのですが(公式ページ をもとに作成)、bodyの先頭2bitを https://github.com/sktometometo/esp_now_ros/blob/master/msg/Packet.msg で定義されたパケットタイプにしていて、その後のbitをどうパースするかはREADME.mdに記述したフレームの定義にしたがってパーサ/シリアライザを実装していました。

esp_now_ros drawio

データフィールドの並びだけでなく、そのパケットが何用途向けなのかがわからないと、float x 3 の並びのセンサデータがセンサの認識したオブジェクトの位置なのか、加速度センサの出力した三軸加速度なのかがわからなくて、パケットのデータが何であるかを説明するフィールドそのものは必要かと思うので、そうなるとフレームの構成としては

| パケットの説明を示すフィールド | データ部分の構造を示すフィールド | データ部分 |

のような構成にしてみようと思います。

@sktometometo
Copy link
Author

ただ、シリアライザやパーサの実装そのものは共通化できる一方で、パース後のデータを利用するプログラムは自分の使いたいパケットの説明に対応したパース後のデータ構造を知っている必要がある気がしていて、それは結局README.mdに今記述しているフレームの構造そのものではないかと思いました。

そう考えるとフレームのうちデータフィールドの構造を記述している部分は冗長で、その結果いまのような形をとっている部分はありました。

フレームの説明とパース結果のなんとなくのデータから自分が使いたいデータ形式への一般的な変換方法があれば、プログラム側で詳細なデータ構造について知っている必要はないのかな

@sktometometo
Copy link
Author

と思ったけど,使っているのは
https://github.com/sktometometo/esp_now_ros/blob/c0807250bfb6671aa8d5045378820ea451a5c7dc/python/esp_now_ros/packet_parser.py
なのかな.ここでパースしていれば,eusとepsの通信はrosのメッセージ経由でbinaryをパースする必要はないのでは.

rosserialで上記のESP_NOWパケットのbodyを直にpublishしているので、subscribeするプログラムで body をなんらかの形でパースする必要はあります。
( /esp_now_ros/send トピックと /esp_now_ros/recv トピック )

今のPythonプログラムではユーザーはこれらのトピックを直にやりとりせず、ESPNOWROSInterface というクラスを通じてトピックのやり取りをするようにしているのですが、 https://github.com/sktometometo/esp_now_ros/blob/c0807250bfb6671aa8d5045378820ea451a5c7dc/node_scripts/esp_now_packet_printer.py#L9-L11 のように結局 parse_packet() 関数でデータをパースする必要があります。

@Naoki-Hiraoka
Copy link
Contributor

要求機能を正確に把握していないため今回使えるかは分からないのですが、body部分のフォーマットをROSのトピックのパケットのフォーマットと共通にして、ROSのトピックのデシリアライザを使えば、bitから任意のデータ形式への変換・publishを一般的に行えるのではないかと思いました.
https://tech.aptpod.co.jp/entry/2020/08/07/120000

@Naoki-Hiraoka
Copy link
Contributor

Naoki-Hiraoka commented Jun 29, 2023

あ、ROSのトピックのデシリアライザの話は既に岡田先生のコメントで出ていて、ROSのトピックはデータフィールドの構造を記述している部分が冗長だから別の手法を検討しているという流れになっていることを見落としていました。

@k-okada
Copy link
Member

k-okada commented Jun 29, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants