Arduinoで遊ぶページ
Arduinoで遊んだ結果を残すページです
RSSリーダ
ESP32

概要

Arduino core for the ESP32を使った、RSSリーダの実験です。

簡単にインターネット接続ができるようになったので、とりあえず、ネット上の情報を表示するものとして、RSSリーダのようなものを作ってみました。

有機ELキャラクタディスプレイに表示するので、日本語は表示できませんが、それっぽいものはできました。

RSSドキュメントを解析するために、手抜きのSAX"風"XMLパーサも作ってみました。

動作している様子。

Arduino core for the ESP32のインストールのページはこちら

I2C接続の有機ELキャラクタディスプレイの実験のページはこちら

実験

RSSの解析

RSSというか、XMLを解析するために、手抜きのSAX風XMLパーサを作ってみました。SAX"風"なので、実際にはSAXでもなんでもない、いい加減なものです。ヘッダファイルを入れても、600行程度です。“風"なので、APIの名前も、パラメータも異なるものにしています。

タグ名の最大長は32文字(終端ナル文字含む)です(ソースをいじれば、変更可能)。XML文書にエラーがある場合は、どのように動くかわかりません。

以下のコールバック関数を用意しました。XMLも詳しくはないし、用語の名前とか意味も勘違いしているかもしれません。あくまで、今回の用途には使えるということです。

関数 説明 パラメータ 備考
foundXMLDecl() XMLドキュメントの開始 なし
foudXMLEnd() XMLドキュメントの終了 なし
foundPI() Processing Instructionを見つけた。 Processing Instructionの名前
foundSTag() 開始タグを見つけた タグ名、属性数、属性
foundETag() 終了タグを見つけた タグ名
foundEmptyElemTag() 空要素タグを見つけた。 タグ名、属性数、属性
foundSection() “<!名称"を見つけた。 名称 試験していません。
beginCharacter() 文字(列)の開始 なし
foundCharacter() 文字を見つけた 文字 タグ外の文字、CDATA内の文字。1文字ごとに通知する。
endCharacter() 文字(列)の終了 なし

また、このパーサは、1文字ずつ文字を読み込むようになっています。このための、1文字を読み込む関数も用意する必要があります。最後は、EOFを返す必要があります。今回は、httpGetChar()という関数が該当します。XMLパーサ用のインスタンスを作成するときに、指定します。

parse()というメソッドを呼ぶと、1文字ずつ読み出し、EOFを読み込むまで、処理を続けます。

スケッチ

有機ELキャラクタディスプレイの1行目には固定文字列、2行目は取得した情報を表示するようにしています。情報のバッファリングはほとんどしていない(ディスプレイに表示する20文字分だけ保持)、ウェブサーバに対しては優しくない設計になっています。このため、読み取り中にタイムアウトが発生する可能性もあります。

10行目から22行目あたりを、各自の環境に合わせて、変更してください。contentsToDisplayは、表示したい要素名を指定します。とりあえず、BBCとCNNのRSSは読み込むことができました。

OLED用のライブラリは、こちらから、XMLパーサはこちらからダウンロードできます。このページのスケッチは、XMLパーサのexamplesにも含めてあります。OLED用のライブラリについては、こちらのページも参照してください。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#include "SO2002A_I2C.h"
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include "shoddyxml.h"
 
#define DISPLAY_WIDTH 20
#define DISPLAY_HEIGHT 2
 
const char *ssid = "YOURSSID";
const char *password = "PASSWORD";
const struct site_t {
  char *title;
  char *url;
  char *contentsToDisplay;
} sites[] = {
  {"CNN.com", "http://rss.cnn.com/rss/edition.rss", "title"},
  {"BBC News", "http://feeds.bbci.co.uk/news/rss.xml", "description"},
};
const int delayPerCharacter = 200;
const int delayPerArticle = 1000;
const int delayPerRSS = 10000;
const char label = 0xfc;
 
int itemDepth = 0;
int lastTagMatches = 0;
char displayBuffer[DISPLAY_WIDTH + 1];
char *contentsToDisplay;
 
