Skip to content

Instantly share code, notes, and snippets.

@nikotan
Last active January 1, 2019 10:08
Show Gist options
  • Save nikotan/5c52005722baab7c3d87402b5e906be8 to your computer and use it in GitHub Desktop.
Save nikotan/5c52005722baab7c3d87402b5e906be8 to your computer and use it in GitHub Desktop.
IoT BOT to monitor BLE tags using ESP32
#include <codecvt>
#include <string>
#include <cassert>
#include <locale>
#include <esp_system.h>
#include <rom/rtc.h>
#include <EEPROM.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
#include <aJSON.h>
struct BLEADD
{
uint8_t a[6];
};
#define DEBUG
#define MONITOR_NAME "m01"
#define SCAN_TIME 20
#define SLEEP_TIME 60
#define EEPROM_SIZE 512
#define MAX_TAGS 16
#define WLAN_SSID "{your_ssid}"
#define WLAN_PSWD "{your_password}"
#define API_HOST "{api_host}"
#define API_PATH "{api_path}"
#define API_KEY "{api_key}"
#define IFTTT_HOST "maker.ifttt.com"
#define IFTTT_PATH "/trigger/{your_event}/with/key/{your_key}"
#define API_ROOT_CA "{api_root_ca}"
#define IFTTT_ROOT_CA "{ifttt_root_ca}"
WiFiClientSecure client;
void doScanBLETag();
void doSendResult();
void updateAPI(BLEScanResults results);
void sendIftttEvent(char* name, int action);
void softReset();
void deepSleep();
String convAddress(BLEAddress address);
String UTF16toUTF8(String str);
std::string utf16_to_utf8(std::u16string const& src);
void setup()
{
delay(500);
#ifdef DEBUG
// デバッグ用にシリアルを開く
Serial.begin(115200);
delay(500);
Serial.println("[start setup()]");
#endif
// reset理由をチェック
RESET_REASON r = rtc_get_reset_reason(0);
#ifdef DEBUG
Serial.print("CPU0 reset reason = ");
Serial.println(r);
delay(100);
#endif
if (r != 12)
{
// software reset 以外の場合
doScanBLETag();
}
else
{
// software reset の場合
doSendResult();
}
}
void loop()
{
}
void doScanBLETag()
{
#ifdef DEBUG
Serial.println("[start doScanBLETag()]");
delay(100);
#endif
// BLEスキャン
//esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
BLEDevice::init("");
BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan->setActiveScan(false);
#ifdef DEBUG
Serial.println("BLE Scanning...");
delay(100);
#endif
BLEScanResults scanResults = pBLEScan->start(SCAN_TIME);
uint8_t nTags = scanResults.getCount();
#ifdef DEBUG
Serial.print("BLE Scan done! (");
Serial.print(nTags);
Serial.println(" devices found)");
delay(100);
#endif
if (nTags > MAX_TAGS) nTags = MAX_TAGS;
#ifdef DEBUG
Serial.println("Update EEPROM...");
delay(100);
#endif
// EEPROMを準備
if (!EEPROM.begin(EEPROM_SIZE))
{
#ifdef DEBUG
Serial.println("failed to initialise EEPROM");
delay(1000);
#endif
deepSleep();
}
// スキャン結果をEEPROMに保存
EEPROM.put<uint8_t>(0, nTags);
if (nTags > 0)
{
for (uint8_t i=0; i<nTags; i++)
{
unsigned char* addr = *scanResults.getDevice(i).getAddress().getNative();
BLEADD buf;
for (int j=0; j<6; j++)
{
buf.a[j] = addr[j];
}
EEPROM.put<BLEADD>(sizeof(uint8_t) + i * sizeof(BLEADD), buf);
}
}
EEPROM.commit();
#ifdef DEBUG
Serial.println("succeeded!");
delay(100);
#endif
softReset();
}
void doSendResult()
{
#ifdef DEBUG
Serial.println("[start doSendResult()]");
delay(100);
#endif
// EEPROMを準備
if (!EEPROM.begin(EEPROM_SIZE))
{
#ifdef DEBUG
Serial.println("failed to initialise EEPROM");
delay(1000);
#endif
deepSleep();
}
// EEPROMからスキャン結果を読取
#ifdef DEBUG
Serial.println("Read EEPROM...");
delay(100);
#endif
uint8_t nTags;
EEPROM.get<uint8_t>(0, nTags);
if (nTags > MAX_TAGS) nTags = MAX_TAGS;
else if (nTags < 0) nTags = 0;
#ifdef DEBUG
Serial.print("number of BLE tags = ");
Serial.println(nTags);
delay(100);
#endif
String data = "{\"monitor_name\":\"" + String(MONITOR_NAME) + "\",\"tag_detected\":[";
BLEADD address;
if (nTags > 0)
{
for (uint8_t i=0; i<nTags; i++)
{
EEPROM.get<BLEADD>(sizeof(uint8_t) + i * sizeof(BLEADD), address);
data += "\"" + convAddress(address) + "\",";
}
data = data.substring(0, data.length()-1);
}
data += "]}";
#ifdef DEBUG
Serial.print("data = ");
Serial.println(data);
delay(100);
#endif
// WiFi接続
WiFi.mode(WIFI_STA);
WiFi.setAutoConnect(false);
WiFi.setAutoReconnect(false);
#ifdef DEBUG
Serial.print("Try to connect to SSID: ");
Serial.println(WLAN_SSID);
#endif
int counter = 0;
WiFi.begin(WLAN_SSID, WLAN_PSWD);
while (WiFi.status() != WL_CONNECTED) {
#ifdef DEBUG
Serial.print(".");
#endif
delay(1000);
counter++;
if (counter > 15)
{
#ifdef DEBUG
Serial.println("\n(wifiConnect) failed!");
delay(100);
#endif
softReset();
}
}
#ifdef DEBUG
Serial.print("Connected to ");
Serial.print(WLAN_SSID);
Serial.print(" (IP = ");
Serial.print(WiFi.localIP());
Serial.println(")");
#endif
// AWSへ送信
// HTTPクライアント準備
client.setCACert(API_ROOT_CA);
// HTTPリクエスト
char buf[1024];
if (!client.connect(API_HOST, 443))
{
#ifdef DEBUG
Serial.println("Connection failed");
delay(100);
#endif
}
else
{
#ifdef DEBUG
Serial.println("Connected to API");
delay(100);
#endif
// HTTPリクエストを送信
sprintf(buf, "POST %s HTTP/1.1\n", API_PATH);
client.print(buf);
sprintf(buf, "Host: %s\n", API_HOST);
client.print(buf);
sprintf(buf, "x-api-key: %s\n", API_KEY);
client.print(buf);
client.print("User-Agent: ESP32/1.0\n");
client.print("Connection: close\n");
client.print("Content-Type: application/json\n");
sprintf(buf, "Content-Length: %d\n\n", data.length());
client.print(buf);
client.print(data.c_str());
// レスポンスを読み込み
// レスポンスヘッダを読み飛ばす
char cbuf0 = '\0';
char cbuf1 = '\0';
char cbuf2 = '\0';
while (client.connected())
{
char c = client.read();
if (cbuf0 == '\r' && cbuf1 == '\n' && cbuf2 == '\r' && c == '\n')
{
break;
}
cbuf0 = cbuf1;
cbuf1 = cbuf2;
cbuf2 = c;
}
// レスポンスボディを読み込む
int i = 0;
while (client.available()) {
buf[i++] = client.read();
}
buf[i] = '\0';
String body = String(buf);
body = UTF16toUTF8(body);
body.toCharArray(buf, sizeof(buf));
#ifdef DEBUG
Serial.println("--- response body ---");
Serial.println(body);
Serial.println("------");
delay(100);
#endif
}
// HTTP切断
if (client.connected())
{
client.stop();
}
// レスポンスボディをJSONパース
aJsonObject* root = aJson.parse(buf);
if (root == NULL)
{
#ifdef DEBUG
Serial.println("aJson.parse() failed");
delay(100);
#endif
}
else
{
aJsonObject* updated = aJson.getObjectItem(root, "updated");
if (updated != NULL)
{
unsigned char num = aJson.getArraySize(updated);
for (unsigned char i=0; i<num; i++)
{
aJsonObject* elem = aJson.getArrayItem(updated, i);
if (elem != NULL)
{
aJsonObject* o_name = aJson.getObjectItem(elem, "name");
aJsonObject* o_action = aJson.getObjectItem(elem, "action");
if (o_action->valueint >= 0)
{
#ifdef DEBUG
Serial.print("send IFTTT event [");
Serial.print(o_name->valuestring);
Serial.print(" => ");
Serial.print(o_action->valueint);
Serial.println("]");
delay(100);
#endif
sendIftttEvent(o_name->valuestring, o_action->valueint);
}
}
}
}
}
deepSleep();
}
// software reset
void softReset()
{
#ifdef DEBUG
Serial.println("Going to software reset now...");
delay(100);
#endif
//esp_restart();
ESP.restart();
}
// deep sleep
void deepSleep()
{
#ifdef DEBUG
Serial.println("Going to deep sleep now...");
delay(100);
#endif
//esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
//esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
//esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
//esp_sleep_pd_config(ESP_PD_DOMAIN_MAX, ESP_PD_OPTION_OFF);
esp_sleep_enable_timer_wakeup(SLEEP_TIME * 1000 * 1000);
esp_deep_sleep_start();
}
// BLEタグMACアドレスを書式変換
String convAddress(BLEADD address)
{
String out = "";
for (int i=0; i<6; i++)
{
String tmp = String(address.a[i], HEX);
if(tmp.length() == 1) tmp = "0" + tmp;
out += tmp + ":";
}
out = out.substring(0, out.length()-1);
out.toUpperCase();
return out;
}
void sendIftttEvent(char* name, int action)
{
// HTTPクライアント準備
client.setCACert(IFTTT_ROOT_CA);
if (!client.connect(IFTTT_HOST, 443))
{
#ifdef DEBUG
Serial.println("Connection failed");
delay(100);
#endif
}
else
{
#ifdef DEBUG
Serial.println("Connected to IFTTT");
delay(100);
#endif
char buf[256];
char data[256];
if (action == 1)
{
sprintf(buf, "%sが自宅に着きました (%s)", name, MONITOR_NAME);
}
else
{
sprintf(buf, "%sが自宅から出ました (%s)", name, MONITOR_NAME);
}
int data_len = sprintf(data, "{\"value1\":\"%s\",\"value2\":\"%s\",\"value3\":%d}", buf, name, action);
#ifdef DEBUG
Serial.println("--- request body ---");
Serial.println(data);
delay(100);
#endif
sprintf(buf, "POST %s HTTP/1.1\n", IFTTT_PATH);
client.print(buf);
sprintf(buf, "Host: %s\n", IFTTT_HOST);
client.print(buf);
client.print("User-Agent: ESP32/1.0\n");
client.print("Connection: close\n");
client.print("Content-Type: application/json\n");
sprintf(buf, "Content-Length: %d\n\n", data_len);
client.print(buf);
client.print(data);
// レスポンスを読み込み
// レスポンスヘッダを読み飛ばす
char cbuf0 = '\0';
char cbuf1 = '\0';
char cbuf2 = '\0';
while (client.connected())
{
char c = client.read();
if (cbuf0 == '\r' && cbuf1 == '\n' && cbuf2 == '\r' && c == '\n')
{
break;
}
cbuf0 = cbuf1;
cbuf1 = cbuf2;
cbuf2 = c;
}
// レスポンスボディを読み込む
int i = 0;
while (client.available()) {
buf[i++] = client.read();
}
buf[i] = '\0';
#ifdef DEBUG
Serial.println("--- response body ---");
Serial.println(buf);
Serial.println("------");
delay(100);
#endif
}
// 切断
if (client.connected())
{
client.stop();
}
}
//******************************************
String UTF16toUTF8(String str)
{
str.replace("\\u","\\");
str += '\0';
uint16_t len = str.length();
char16_t utf16code[len];
int i=0;
String str4 = "";
for(int j=0; j<len; j++){
if(str[j] == 0x5C){ //'\'を消去
j++;
for(int k=0; k<4; k++){
str4 += str[j+k];
}
utf16code[i] = strtol(str4.c_str(), NULL, 16); //16進文字列を16進数値に変換
str4 = "";
j = j+3;
i++;
}else if(str[j] == 0x23){ //'#'を消去
utf16code[i] = 0xFF03; //全角#に変換
i++;
}else{
utf16code[i] = (char16_t)str[j];
i++;
}
}
std::u16string u16str(utf16code);
std::string u8str = utf16_to_utf8(u16str);
String ret_str = String(u8str.c_str());
//URLに影響のある特殊文字を全角に変換
ret_str.replace("+", "+");
ret_str.replace("&", "&");
ret_str.replace("\\", "¥");
return ret_str;
}
//*****************************************************
std::string utf16_to_utf8(std::u16string const& src)
{
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter;
return converter.to_bytes(src);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment