Last active
January 1, 2019 10:08
-
-
Save nikotan/5c52005722baab7c3d87402b5e906be8 to your computer and use it in GitHub Desktop.
IoT BOT to monitor BLE tags using ESP32
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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