2020年3月30日月曜日

SPRESENSEで自作MQTTパブリッシャを動作させる方法

SPRESENSEで自作MQTTパブリッシャを動作させてみようと下記の「MqttPublisherTest.ino」というプログラムを実行したところ, 次のようなエラーに出くわしました.

mosquitto
New connection from 192.168.1.21 on port 1883.
Client  has exceeded timeout, disconnecting.
シリアルモニタ
failed, rc=-4

どうも使っているライブラリが怪しかったので,「SparkfunESP8286WiFi.cpp」というコードの(1)の部分を修正したところ, 上記のエラーは解消することができました, ただ,今度は次のような別のエラーが表示されるようになりました.

mosquitto
New connection from 192.168.1.21 on port 1883.
New client connected from 192.168.1.21 as ESP8266Client-0 (p2, c1, k15).
Socket error on client ESP8266Client-0, disconnecting.
シリアルモニタ
failed, rc=-1

処理内容を細かくチェックすると,MQTTブローカからの返信を処理していない箇所があったので, 「SparkfunESP8286WiFi.cpp」というコードの(2)の部分を追加したところ, シリアルモニタのエラーは解消することができました.

mosquitto
New connection from 192.168.1.21 on port 1883.
New client connected from 192.168.1.21 as ESP8266Client-5165 (p2, c1, k15).
Socket error on client ESP8266Client-5165, disconnecting.
シリアルモニタ
なし

引き続き処理内容を細かくチェックすると,コネクションの状態を返すべきところがWiFiの接続状態を返している箇所があったので, 「SparkfunESP8286Client.cpp」というコードの(3)の部分を修正したところ, ようやく期待通りの動作をさせることができました.

