y2210681 が 2024年01月31日07時55分53秒 に編集
初版
タイトルの変更
猫などの物体を検知し車のエンジンルームへの侵入による事故を防ぐ距離センサを用いたデバイス
タグの変更
センシング
IoT
Arduino
SPRESENSE
メイン画像の変更
記事種類の変更
製作品
本文の変更
## 1. 概要 猫などの車のエンジンルームへの侵入による事故は冬を中心に19件にも上ります[1]。 これらの事故により猫の命が失われたり、車へのダメージが懸念されます。 なお、これまでの先行事例では超音波によって猫を寄せ付けなくする方法や[2]、散水によって寄せ付けなくする方法[3]などがありますが今回の方法ではデータを送ることによってユーザーに猫の侵入の可能性を認知させるようにし事故の発生を未然に防ごうとするものとなります。 猫の命を守りたい!! 車の寿命を縮めたくない!! といった思いこの提案を提出します。 ## 2.使用したもの #### spresense メインボード #### spresense 拡張ボード #### spresense カメラモジュール #### spresense wifiモジュール #### シャープ測距モジュール GP2Y0A21YK 距離が近ければ近いほど高い電圧を返します。 #### ブレッドボード ## 3.提案手法 ![キャプションを入力できます](https://camo.elchika.com/2d9882c8e265f847f1a3b03c415de15f4f08036a/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f61353039336436652d663264342d343237392d613266632d3165393835343636333365322f33396161373438622d306439342d343639332d393732382d626166363636663333393433/) SPRESENSEと各種モジュールを用いて猫がいる可能性が高いときにクラウドサービスCLIP Viewer Liteにデータをおくることによって小動物のエンジンルームへの侵入を防ぎます。 具体的には、まずセットアップ段階ではある時間の間距離センサで返された電圧の値を配列に格納し、格納された値の平均値をとりその電圧値から一定の値よりも大きくなった時物体が通ったと判断してCLIP Viewer Liteにデータをおくります。 また、同時にカメラモジュールによりその時の写真を撮りmicroSDにデータを保存する。このことによって物体検知したときにその物体がただのゴミや木くずであったのか、それとも小動物であったのかを後から確認することができます。 ## 4.回路図 ![キャプションを入力できます](https://camo.elchika.com/fb91338430f547c979d32dddc128801e50167d9b/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f61353039336436652d663264342d343237392d613266632d3165393835343636333365322f34646166653761612d353539662d343565652d393739382d626162653164626362613735/) ## 5.ソースコードと実行例 下にSPRESENSEに書き込んだArduino言語によるソースコードを記載します。 下記「xxxxxxx」部分には、接続先のエンドポイント名を記入してください。 ``` #include <AmbientGs2200.h> #include <GS2200AtCmd.h> #include <GS2200Hal.h> #include <HttpGs2200.h> #include <MqttGs2200.h> #include <TelitWiFi.h> /** * wifi_sample_simple.ino - Wi-Fi Add-onボードiS110BでCLIP Viewer Lite(クラウドサービス)に * Wi-Fi通信を使ってデータ転送を行うサンプルプログラム * Copyright 2023 CRESCO LTD. */ #include <GS2200Hal.h> #include <GS2200AtCmd.h> #include <TelitWiFi.h> #include "wifi_config.h" #include "certificates.h" // SPI通信最大データ長 #define WIFI_SPI_DATA_MAX (1400) // BULKデータ最大サイズ #define WIFI_BULK_SIZE_MAX (1024) // BULKデータ転送試行最大回数 #define BULK_TX_TRY_MAX (10) // BULKデータ転送試行間隔 (ミリ秒) #define BLUK_TX_RETRY_INTERVAL (1000) // PIN定義:LED(プログラム状態) #define LED_RUN PIN_LED0 // PIN定義:LED(Wi-Fi接続状態) #define LED_WIFI PIN_LED1 // PIN定義:LED(送信状態) #define LED_SEND PIN_LED2 // 復帰値定義: 正常 #define RET_OK (0) // 復帰値定義: 異常 #define RET_ERROR (-1) // 復帰値定義: パラメタ異常 #define RET_INVALID_PARAM (-10) // 復帰値定義: メモリ不足 #define RET_NO_MEMORY (-11) // 復帰値定義: Wi-Fi未接続 #define RET_NO_WIFI (-12) // 復帰値定義: HTTPステータスコード異常 #define RET_HTTP_STATUS_NG (-13) // 証明書登録名:サーバー証明書 #define CERT_NAME_CA ("SSL_CLIP_CA") // 証明書登録名:クライアント証明書 #define CERT_NAME_CLIENT ("SSL_CLIP_CLIENT") // 証明書登録名:秘密鍵 #define CERT_NAME_KEY ("SSL_CLIP_KEY") // SNTPサーバー名 // 参照: https://jjy.nict.go.jp/tsp/PubNtp/index.html #define SNTP_HOST_NAME ("ntp.nict.jp") // 接続先エンドポイント名 #define ENDPOINT_HOST_NAME ("xxxxxxx") // 接続先エンドポイントポート番号 #define ENDPOINT_PORT (8443) // 外部参照 (GS2200-WiFi内GS2200AtCmd.cpp) extern uint8_t *RespBuffer[]; extern int RespBuffer_Index; // 外部参照 (GS2200-WiFi内GS2200Hal.cpp) extern uint8_t ESCBuffer[]; extern uint32_t ESCBufferCnt; // Wi-Fi制御用インスタンス TelitWiFi gs2200; // Wi-Fi用設定 TWIFI_Params gsparams; // Wi-Fi用コマンド作成領域 static char gs2200_cmd[128]; // Wi-Fi用コマンド応答受信領域 static char gs2200_resp[128]; // デバイスID(MACアドレス)保持領域 static String device_id; // Wi-Fi Add-onボードとの通信用作業領域 static uint8_t work_buf[WIFI_SPI_DATA_MAX]; /* * camera.ino - Simple camera example sketch * Copyright 2018, 2022 Sony Semiconductor Solutions Corporation */ #include <SDHCI.h> #include <stdio.h> /* for sprintf */ #include <unistd.h> #include <Camera.h> #define BAUDRATE (115200) #define TOTAL_PICTURE_COUNT (200) #define SIZE (300) SDClass theSD; int take_picture_count = 0; float a[SIZE]; float sum; int i; float avg; /** * Print error message */ void printError(enum CamErr err) { Serial.print("Error: "); switch (err) { case CAM_ERR_NO_DEVICE: Serial.println("No Device"); break; case CAM_ERR_ILLEGAL_DEVERR: Serial.println("Illegal device error"); break; case CAM_ERR_ALREADY_INITIALIZED: Serial.println("Already initialized"); break; case CAM_ERR_NOT_INITIALIZED: Serial.println("Not initialized"); break; case CAM_ERR_NOT_STILL_INITIALIZED: Serial.println("Still picture not initialized"); break; case CAM_ERR_CANT_CREATE_THREAD: Serial.println("Failed to create thread"); break; case CAM_ERR_INVALID_PARAM: Serial.println("Invalid parameter"); break; case CAM_ERR_NO_MEMORY: Serial.println("No memory"); break; case CAM_ERR_USR_INUSED: Serial.println("Buffer already in use"); break; case CAM_ERR_NOT_PERMITTED: Serial.println("Operation not permitted"); break; default: break; } } /** * Callback from Camera library when video frame is captured. */ void CamCB(CamImage img) { /* Check the img instance is available or not. */ if (img.isAvailable()) { /* If you want RGB565 data, convert image data format to RGB565 */ img.convertPixFormat(CAM_IMAGE_PIX_FMT_RGB565); /* You can use image data directly by using getImgSize() and getImgBuff(). * for displaying image to a display, etc. */ /*serial.printのところもとに戻す*/ // /*Serial.print("Image data size = ");*/ // Serial.print(img.getImgSize(), DEC); // Serial.print(" , "); // /*Serial.print("buff addr = ");*/ // Serial.print((unsigned long)img .getImgBuff(), HEX); // Serial.println(""); } else { Serial.println("Failed to get video stream image"); } } /** * @brief Initialize camera */ void setup() { int ret; Serial.begin(BAUDRATE); // LED初期設定 pinMode(LED_RUN, OUTPUT); digitalWrite(LED_RUN, HIGH); pinMode(LED_WIFI, OUTPUT); digitalWrite(LED_WIFI, LOW); pinMode(LED_SEND, OUTPUT); digitalWrite(LED_SEND, LOW); // Wi-Fiモジュール初期設定 if (initialize_wifi_module() != RET_OK) { start_error_routine(); } // Wi-Fi接続処理 wifi_connect(); digitalWrite(LED_WIFI, HIGH); // 時刻補正処理 do { bool is_connected; ret = time_sync_routine(); // Wi-Fi切断時は再接続 is_wifi_connected(is_connected); if (is_connected == false) { digitalWrite(LED_WIFI, LOW); wifi_connect(); digitalWrite(LED_WIFI, HIGH); } } while (ret != RET_OK); CamErr err; /* Open serial communications and wait for port to open */ while (!Serial) { ; /* wait for serial port to connect. Needed for native USB port only */ } /* Initialize SD */ while (!theSD.begin()) { /* wait until SD card is mounted. */ Serial.println("Insert SD card."); } /* begin() without parameters means that * number of buffers = 1, 30FPS, QVGA, YUV 4:2:2 format */ Serial.println("Prepare camera"); err = theCamera.begin(); if (err != CAM_ERR_SUCCESS) { printError(err); } /* Start video stream. * If received video stream data from camera device, * camera library call CamCB. */ Serial.println("Start streaming"); err = theCamera.startStreaming(true, CamCB); if (err != CAM_ERR_SUCCESS) { printError(err); } /* Auto white balance configuration */ Serial.println("Set Auto white balance parameter"); err = theCamera.setAutoWhiteBalanceMode(CAM_WHITE_BALANCE_DAYLIGHT); if (err != CAM_ERR_SUCCESS) { printError(err); } /* Set parameters about still picture. * In the following case, QUADVGA and JPEG. */ Serial.println("Set still picture format"); err = theCamera.setStillPictureImageFormat( CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V, CAM_IMAGE_PIX_FMT_JPG); if (err != CAM_ERR_SUCCESS) { printError(err); } for (i=0;i<SIZE;i+=1){ int sensorValue = analogRead(A0); a[i] = sensorValue; sum += a[i]; delay(10); } avg = sum / SIZE; for (i=0;i<SIZE;i+=1){ Serial.println(a[i]); if(i%10==1){ printf("\n"); } } Serial.println("avg:"); Serial.println(avg); } /** * @brief Take picture with format JPEG per second */ void loop() { int sensorValue = analogRead(A0); Serial.println(sensorValue); delay(1000); /* You can change the format of still picture at here also, if you want. */ /* theCamera.setStillPictureImageFormat( * CAM_IMGSIZE_HD_H, * CAM_IMGSIZE_HD_V, * CAM_IMAGE_PIX_FMT_JPG); */ /* This sample code can take pictures in every one second from starting. */ if (sensorValue > avg + 100) { /* Take still picture. * Unlike video stream(startStreaming) , this API wait to receive image data * from camera device. */ Serial.println("call takePicture()"); CamImage img = theCamera.takePicture(); /* Check availability of the img instance. */ /* If any errors occur, the img is not available. */ /* Create file name */ char filename[16] = {0}; sprintf(filename, "PICT%03d.JPG", take_picture_count); Serial.print("Save taken picture as "); Serial.print(filename); Serial.println(""); theSD.remove(filename); File myFile = theSD.open(filename, FILE_WRITE); myFile.write(img.getImgBuff(), img.getImgSize()); myFile.close(); Serial.println("Take picture"); take_picture_count++; } int ret; byte payload[16]; bool is_connected; // Wi-Fi切断時は再接続 is_wifi_connected(is_connected); if (is_connected == false) { digitalWrite(LED_WIFI, LOW); wifi_connect(); digitalWrite(LED_WIFI, HIGH); } // データ準備 // すべて0で設定 memset(payload, 0, sizeof(payload)); for (int i = 0; i < 4; i++) { payload[sizeof(payload) - 1 - i] = (sensorValue >> (i * 8)) & 0xFF; } // 送信するデータを表示 Serial.print("Payload: "); for (int i = 0; i < sizeof(payload); i++) { Serial.print(payload[i], HEX); //HEX:16進数出力 Serial.print(" "); } digitalWrite(LED_SEND, HIGH); // データ送信 if (send_data(payload, sizeof(payload)) == RET_OK) { Serial.println("[sample]completed sending."); } else { Serial.println("[sample]failed sending."); } Serial.println(""); digitalWrite(LED_SEND, LOW); } /** * @brief エラー時処理 */ void start_error_routine(void) { digitalWrite(LED_RUN, LOW); Serial.flush(); sleep(1); exit(1); } /** * @brief Wi-Fiモジュール初期設定 * @retval RET_OK 成功 * @retval RET_ERROR 失敗 */ int initialize_wifi_module(void) { // Wi-Fi子機設定 Init_GS2200_SPI_type(iS110B_TypeC); gsparams.mode = ATCMD_MODE_STATION; gsparams.psave = ATCMD_PSAVE_DEFAULT; if (gs2200.begin(gsparams)) { // 初期設定エラー (Wi-Fi子機設定失敗) Serial.println("[sample]cannnot initialize iS110B."); return RET_ERROR; } // MACアドレス取得とデバイスID生成 device_id = String(); memset(gs2200_resp, 0, sizeof(gs2200_resp)); while (Get_GPIO37Status()) { AtCmd_RecvResponse(); } if (AtCmd_NMAC_Q(gs2200_resp) == ATCMD_RESP_OK) { char *ptr = gs2200_resp; while (*ptr != NULL) { char c = *ptr; ptr++; if (c == ':') { continue; } if (isAlphaNumeric(c)) { device_id.concat((char)tolower(c)); } } } else { // 初期設定エラー (MACアドレス取得失敗) Serial.println("[sample]cannnot get mac address."); return RET_ERROR; } // HTTPS接続設定 if (setup_https_connection() != RET_OK) { // 初期設定エラー (HTTPS接続設定失敗) Serial.println("[sample]cannnot register certifications."); return RET_ERROR; } return RET_OK; } /** * @brief HTTPS接続設定 * @retval RET_OK 成功 * @retval RET_ERROR 失敗 */ int setup_https_connection(void) { // HTTPS接続用証明書登録 if (add_certificate(CERT_NAME_CA, server_cert, sizeof(server_cert)) != RET_OK) { return RET_ERROR; } if (add_certificate(CERT_NAME_CLIENT, client_cert, sizeof(client_cert)) != RET_OK) { return RET_ERROR; } if (add_certificate(CERT_NAME_KEY, client_key, sizeof(client_key)) != RET_OK) { return RET_ERROR; } return RET_OK; } /** * @brief 証明書追加 * @param name 登録名 * @param der 証明書(DER形式) * @param length 長さ */ int add_certificate(char *name, const uint8_t *der, const size_t length) { size_t dst_index; size_t src_index; ATCMD_RESP_E resp=ATCMD_RESP_UNMATCH; SPI_RESP_STATUS_E s; uint8_t *tx_buf = work_buf; size_t tx_buf_len = sizeof(work_buf); if (tx_buf_len > WIFI_SPI_DATA_MAX) { tx_buf_len = WIFI_SPI_DATA_MAX; } while (Get_GPIO37Status()) { AtCmd_RecvResponse(); } memset(gs2200_cmd, 0, sizeof(gs2200_cmd)); snprintf(gs2200_cmd, sizeof(gs2200_cmd), "AT+TCERTADD=%s,0,%d,1\r\n", name, length); resp = AtCmd_SendCommand( gs2200_cmd ); if (resp != ATCMD_RESP_OK) { return RET_ERROR; } // フォーマットに合わせて転送 snprintf((char*)tx_buf, tx_buf_len, "%cW", ATCMD_ESC); dst_index = 2; src_index = 0; do { size_t cpy_size = length - src_index; if (cpy_size > tx_buf_len - dst_index) { cpy_size = tx_buf_len - dst_index; } memcpy(tx_buf + dst_index, der + src_index, cpy_size); dst_index += cpy_size; src_index += cpy_size; s = WiFi_Write( (char *)tx_buf, dst_index ); if ( s ==SPI_RESP_STATUS_OK ) { dst_index = 0; } else { return RET_ERROR; } } while (length - src_index > 0); return RET_OK; } /** * @brief Wi-Fi接続 * @retval RET_OK 成功 * @retval RET_ERROR 失敗 */ int wifi_connect(void) { int ret; bool is_connected = false; do { while (Get_GPIO37Status()) { AtCmd_RecvResponse(); } // Wi-Fi接続要求 ret = gs2200.activate_station(WIFI_AP_SSID, WIFI_AP_PASS); if (ret != OK) { sleep(1); continue; } // Wi-Fi接続待ち(最大30秒) for (int count=0; count < 30; count++) { sleep(1); is_wifi_connected(is_connected); if (is_connected) { break; } } } while (is_connected == false); return RET_OK; } /** * @brief Wi-Fi接続状態取得 * @param is_connected [out]接続状態(true:接続中,false:切断中) * @retval RET_OK 成功 * @retval RET_ERROR 失敗 */ int is_wifi_connected(bool &is_connected) { ATCMD_RESP_E resp; ATCMD_NetworkStatus status; is_connected = false; while (Get_GPIO37Status()) { AtCmd_RecvResponse(); } resp = AtCmd_NSTAT(&status); if (resp != ATCMD_RESP_OK) { return RET_ERROR; } if (status.connected) { is_connected = true; } return RET_OK; } /** * @brief Wi-Fiモジュール内の時刻同期処理 * @retval RET_OK 成功 * @retval RET_ERROR 失敗 */ int time_sync_routine(void) { int ret; uint64_t st_time; uint32_t epoch = 0; const uint32_t threshold = 1672531200; while (Get_GPIO37Status()) { AtCmd_RecvResponse(); } // ホスト名からIPアドレスへ変換 memset(gs2200_resp, 0, sizeof(gs2200_resp)); if (AtCmd_DNSLOOKUP(SNTP_HOST_NAME, gs2200_resp) != ATCMD_RESP_OK) { return RET_ERROR; } while (Get_GPIO37Status()) { AtCmd_RecvResponse(); } // 時刻同期要求 memset(gs2200_cmd, 0, sizeof(gs2200_cmd)); ret = snprintf(gs2200_cmd, sizeof(gs2200_cmd)-1, "AT+NTIMESYNC=1,%s,30,0\r\n", gs2200_resp); if (AtCmd_SendCommand(gs2200_cmd) != ATCMD_RESP_OK) { return RET_ERROR; } // 時刻同期待ち(最大30秒) st_time = millis(); do { sleep(1); gettime_from_modulue(epoch); } while (epoch < threshold && (millis() - st_time < 30 * 1000)); if (epoch < threshold) { return RET_ERROR; } return RET_OK; } /** * @brief Wi-Fiモジュールから時間取得 * @param epoch EPOCH時間 * @retval RET_OK 成功 * @retval RET_ERROR 失敗 */ int gettime_from_modulue(uint32_t &epoch) { int ret; uint64_t epoch_ms; while (Get_GPIO37Status()) { AtCmd_RecvResponse(); } memset(gs2200_resp, 0, sizeof(gs2200_resp)); memset(gs2200_cmd, 0, sizeof(gs2200_cmd)); if (AtCmd_SendCommand("AT+GETTIME=?\r\n") != ATCMD_RESP_OK) { return RET_ERROR; } if (RespBuffer_Index == 0) { return RET_ERROR; } // 応答データ解析 char *ptr = (char*)RespBuffer[0]; ptr = strrchr(ptr, ','); if (ptr == NULL) { return RET_ERROR; } ptr++; epoch_ms = strtoull(ptr, NULL, 10); epoch = (uint32_t) (epoch_ms / 1000); return RET_OK; } /** * @brief データ送信 * @param data データの先頭アドレス * @param length データ長 * @retval RET_OK 成功 * @retval RET_ERROR 失敗 */ int send_data(byte* data, size_t length) { int ret; char cid = 0; String body = String(); int ret_func = RET_ERROR; // パラメタチェック if (data == NULL || length == 0) { return RET_INVALID_PARAM; } // フォーマット変換 ret = create_http_body(body, data, length); if (ret != RET_OK) { return RET_NO_MEMORY; } // 接続処理 ret = connect_server(ENDPOINT_HOST_NAME, ENDPOINT_PORT, cid); if (ret != RET_OK) { disconnect_server(0); return RET_ERROR; } // HTTPリクエスト送信 ret = send_http_request(cid, ENDPOINT_HOST_NAME, ENDPOINT_PORT, body.c_str()); if (ret == RET_OK) { ret_func = RET_OK; } // 切断処理 disconnect_server(cid); return ret_func; } /** * @brief サーバーへの接続処理 * @param host [in] 接続先ホスト名 * @param port [in] 接続先ポート番号 * @param cid [out] 接続ID * @retval RET_OK 成功 * @retval RET_ERROR 失敗 */ int connect_server(const char *host, const int port, char &cid) { ATCMD_RESP_E resp; while (Get_GPIO37Status()) { AtCmd_RecvResponse(); } // ホスト名 => IPアドレス変換 memset(gs2200_resp, 0, sizeof(gs2200_resp)); resp = AtCmd_DNSLOOKUP((char*)host, gs2200_resp); if (resp != ATCMD_RESP_OK) { return RET_ERROR; } String ip_str = String(gs2200_resp); String port_str = String(port); resp = AtCmd_NCTCP((char*)ip_str.c_str(), (char*)port_str.c_str(), &cid); if (resp != ATCMD_RESP_OK) { return RET_ERROR; } while (Get_GPIO37Status()) { AtCmd_RecvResponse(); } memset(gs2200_cmd, 0, sizeof(gs2200_cmd)); snprintf(gs2200_cmd, sizeof(gs2200_cmd), "AT+SSLOPEN=%c,%s,%s,%s\r\n", cid, CERT_NAME_CA, CERT_NAME_CLIENT, CERT_NAME_KEY); resp = AtCmd_SendCommand(gs2200_cmd); if (resp != ATCMD_RESP_OK) { disconnect_server(cid); return RET_ERROR; } return RET_OK; } /** * @brief HTTPリクエスト送信 * @param cid [in] 接続ID * @param host [in] ホスト名 * @param port [in] ポート番号 * @param body [in] HTTPボディ * @retval RET_OK 成功 * @retval RET_NO_MEMORY メモリ不足 * @retval RET_ERROR 失敗 */ int send_http_request(const char cid, const char *host, const int port, const char *body) { ATCMD_RESP_E resp; String http_req, http_resp; char *buf = (char*)work_buf; size_t buf_len = sizeof(work_buf); int32_t send_index = 0; uint64_t rcv_st_time; int ret_func = RET_ERROR; bool recv_resp = false; // POST メッセージ設定 if (http_req.concat("POST /topics/clip/wifi?qos=1 HTTP/1.1\r\n") == 0) { return RET_NO_MEMORY; } // HTTP HEADERS設定 if (http_req.concat("Content-Type: application/json\r\n") == 0) { return RET_NO_MEMORY; } memset(buf, 0, buf_len); snprintf(buf, buf_len, "Content-Length: %d\r\n", strlen(body)); if (http_req.concat(buf) == 0) { return RET_NO_MEMORY; } memset(buf, 0, buf_len); snprintf(buf, buf_len, "Host: %s\r\n", host); if (http_req.concat(buf) == 0) { return RET_NO_MEMORY; } // 区切り行設定 if (http_req.concat("\r\n") == 0) { return RET_NO_MEMORY; } // BODYデータ設定 if (http_req.concat(body) == 0) { return RET_NO_MEMORY; } while (Get_GPIO37Status()) { AtCmd_RecvResponse(); } WiFi_InitESCBuffer(); // サイズ制限があるため分割して転送 do { ATCMD_RESP_E atcmd_resp; int32_t send_size = http_req.length() - send_index; if (send_size > WIFI_BULK_SIZE_MAX) { send_size = WIFI_BULK_SIZE_MAX; } while (Get_GPIO37Status()) { atcmd_resp = AtCmd_RecvResponse(); if (atcmd_resp == ATCMD_RESP_ESC_FAIL) { return RET_ERROR; } } // 分割2回目以降は少し時間を開けて実施 if (send_index > 0) { usleep(100 * 1000); } resp = ATCMD_RESP_SPI_ERROR; for (int try_cnt=0; try_cnt < BULK_TX_TRY_MAX; try_cnt++) { resp = AtCmd_SendBulkData(cid, http_req.c_str() + send_index, send_size); if (resp == ATCMD_RESP_OK) { break; } usleep(BLUK_TX_RETRY_INTERVAL * 1000); } if (resp != ATCMD_RESP_OK) { return RET_ERROR; } send_index += send_size; } while(http_req.length() - send_index > 0); // レスポンス受信処理 ret_func = RET_ERROR; rcv_st_time = millis(); do { while (Get_GPIO37Status()) { resp = AtCmd_RecvResponse(); if (resp == ATCMD_RESP_BULK_DATA_RX && Check_CID(cid)) { // レスポンスのステータスコード確認 http_resp.concat((const char*)(ESCBuffer+1)); if (http_resp.length() != ESCBufferCnt -1) { Serial.println("[sample]invalid response."); } if (http_resp.indexOf("200 OK") >= 0) { ret_func = RET_OK; } else { ret_func = RET_HTTP_STATUS_NG; } recv_resp = true; break; } } } while ( !recv_resp && (millis() - rcv_st_time < 30 * 1000)); return ret_func; } /** * @brief サーバーとの切断処理 * @param cid [in] 接続ID * @retval RET_OK 成功 * @retval RET_ERROR 失敗 */ int disconnect_server(const char cid) { ATCMD_RESP_E resp; if (cid) { resp = AtCmd_NCLOSE(cid); } else { resp = AtCmd_NCLOSEALL(); } if (resp != ATCMD_RESP_OK) { return RET_ERROR; } return RET_OK; } /** * @brief HTTPボディデータ作成 * @details CLIP Viewer向けフォーマットへ合わせて作成 * @param body [out] 格納先 * @param data [in] payloadデータ(バイナリ形式) * @param length [in] payloadデータ長 * @retval RET_OK 成功 * @retval RET_NO_MEMORY メモリ不足 */ int create_http_body(String &body, byte* data, size_t length) { int index = 0; char hex_buf[3]; body = String(); if (body.concat("{\"id\":\"") == 0) { return RET_NO_MEMORY; } if (body.concat(device_id) == 0) { return RET_NO_MEMORY; } if (body.concat("\",\"payload\":\"") == 0) { return RET_NO_MEMORY; } for (index = 0; index < length; index++) { snprintf(hex_buf, sizeof(hex_buf), "%02x", data[index]); if (body.concat(hex_buf) == 0) { return RET_NO_MEMORY; } } if (body.concat("\"}") == 0) { return RET_NO_MEMORY; } return RET_OK; } ``` ソースコードに関してはArduinoIDEのサンプルコードcamera.inoと株式会社クレスコ様の提供するクラウドサービスCLIPViewerLiteのサンプルコードwifi_sample_simple.inoをベースにし一部改変したものを用いました。 また、実行例を添付します。 ``` 131 133 132 134 130 457 call takePicture() Save taken picture as PICT005.JPG Take picture Payload: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 C9 [sample]completed sending. 137 ``` 上の実行例では物体が検知されないときはシリアルモニタ内に電圧値のみを返しある電圧値よりも高くなった時、つまり物体が検知されたときtakePicture関数がよびだされ写真が撮られ、CLIP Viewer Liteにデータが送られます。 ### 6.まとめと今後の展望 今回は猫などの小動物のエンジンルームへの侵入を防ぎ、それによる事故を防ぐことをSPRESENSEを用いて提案しました。いかにして物体を検知するかはカメラをもちいた手法や赤外線センサを用いた手法などいろいろ考えましたが、シンプルに距離をはかることによって判断することに落ち着きました。今後の展望としてはCLIP Viewer Lite APIとLINEを用いて物体を検知したときにLINEに通知を送るなどしてユーザーが事故の可能性を認知しやすいように工夫できたらよいと考えます。 #### 参考文献 [1] 本部MA2022-048.” 猫がクルマに入り込んでしまったトラブル1カ月で19件!1年通して発生するトラブル、暖かくなっても引き続きご注意を!”.JAF.2023-2-21. https://jaf.or.jp/common/news/2023/20230221-001 .(ref 2023-9-4) [2] 鈴木有.” 聞こえない人には存在せず 聞こえる人にはつらい音”.NHK.2023-04-06. https://www3.nhk.or.jp/news/special/lifechat/post_132.html .(ref 2023-9-4) [3] おっさんくん.” エンジンルーム ねこ侵入対策 完成”みんカラ.2019-3-3. https://minkara.carview.co.jp/userid/3057595/blog/42570941/ .(ref 2023-9-4)