int httpGetChar();
 
WiFiMulti wifiMulti;
HTTPClient http;
WiFiClient *stream;
SO2002A_I2C oled(0x3c);
shoddyxml x(httpGetChar);
 
void clearDisplayBuffer() {
  for (int i = 0; i < DISPLAY_WIDTH + 1; i++) {
    displayBuffer[i] = ' ';
  }
  displayBuffer[DISPLAY_WIDTH - 1] = label;
}
 
void displayPutChar(char c) {
  displayBuffer[DISPLAY_WIDTH] = c;
  for (int i = 0; i < DISPLAY_WIDTH; i++) {
    displayBuffer[i] = displayBuffer[i + 1];
  }
}
 
void printDisplayBuffer() {
  for (int i = 0; i < DISPLAY_WIDTH; i++) {
    oled.setCursor(i, 1);
    oled.print(displayBuffer[i]);
  }
}
 
void foundXMLDeclOrEnd() {
 
}
 
void foundPI(char *s) {
 
}
 
void foundSTag(char *s, int numAttributes, attribute_t attributes[]) {
  if (strcmp(s, "item") == 0) {
    itemDepth++;
  }
 
  if (strcmp(s, contentsToDisplay) == 0) {
    lastTagMatches = 1;
  } else {
    lastTagMatches = 0;
  }
}
 
void foundETag(char *s) {
  if ((itemDepth == 1) && (strcmp(s, contentsToDisplay) == 0)) {
    for (int i = 0; i < DISPLAY_WIDTH; i++) {
      displayPutChar(' ');
      printDisplayBuffer();
      delay(delayPerCharacter);
    }
 
    clearDisplayBuffer();
    delay(delayPerArticle);
  }
  if (strcmp(s, "item") == 0) {
    itemDepth--;
  }
}
 
void foundEmptyElemTag(char *s, int numAttributes, attribute_t attributes[]) {
 
}
 
void foundCharacter(char c) {
  if ((itemDepth == 1) && (lastTagMatches == 1)) {
    displayPutChar(c);
    printDisplayBuffer();
    delay(200);
  }
}
 
void foundElement(char *s) {
 
}
 
int httpGetChar() {
  if (http.connected()) {
    if (stream->available()) {
      return stream->read();
    } else {
      return 0;
    }
  }
  return EOF;
}
 
void setup() {
  // put your setup code here, to run once:
  oled.begin(DISPLAY_WIDTH, DISPLAY_HEIGHT);
  oled.clear();
 
  /*
    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
  */
 
  wifiMulti.addAP(ssid, password);
 
  clearDisplayBuffer();
 
  x.foundXMLDecl = foundXMLDeclOrEnd;
  x.foundXMLEnd = foundXMLDeclOrEnd;
  x.foundPI = foundPI;
  x.foundSTag = foundSTag;
  x.foundETag = foundETag;
  x.foundEmptyElemTag = foundEmptyElemTag;
  x.foundCharacter = foundCharacter;
  x.foundElement = foundElement;
}
 
void loop() {
  for (int i = 0; i < sizeof(sites) / sizeof(struct site_t); i++) {
    if ((wifiMulti.run() == WL_CONNECTED)) {
      itemDepth = 0;
      lastTagMatches = 0;
 
      oled.clear();
      oled.setCursor(0, 0);
      oled.print(sites[i].title);
      contentsToDisplay = sites[i].contentsToDisplay;
      http.begin(sites[i].url);
      int httpCode = http.GET();
      if (httpCode > 0) {
        if (httpCode == HTTP_CODE_OK) {
          stream = http.getStreamPtr();
          x.parse();
        }
      }
      http.end();
      delay(delayPerRSS);
    } else {
      wifiMulti.addAP(ssid, password);
    }
  }
}

バージョン

Hardware:ESP-WROOM-32
Software:Arduino 1.8.4/Arduino core for the ESP32

最終更新日

June 20, 2020

inserted by FC2 system