#include <SparkFunESP8266WiFi.h>
#include <PubSubClient.h>
const char* SSID = "XXXXXXXXXXXXX";
const char* PSK = "XXXXXXXXXXXXX";
const char* MQTT_BROKER = "X.X.X.X";
const int MQTT_PORT = 1883;
const char* MQTT_TOPIC = "suppa";
const int PUBLISH_INTERVAL_SEC = 1;
const int MAX_NUMBER_OF_PUBLISH = 60;
ESP8266Client client;
PubSubClient mqttClient(client);
time_t lastPublishedTime = 0;
int numberOfPublishes = 0;
void setup() {
Serial.begin(115200);
initializeESP8266();
connectESP8266();
displayConnectInfo();
mqttClient.setServer(MQTT_BROKER, MQTT_PORT);
}
void loop() {
if (!mqttClient.connected()) {
reconnect();
}
mqttClient.loop();
time_t currentTime = time(NULL);
if (currentTime - lastPublishedTime >= 1) {
numberOfPublishes++;
String payload(numberOfPublishes, DEC);
mqttClient.publish(MQTT_TOPIC, payload.c_str());
lastPublishedTime = currentTime;
}
}
void initializeESP8266() {
if (!esp8266.begin(115200)){
Serial.println(F("Error talking to ESP8266."));
exit(0);
}
Serial.println(F("ESP8266 Shield Present"));
}
void connectESP8266() {
if (esp8266.getMode() != ESP8266_MODE_STA) {
if (esp8266.setMode(ESP8266_MODE_STA) < 0) {
Serial.println(F("Error setting mode."));
exit(0);
}
}
Serial.println(F("Mode set to station"));
if (esp8266.status() <= 0) {
Serial.print(F("Connecting to "));
Serial.println(SSID);
if (esp8266.connect(SSID, PSK) < 0) {
Serial.println(F("Error connecting"));
exit(0);
}
}
}
void displayConnectInfo() {
char connectedSSID[24];
memset(connectedSSID, 0, 24);
int retVal = esp8266.getAP(connectedSSID);
if (retVal > 0) {
Serial.print(F("Connected to: "));
Serial.println(connectedSSID);
}
IPAddress myIP = esp8266.localIP();
Serial.print(F("My IP: ")); Serial.println(myIP);
}
void reconnect() {
while (!mqttClient.connected()) {
Serial.print("Attempting MQTT connection...");
String clientId = "ESP8266Client-";
clientId += String(random(0xffff), HEX);
if (mqttClient.connect(clientId.c_str())) {
Serial.println("connected");
}
else {
Serial.print("failed, rc=");
Serial.print(mqttClient.state());
exit(0);
}
}
}
uint8_t ESP8266Client::connected()
{
// If data is available, assume we're connected. Otherwise,
// we'll try to send the status query, and will probably end
// up timing out if data is still coming in.
/* modified part (2)
status()の戻り値はWiFiの接続状態で,コネクションの状態ではない..
このため,コネクションが張られているにも関わらず,
張られていないと判断されて自らコネクションをクローズしてしまっていた感じ.
コネクションの接続状態を示すesp8266._status.statをチェックして接続中であれば1を返すコードを追加.
if (_socket == ESP8266_SOCK_NOT_AVAIL)
return 0;
else if (available() > 0)
return 1;
else if (status() == ESP8266_STATUS_CONNECTED)
return 1;
*/
int clientStatus = status();
if (_socket == ESP8266_SOCK_NOT_AVAIL)
return 0;
else if (available() > 0)
return 1;
else if (esp8266._status.stat == ESP8266_STATUS_CONNECTED) {
return 1;
}
else if (clientStatus == ESP8266_STATUS_CONNECTED)
return 1;
/* modified part (2) */
return 0;
}
int16_t ESP8266Class::tcpSend(uint8_t linkID, const uint8_t *buf, size_t size)
{
if (size > 2048)
return ESP8266_CMD_BAD;
char params[8];
sprintf(params, "%d,%d", linkID, size);
sendCommand(ESP8266_TCP_SEND, ESP8266_CMD_SETUP, params);
int16_t rsp = readForResponses(RESPONSE_OK, RESPONSE_ERROR, COMMAND_RESPONSE_TIMEOUT);
//if (rsp > 0)
if (rsp != ESP8266_RSP_FAIL)
{
/* modified part (1)
print関数だと,buf内のデータをテキストとして扱ってしまい,その内容を改変してしまうケースがあるみたい.
buf内のデータをそのまま送信するには,write関数を使えば良いみたいなので,それに置き換え */
/* print((const char *)buf);.*/
_serial->write(buf, size);
/* modified part (1) */
rsp = readForResponse("SEND OK", COMMAND_RESPONSE_TIMEOUT);
/* added part (2)
readForResponse関数でデータを送信すると,
例えば「+IPD,0,4:XXXX」というようなレスポンスが届く場合があるが,
オリジナルのコードでは,それに対処する処理がなかったので,
後続する処理でレスポンスの受け取りに失敗していた模様.
そのため,レスポンスに対処するコードを追加.
*/
char responseHeader[] = "\r\n\r\n+IPD,,";
int responseHeaderIndex = 0;
int state = 1;
long timeIn = millis();
int dataSize = 0;
while (1) {
if (millis() > timeIn + 500) {
break;
}
if (_serial->available()) {
rsp += readByteToBuffer();
char c = esp8266RxBuffer[bufferHead - 1];
switch (state) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 11:
if (c == responseHeader[responseHeaderIndex]) {
responseHeaderIndex++;
state++;
}
else {
state = 0;
}
break;
case 10:
if (c == linkID + '0') {
state = 11;
}
else {
state = 0;
}
break;
case 12:
if (c == ':') {
state = 13;
}
else {
dataSize = dataSize * 10 + (c - '0');
}
break;
}
if (state == 0 || state == 13) {
break;
}
}
}
/* added part (2) */
if (rsp > 0)
return size;
}
return rsp;
}
uint8_t ESP8266Client::connected()
{
// If data is available, assume we're connected. Otherwise,
// we'll try to send the status query, and will probably end
// up timing out if data is still coming in.
/* modified part (3)
status()の戻り値はWiFiの接続状態で,コネクションの状態ではない..
このため,コネクションが張られているにも関わらず,
張られていないと判断されて自らコネクションをクローズしてしまっていた感じ.
コネクションの接続状態を示すesp8266._status.statをチェックして接続中であれば1を返すコードを追加.
if (_socket == ESP8266_SOCK_NOT_AVAIL)
return 0;
else if (available() > 0)
return 1;
else if (status() == ESP8266_STATUS_CONNECTED)
return 1;
*/
int clientStatus = status();
if (_socket == ESP8266_SOCK_NOT_AVAIL)
return 0;
else if (available() > 0)
return 1;
else if (esp8266._status.stat == ESP8266_STATUS_CONNECTED) {
return 1;
}
else if (clientStatus == ESP8266_STATUS_CONNECTED)
return 1;
/* modified part (3) */
return 0;
}