ESP-IDFを使ってみる

XML Parser


ESP-IDFにはUndocumentな機能が沢山有ります。
ESP-IDFのコンポーネントには、こちらのExpat XML Parser(Version2.4.8)が含まれています。
APIはこ ちらにまとまっています。

サンプルコードがこ ちらに公開されています。
サンプルコード(test_expat.c)のこ の部分を変更するだけで、サンプルコードを試すことができます。
TEST_CASE("Expat parses XML", "[expat]")
 ↓
void app_main()

このライブラリを使うためには、事前に3つのCallBack関数を用意する必要が有ります。
これらのCallBack関数はAPIだけは決まっていますが、関数の中の処理はユーザが自由に決めることができます。

static void XMLCALL start_element(void *userData, const XML_Char *name, const XML_Char **atts)
この関数は開始タグを発見した時に呼び出されるCallBackです。
nameにタグ名が渡ってきます。

static void XMLCALL end_element(void *userData, const XML_Char *name)
この関数は終了タグを発見した時に呼び出されるCallBackです。
nameにタグ名が渡ってきます。

static void data_handler(void *userData, const XML_Char *s, int len)
この関数は開始タグと、終了タグに挟まれているデータを発見した時に呼び出されるCallBackです。
sにデータの文字列、lenに文字列の長さが渡ってきます。

例えば<title>Page title</title>のMXLをParseする場合、
start_element(&userData, "title", &&atts);
data_handler(&userData, "Page title", 10);
end_element(&userData, "title");
の順に呼ばれます。

<html><title>Page title</title><body><h>header</h>< /body></html>のXMLをParseする場合、
start_element(&userData, "html", &&atts);
start_element(&userData, "title", &&atts);
data_handler(&userData, "Page title", 10);
end_element(&userData, "title");
start_element(&userData, "body", &&atts);
start_element(&userData, "h", &&atts);
data_handler(&userData, "header", 6);
end_element(&userData, "h");
end_element(&userData, "body");
end_element(&userData, "html");
の順に呼ばれます。


Parse対象の文字列は、
XML_Parse(parser, test_in, strlen(test_in), 1));
を使って指定します。

例えば、InternetからRSSを読む場合、一度に全てのRSSデータを読みことができません。
もし、Parse対象の文字列が複数に分割されている場合は、
XML_Parse(parser, test_1st, strlen(test_1st), 0));
XML_Parse(parser, test_2nd, strlen(test_2nd), 0));
XML_Parse(parser, test_3rd, strlen(test_3rd), 1));
の様に、最後のフラグで継続か、最終かを示します。

userDataの形式は自由で、サンプルコードでは以下の構造体のポインターを渡しています。
構造体のメンバーは好きに決めることができます。
typedef struct {
    int depth;
    char output[512];
    int output_off;
} user_data_t;

各CallBackの中で、Parseした結果をこの領域に格納していくことになります。
サンプルコードではoutput[]の領域に、Parseした結果を詰めています。



このライブラリを使うと、簡単にRSSページから必要な情報を取り出すことができます。
Yahoo Japanが RSS形式で週刊天気予報を公開していましたが、2022年3月末でサービスを終了してしまいました。
こちらで M5Stackに表示するコードを公開していますが、もう使えません。



ESP-IDF V5から、このライブラリは非標準になりました。
このライブラリを使う場合は、以下のコマンドを使って、Manifest File(idf_component.yml)を作成します。
$ idf.py add-dependency espressif/expat

Manifest File(idf_component.yml)として、プロジェクト内のmainディレクトリに以下のファイルが作られます。
$ cat idf_component.yml
## IDF Component Manager Manifest File
dependencies:
  espressif/expat: "*"
  ## Required IDF version
  idf:
    version: ">=4.1.0"
  # # Put list of dependencies here
  # # For components maintained by Espressif:
  # component: "~1.0.0"
  # # For 3rd party components:
  # username/component: ">=1.0.0,<2.0.0"
  # username2/component2:
  #   version: "~1.0.0"
  #   # For transient dependencies `public` flag can be set.
  #   # `public` flag doesn't have an effect dependencies of the `main` component.
  #   # All dependencies of `main` are public by default.
  #   public: true

Manifest File(idf_component.yml)があると、このファイルに従って、ビルド時に必要なコンポーネントがダウンロードされ、以下のディレクト リに格納されます。
$ ls -l managed_components
合計 4
drwxr-xr-x 5 nop nop 4096  8月  2 14:05 espressif__expat

但し、このままだとESP-IDF V4でビルドした時にも、Manifest File(idf_component.yml)に従ってコンポーネントを追加しようとします。
ESP-IDF V4では、このライブラリは標準ライブラリなので、コンポーネントの追加がエラーになります。
そこで、Manifest File(idf_component.yml)にルールを追加します。
これでESP-IDF V4でもV5でもビルドが通ります。
この技はEspressIFのエンジニアに教えてもらいました。
$ cat idf_component.yml
## IDF Component Manager Manifest File
dependencies:
  espressif/expat:
    version: "^2.4.3"
    rules:
      - if: "idf_version >=5.0"

以下のコマンドでESP-IDF V4ではコンポーネントの追加がスキップされていることが分かります。
$ idf.py reconfigure
Executing action: reconfigure
Running cmake in directory /home/nop/rtos/esp-idf-xml/build
Executing "cmake -G Ninja -DPYTHON_DEPS_CHECKED=1 -DESP_PLATFORM=1 -DCCACHE_ENABLE=0 /home/nop/rtos/esp-idf-xml"...
-- Building ESP-IDF components for target esp32
Skipping optional dependency: espressif/expat
Skipping optional dependency: espressif/expat
-- Project sdkconfig file /home/nop/rtos/esp-idf-xml/sdkconfig

Manifest File(idf_component.yml)には以下のようなルールを適用することができます。
rules:
  - if: "idf_version >=3.3,<5.0" # supports all SimpleSpec grammars (https://python-semanticversion.readthedocs.io/en/latest/reference.html#semantic_version.SimpleSpec)
  - if: "target in [esp32, esp32c3]" # supports boolean operator ==, !=, in, not in.

続く...