mametarou963のアイコン画像
mametarou963 2022年09月26日作成 (2022年09月26日更新) © MIT
製作品 製作品 閲覧数 994
mametarou963 2022年09月26日作成 (2022年09月26日更新) © MIT 製作品 製作品 閲覧数 994

SPRESENSEとELTRESでつくるキッズみまもりシステム

SPRESENSEとELTRESでつくるキッズみまもりシステム

背景

子供や認証の方の行方不明者の人数は令和4年現在でも横ばいに推移しています。
8割以上は発見されていますが、対策が実施されているにも関わらず、値が改善されないことから、
個々人の対策も重要になってくると考えられます。

キッズ向けのGPSは多数発表されていますが、「spresenseが省電力」であること、「SPRESENSE用ELTRESアドオンボード」が小型で、ランニングコストが安価であり、数分単位で位置情報をクラウド上に通知できることから、キッズみまもりに十分な性能を備えており、今回開発してみようと考えました。

ユースケース

  • 広い屋外で子供と遊ぶ家族
  • 子供の帰りを待つ家族

システム概要

見守りたい子供に「キッズみまもり子機」を持ってもらい、見守りたい親に「キッズみまもり親機」を持ってもらいます。

キャプションを入力できます

親(親機)と子供(子機)が事前に設定した距離以上離れた場合、親機のLEDが点灯し、親に離れてしまったことを通知します。
親機には以下の位置・距離情報が表示されています。

  • 自分(親機)の位置
  • 子供(子機)の位置
  • 離れている距離(m)
  • 離れている方角(角度)

親は上記の情報を頼りに素早く子供のところに到達できます。

キャプションを入力できます

また、親機の位置情報を自宅や固定の場所に設定することで、
親を原点にするのではなく、特定の場所を原点にして一定距離離れた場合にお知らせるようにもできます。例として、家を原点にし、設定距離を学校にしておけば、通学路から、逸脱した遠くに連れ去られてしまった場合に、素早く気づくことができます。

機器構成

機器名 配置位置 役割
キッズみまもり子機 対象となる子供 対象となる子供の位置情報をリアルタイムでクラウド(CLIP Viewer Lite)に通知する
キッズみまもり親機 対象となる親 サーバーPC経由で受信した子機の位置情報と、自機に取り付けられたGPSモジュールの位置情報を比較し、子機-親機間の位置・距離情報を算出・表示する。必要に応じて、LEDを点灯させ、距離がしきち値より離れていることを示す
CLIP Viewer Liteデータ・通信用PC 任意 定期的に、CLIP Viewer Liteからcsv形式をダウンロードし、サーバーPCのMQTT Brokerに子供の位置情報を送信する
サーバーPC 任意 MQTT Brokerでmqttのメッセージを送受信する

キッズみまもり子機

キャプションを入力できます

結線

※ SPRESENSE用ELTRESアドオンボードの公式通りのため省略

キッズみまもり親機

キャプションを入力できます

ピンアサイン

UNIT CatM M5StickC
TXD G33
RXD G32
5V Vout
GND GND
GPS受信機キット M5StickC
5V 5V→
GND GND
RXD G26
TXD G0
1PPS -

CLIP Viewer Liteデータ・通信用PC

※ 任意のPC

サーバーPC

※ 任意のレンタルサーバー

システム構成

キャプションを入力できます

CLIP Viewer Liteデータ・通信用PCの動作

CLIP Viewer LiteにはWeb APIが備わっています。
サーバーPC側から最新の値を要求し、サーバーPCのMQTT Brokerに値を送信すれば、やりたいことは達成できます。

しかし、CLIP Viewer LiteのWeb APIは2022年9月現在有料であり、
「2022年 SPRESENSE™ 活用コンテスト」では標準では有効になっていないことから、
他の方法で通知する必要がありました。

CLIP Viewer LiteではWeb APIの他に最新の値をcsvでダウンロードする機能も提供されていることから、Windows操作の自動化ツールであるUWSCを使用し、1分や3分などの固定間隔でcsvのダウンロード→最新の位置情報の抜き取り→サーバーPCのMQTT Brokerに値を送信することにしました。

UWSCへ記録・実行した操作手順

  1. webページCLIP Viewer Liteにアクセス
  2. ユーザー名の入力欄をクリック
  3. 自分のユーザー名を入力
  4. パスワードの入力欄をクリック
  5. 自分のパスワード名を入力
  6. 「サインイン」をクリック
  7. ダッシュボードが表示されるので、「CSVダウンロード」ボタンをクリック
  8. 画面右上のハンバーガーボタンをクリックし、「サインアウト」ボタンをクリック
  9. ブラウザを閉じる
  10. 本記事の末に示す、「CLIP Viewer Liteの最新CSVファイル読取およびサーバーPCのMQTT Brokerへの位置情報送信スクリプト」を実行する

動作の様子

親機-子機間が近い場合

キャプションを入力できます

ほぼ、同じ空間にいる場合の表示のようすです。
距離が8mと出ており、探しに行く必要がありません。

親機-子機間が遠い場合

キャプションを入力できます

かなり、遠くにいる場合の表示のようすです。
LEDにより、距離がしきい値を上回っていることがわかります。(※しきい値は300mに設定)
距離が317m、角度321°と出ており、北西の方向に300m程度いったところに子機があることがわかりますので、その方向・距離に探しに行く必要があることがわかります。

プログラム

子機(spresense)のプログラム ※eltres_sample_gps.inoママ

#include <EltresAddonBoard.h>

// PIN定義:LED(プログラム状態)
#define LED_RUN PIN_LED0
// PIN定義:LED(GNSS電波状態)
#define LED_GNSS PIN_LED1
// PIN定義:LED(ELTRES状態)
#define LED_SND PIN_LED2
// PIN定義:LED(エラー状態)
#define LED_ERR PIN_LED3

// プログラム内部状態:初期状態
#define PROGRAM_STS_INIT      (0)
// プログラム内部状態:起動中
#define PROGRAM_STS_RUNNING   (1)
// プログラム内部状態:終了
#define PROGRAM_STS_STOPPED   (3)

// プログラム内部状態
int program_sts = PROGRAM_STS_INIT;
// GNSS電波受信タイムアウト(GNSS受信エラー)発生フラグ
bool gnss_recevie_timeout = false;
// 点滅処理で最後に変更した時間
uint64_t last_change_blink_time = 0;
// イベント通知での送信直前通知(5秒前)受信フラグ
bool event_send_ready = false;
// ペイロードデータ格納場所
uint8_t payload[16];
// 最新のGGA情報
eltres_board_gga_info last_gga_info;

/**
 * @brief イベント通知受信コールバック
 * @param event イベント種別
 */
void eltres_event_cb(eltres_board_event event) {
  switch (event) {
  case ELTRES_BOARD_EVT_GNSS_TMOUT:
    // GNSS電波受信タイムアウト
    Serial.println("gnss wait timeout error.");
    gnss_recevie_timeout = true;
    break;
  case ELTRES_BOARD_EVT_IDLE:
    // アイドル状態
    Serial.println("waiting sending timings.");
    digitalWrite(LED_SND, LOW);
    break;
  case ELTRES_BOARD_EVT_SEND_READY:
    // 送信直前通知(5秒前)
    Serial.println("Shortly before sending, so setup payload if need.");
    event_send_ready = true;
    break;
  case ELTRES_BOARD_EVT_SENDING:
    // 送信開始
    Serial.println("start sending.");
    digitalWrite(LED_SND, HIGH);
    break;
  case ELTRES_BOARD_EVT_GNSS_UNRECEIVE:
    // GNSS電波未受信
    Serial.println("gnss wave has not been received.");
    digitalWrite(LED_GNSS, LOW);
    break;
  case ELTRES_BOARD_EVT_GNSS_RECEIVE:
    // GNSS電波受信
    Serial.println("gnss wave has been received.");
    digitalWrite(LED_GNSS, HIGH);
    gnss_recevie_timeout = false;
    break;
  case ELTRES_BOARD_EVT_FAULT:
    // 内部エラー発生
    Serial.println("internal error.");
    break;
  }
}

/**
 * @brief GGA情報受信コールバック
 * @param gga_info GGA情報のポインタ
 */
void gga_event_cb(const eltres_board_gga_info *gga_info) {
  Serial.print("[gga]");
  last_gga_info = *gga_info;
  if (gga_info->m_pos_status) {
    // 測位状態
    // GGA情報をシリアルモニタへ出力
    Serial.print("utc: ");
    Serial.println((const char *)gga_info->m_utc);
    Serial.print("lat: ");
    Serial.print((const char *)gga_info->m_n_s);
    Serial.print((const char *)gga_info->m_lat);
    Serial.print(", lon: ");
    Serial.print((const char *)gga_info->m_e_w);
    Serial.println((const char *)gga_info->m_lon);
    Serial.print("pos_status: ");
    Serial.print(gga_info->m_pos_status);
    Serial.print(", sat_used: ");
    Serial.println(gga_info->m_sat_used);
    Serial.print("hdop: ");
    Serial.print(gga_info->m_hdop);
    Serial.print(", height: ");
    Serial.print(gga_info->m_height);
    Serial.print(" m, geoid: ");
    Serial.print(gga_info->m_geoid);
    Serial.println(" m");
  } else {
    // 非測位状態
    // "invalid data"をシリアルモニタへ出力
    Serial.println("invalid data.");
  }
}

/**
 * @brief setup()関数
 */
void setup() {
  // シリアルモニタ出力設定
  Serial.begin(115200);

  // LED初期設定
  pinMode(LED_RUN, OUTPUT);
  digitalWrite(LED_RUN, HIGH);
  pinMode(LED_GNSS, OUTPUT);
  digitalWrite(LED_GNSS, LOW);
  pinMode(LED_SND, OUTPUT);
  digitalWrite(LED_SND, LOW);
  pinMode(LED_ERR, OUTPUT);
  digitalWrite(LED_ERR, LOW);

  // ELTRES起動処理
  eltres_board_result ret = EltresAddonBoard.begin(ELTRES_BOARD_SEND_MODE_1MIN,eltres_event_cb, gga_event_cb);
  if (ret != ELTRES_BOARD_RESULT_OK) {
    // ELTRESエラー発生
    digitalWrite(LED_RUN, LOW);
    digitalWrite(LED_ERR, HIGH);
    program_sts = PROGRAM_STS_STOPPED;
    Serial.print("cannot start eltres board (");
    Serial.print(ret);
    Serial.println(").");
  } else {
    // 正常
    program_sts = PROGRAM_STS_RUNNING;
  }
}

/**
 * @brief loop()関数
 */
void loop() {

  switch (program_sts) {
    case PROGRAM_STS_RUNNING:
      // プログラム内部状態:起動中
      if (gnss_recevie_timeout) {
        // GNSS電波受信タイムアウト(GNSS受信エラー)時の点滅処理
        uint64_t now_time = millis();
        if ((now_time - last_change_blink_time) >= 1000) {
          last_change_blink_time = now_time;
          bool set_value = digitalRead(LED_ERR);
          bool next_value = (set_value == LOW) ? HIGH : LOW;
          digitalWrite(LED_ERR, next_value);
        }
      } else {
        digitalWrite(LED_ERR, LOW);
      }

      if (event_send_ready) {
        // 送信直前通知時の処理
        event_send_ready = false;
        setup_payload_gps();
        // 送信ペイロードの設定
        EltresAddonBoard.set_payload(payload);
      }
      break;
     
    case PROGRAM_STS_STOPPED:
      // プログラム内部状態:終了
      break;
  }
  // 次のループ処理まで100ミリ秒待機
  delay(100);
}

/**
 * @brief GPSペイロード設定
 */
void setup_payload_gps() {
  String lat_string = String((char*)last_gga_info.m_lat);
  String lon_string = String((char*)last_gga_info.m_lon);
  int index;
  uint32_t gnss_time;
  uint32_t utc_time;

  // GNSS時刻(epoch秒)の取得
  EltresAddonBoard.get_gnss_time(&gnss_time);
  // UTC時刻を計算(閏秒補正)
  utc_time = gnss_time - 18;

  // 設定情報をシリアルモニタへ出力
  Serial.print("[setup_payload_gps]");
  Serial.print("lat:");
  Serial.print(lat_string);
  Serial.print(",lon:");
  Serial.print(lon_string);
  Serial.print(",utc:");
  Serial.print(utc_time);
  Serial.print(",pos:");
  Serial.print(last_gga_info.m_pos_status);
  Serial.println();

  // ペイロード領域初期化
  memset(payload, 0x00, sizeof(payload));
  // ペイロード種別[GPSペイロード]設定
  payload[0] = 0x81;
  // 緯度設定
  index = 0;
  payload[1] = (uint8_t)(((lat_string.substring(index,index+1).toInt() << 4)
                + lat_string.substring(index+1,index+2).toInt()) & 0xff);
  index += 2;
  payload[2] = (uint8_t)(((lat_string.substring(index,index+1).toInt() << 4)
                + lat_string.substring(index+1,index+2).toInt()) & 0xff);
  index += 2;
  index += 1;   // skip "."
  payload[3] = (uint8_t)(((lat_string.substring(index,index+1).toInt() << 4)
                + lat_string.substring(index+1,index+2).toInt()) & 0xff);
  index += 2;
  payload[4] = (uint8_t)(((lat_string.substring(index,index+1).toInt() << 4)
                + lat_string.substring(index+1,index+2).toInt()) & 0xff);
  // 経度設定
  index = 0;
  payload[5] = (uint8_t)(lon_string.substring(index,index+1).toInt() & 0xff);
  index += 1;
  payload[6] = (uint8_t)(((lon_string.substring(index,index+1).toInt() << 4)
                + lon_string.substring(index+1,index+2).toInt()) & 0xff);
  index += 2;
  payload[7] = (uint8_t)(((lon_string.substring(index,index+1).toInt() << 4)
                + lon_string.substring(index+1,index+2).toInt()) & 0xff);
  index += 2;
  index += 1;   // skip "."
  payload[8] = (uint8_t)(((lon_string.substring(index,index+1).toInt() << 4)
                + lon_string.substring(index+1,index+2).toInt()) & 0xff);
  index += 2;
  payload[9] = (uint8_t)(((lon_string.substring(index,index+1).toInt() << 4)
                + lon_string.substring(index+1,index+2).toInt()) & 0xff);
  // 時刻(EPOCH秒)設定
  payload[10] = (uint8_t)((utc_time >> 24) & 0xff);
  payload[11] = (uint8_t)((utc_time >> 16) & 0xff);
  payload[12] = (uint8_t)((utc_time >> 8) & 0xff);
  payload[13] = (uint8_t)(utc_time & 0xff);
  // 拡張用領域(0固定)設定
  payload[14] = 0x00;
  // 品質設定
  payload[15] = last_gga_info.m_pos_status;
}

親機(M5StickC)のプログラム

#include <M5StickC.h>
#include <ArduinoJson.h>
#include "mqtt-config.h"
/* ■CAT-M */
#define TINY_GSM_MODEM_SIM7080
#define TINY_GSM_RX_BUFFER 650  // なくても動いたけど、あったほうが安定する気がする
#define TINY_GSM_YIELD() { delay(2); } // なくても動いたけど、あったほうが安定する気がする
#include <TinyGsmClient.h>
#include <PubSubClient.h>

#define THRESHOLD_DIST 300

const char apn[]      = "povo.jp";
const char* broker = MY_BROKER;

const char* topicTest       = "eltres";
#define GSM_AUTOBAUD_MIN 9600
#define GSM_AUTOBAUD_MAX 115200

// TinyGsmClient
TinyGsm        modem(Serial2);
TinyGsmClient  client(modem);
PubSubClient  mqtt(client);

uint32_t lastReconnectAttempt = 0;

/* ■GPS */
#include <TinyGPS++.h>
TinyGPSPlus gps;
double currentLat = 0.0;
double currentLong = 0.0;

void mqttCallback(char* topic, byte* payload, unsigned int len) {
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.print("Message arv\n");
  char payloadBuf[256] = {0};
  strncpy(payloadBuf,(const char *)payload,len);

  StaticJsonDocument<128> doc;  // メモリを確保(静的)
  deserializeJson(doc, payloadBuf);       // json形式に変換
  const char* latitudeChar = doc["latitude"];
  const char* longitudeChar = doc["longitude"];
  const char* timeStamp = doc["timeStamp"];

  double latitude = String(latitudeChar).toDouble();
  double longitude = String(longitudeChar).toDouble();

  char buf1[256] = {0};
  sprintf(buf1,"currentLat:\n %lf",currentLat);
  M5.Lcd.printf("%s\n",buf1);

  char buf2[256] = {0};
  sprintf(buf2,"currentlong:\n %lf\n",currentLong);
  M5.Lcd.printf("%s\n",buf2);
  
  char buf3[256] = {0};
  sprintf(buf3,"targetLat:\n %lf",latitude);
  M5.Lcd.printf("%s\n",buf3);

  char buf4[256] = {0};
  sprintf(buf4,"targetlong:\n %lf\n",longitude);
  M5.Lcd.printf("%s\n",buf4);
  
  long dist = CalcDistance(currentLat,currentLong,latitude,longitude);
  int bearing = CalcBearing(currentLat,currentLong,latitude,longitude);

  M5.Lcd.print("------------\n");
  M5.Lcd.println(String(timeStamp));
  char buf[256] = {0};
  sprintf(buf,"dist: \n %ld [m]",dist);
  M5.Lcd.printf("%s\n",buf);
  M5.Lcd.printf("bearing: \n %d [deg]\n\n",bearing);
  M5.Lcd.print("------------\n");

  if(dist < THRESHOLD_DIST)
  {
  M5.Lcd.printf("LED OFF\n");
    digitalWrite(GPIO_NUM_10, HIGH); // OFF
  }else
  {
  M5.Lcd.printf("LED ON\n");
    digitalWrite(GPIO_NUM_10, LOW); // ON
  }
}

boolean mqttConnect() {
  M5.Lcd.print("Connecting to ");
  M5.Lcd.println(broker);

  // Connect to MQTT Broker
  boolean status = mqtt.connect("GsmClientTest");

  // Or, if you want to authenticate MQTT:
  // boolean status = mqtt.connect("GsmClientName", "mqtt_user", "mqtt_pass");

  if (status == false) {
    M5.Lcd.println(" fail");
    return false;
  }
  M5.Lcd.println(" success");
  mqtt.subscribe(topicTest);
  return mqtt.connected();
}


// the setup routine runs once when M5StickC starts up
void setup(){
  // Initialize the M5StickC object
  M5.begin();
  // GPS通信用
  Serial1.begin(9600, SERIAL_8N1, 0, 26); // EXT_IO
  Serial2.begin(9600, SERIAL_8N1, 33, 32);

  // LCD display
  M5.Lcd.print("Hello World");
  Serial.println("Hello World");

  M5.Lcd.println("Wait...");  // Print text on the screen (string) 在屏幕上打印文本(字符串)
  // Set GSM module baud rate

  // モデムのリスタート
  M5.Lcd.println("Initializing modem...");  // Print text on the screen (string) 在屏幕上打印文本(字符串)
  modem.restart();

  // モデムの情報の取得
  String modemInfo = modem.getModemInfo();
  M5.Lcd.print("Modem Info: ");
  M5.Lcd.println(modemInfo);


  // GPRS connection parameters are usually set after network registration
  M5.Lcd.print(F("Connecting to "));
  M5.Lcd.print(apn);
  if (!modem.gprsConnect(apn, "", "")) {
    M5.Lcd.println("-> fail");
    delay(10000);
    return;
  }
  M5.Lcd.println("-> success");

  if (modem.isGprsConnected()) { M5.Lcd.println("GPRS connected"); }

  mqtt.setServer(broker, 1883);
  mqtt.setCallback(mqttCallback); 
  
  pinMode(GPIO_NUM_10, OUTPUT);
  digitalWrite(GPIO_NUM_10, HIGH); // OFF
  
  M5.Lcd.print("work start1");
  Serial.println("work start1");
}

// function from https://forum.arduino.cc/index.php?topic=45760.0
//convert degrees to radians
double dtor(double fdegrees)
{
  return(fdegrees * M_PI / 180);
}

//Convert radians to degrees
double rtod(double fradians)
{
  return(fradians * 180.0 / M_PI);
}

//Calculate distance form lat1/lon1 to lat2/lon2 using haversine formula
//Note lat1/lon1/lat2/lon2 must be in radians
//Returns distance in feet
long CalcDistance(double lat1, double lon1, double lat2, double lon2)
{
  double dlon, dlat, a, c;
  double dist = 0.0;
  dlon = dtor(lon2 - lon1);
  dlat = dtor(lat2 - lat1);
  a = pow(sin(dlat/2),2) + cos(dtor(lat1)) * cos(dtor(lat2)) * pow(sin(dlon/2),2);
  c = 2 * atan2(sqrt(a), sqrt(1-a));

  //dist = 20925656.2 * c;  //radius of the earth (6378140 meters) in feet 20925656.2
  dist = 6378140.0 * c;  //radius of the earth (6378140 meters) in feet 20925656.2
  return( (long) dist + 0.5);
}

//Calculate bearing from lat1/lon1 to lat2/lon2
//Note lat1/lon1/lat2/lon2 must be in radians
//Returns bearing in degrees
int CalcBearing(double lat1, double lon1, double lat2, double lon2)
{
  lat1 = dtor(lat1);
  lon1 = dtor(lon1);
  lat2 = dtor(lat2);
  lon2 = dtor(lon2);

  //determine angle
  double bearing = atan2(sin(lon2-lon1)*cos(lat2), (cos(lat1)*sin(lat2))-(sin(lat1)*cos(lat2)*cos(lon2-lon1)));
  //convert to degrees
  bearing = rtod(bearing);
  //use mod to turn -90 = 270
  bearing = fmod((bearing + 360.0), 360);
  return (int) bearing + 0.5;
}


// the loop routine runs over and over again forever
void loop() {
  while (Serial1.available() > 0){
    char c = Serial1.read();
    //Serial.print(c);
    gps.encode(c);
    if (gps.location.isUpdated()){
      Serial.print("LAT=");
      Serial.println(gps.location.lat(), 6);
      currentLat = gps.location.lat();
      Serial.print("LONG=");
      Serial.println(gps.location.lng(), 6);
      currentLong = gps.location.lng();
      Serial.print("ALT=");
      Serial.println(gps.altitude.meters());
    }
  }
  
  // Make sure we're still registered on the network
  if (!modem.isNetworkConnected()) {
      M5.Lcd.println("Network disconnected");
      
      if (!modem.waitForNetwork(180000L, true)) {
        M5.Lcd.println(" fail");
        delay(10000);
        return;
      }
      
      if (modem.isNetworkConnected()) {
        M5.Lcd.println("Network re-connected");
      }
      
      // and make sure GPRS/EPS is still connected
      if (!modem.isGprsConnected()) {
        M5.Lcd.println("GPRS disconnected!");
        M5.Lcd.print(F("Connecting to "));
        M5.Lcd.print(apn);
        if (!modem.gprsConnect(apn, "", "")) {
          M5.Lcd.println(" fail");
          delay(10000);
          return;
        }
        if (modem.isGprsConnected()) { M5.Lcd.println("GPRS reconnected"); }
      }
  }
      
  if (!mqtt.connected()) {
    M5.Lcd.println("=== MQTT NOT CONNECTED ===");
    // Reconnect every 10 seconds
    uint32_t t = millis();
    if (t - lastReconnectAttempt > 10000L) {
      lastReconnectAttempt = t;
      if (mqttConnect()) { lastReconnectAttempt = 0; }
    }
    delay(100);
    return;
  }
  
  mqtt.loop();
}

CLIP Viewer Liteの最新CSVファイル読取およびサーバーPCのMQTT Brokerへの位置情報送信スクリプト

path = "c:/Users/{UserName}}/Downloads"
url = "XXX.com"

#import
import os
import csv
import json
import paho.mqtt.client as mqtt     # MQTTのライブラリをインポート
from time import sleep              # 3秒間のウェイトのために使う
import time
import sys

connected = False

class TudeValue:
    def __init__( self,degrees,minutes,seconds):
        self.degrees = degrees
        self.minutes = minutes
        self.seconds = seconds

class GpsDataSet:
    def __init__( self, latDegrees , latMinutesSeconds, longDegrees,longMinutesSeconds, timeStamp) :
        latMinuts,latSecondsBase = latMinutesSeconds.split('.')
        latSeconds = str(round(float(latSecondsBase) * 60 / 10000,1))
        print("gps2")
        print(latDegrees)
        print(latMinuts)
        print(latSeconds)
        self.latitude = TudeValue(latDegrees,latMinuts,latSeconds)
        longMinuts,longSecondsBase = longMinutesSeconds.split('.')
        longSeconds = str(round(float(longSecondsBase) * 60 / 10000,1))
        print("gps3")
        print(longDegrees)
        print(longMinuts)
        print(longSeconds)
        self.longitude = TudeValue(longDegrees,longMinuts,longSeconds)
        self.timeStamp = timeStamp


# 与えられたフォルダから最新のCSVファイルのフルパスを取得
def getLatestCsvFile(folderPath):
    filePath = None
    files = os.listdir(folderPath)
    for item in files:
        itemFilePath = path + "/" + item            # 該当のファイルのフルパスを取得
        root, ext = os.path.splitext(itemFilePath)  # extに拡張子の取出
        if ext != ".csv":                           # .csvファイル以外は対象から除外
            continue

        ts = os.path.getmtime(itemFilePath)
        if filePath is None:
            filePath = itemFilePath
        else :
            latestFileTs = os.path.getmtime(filePath)
            fileTs = os.path.getmtime(itemFilePath)
            if(fileTs - latestFileTs > 0):
                filePath = itemFilePath
    return filePath

def getLastestGpsData(csvFilePath):
    with open(csvFilePath, "r", encoding="UTF-8", errors="", newline="" ) as csvFile:
        reader = csv.reader(csvFile)
        csvList = [row for row in reader]
    
    latDegrees,latMinutesSeconds,longDegrees,longMinutesSeconds = csvList[1][8].split(' ')
    return GpsDataSet(latDegrees,latMinutesSeconds,longDegrees,longMinutesSeconds,csvList[1][9])

def getGpsDataJson(gpsDataSet):
    latitude = round(
        float(gpsDataSet.latitude.degrees) +
        ((float(gpsDataSet.latitude.minutes) / 60)) + 
        ((float(gpsDataSet.latitude.seconds) / 60 / 60))
        ,6)
    longitude = round(
        float(gpsDataSet.longitude.degrees) + 
        (float(gpsDataSet.longitude.minutes) / 60) + 
        (float(gpsDataSet.longitude.seconds) /60 / 60)
        ,6)
    obj = {
        "latitude" : str(latitude),
        "longitude" : str(longitude),
        "timeStamp" : gpsDataSet.timeStamp
        } 
    json_data = json.dumps(obj).encode("utf-8")
    return json_data

# ブローカーに接続できたときの処理
def on_connect(client, userdata, flag, rc):
  print("Connected with result code " + str(rc))

# ブローカーが切断したときの処理
def on_disconnect(client, userdata, rc):
  if rc != 0:
     print("Unexpected disconnection.")

# publishが完了したときの処理
def on_publish(client, userdata, mid):
  print("publish: {0}".format(mid))

# 対象のフォルダから最新のCSVファイルを取得
latestCsvFile = getLatestCsvFile(path)
# 取得したCSVファイルの最新の緯度経度とタイムスタンプを取得
print(latestCsvFile)
gpsDataSet = getLastestGpsData(latestCsvFile)
gpsDataSetJson = getGpsDataJson(gpsDataSet)
# mqtt各種設定
client = mqtt.Client()                 # クラスのインスタンス(実体)の作成
client.on_connect = on_connect         # 接続時のコールバック関数を登録
client.on_disconnect = on_disconnect   # 切断時のコールバックを登録
client.on_publish = on_publish         # メッセージ送信時のコールバック

client.connect(url, 1883, 60)
# 通信処理スタート
client.loop_start()    # subはloop_forever()だが,pubはloop_start()で起動だけさせる

time.sleep(5)
print("publish")
print(gpsDataSetJson)
client.publish("eltres",gpsDataSetJson)
time.sleep(5)
1
mametarou963のアイコン画像
広島で細々と活動しています。 最近はM5Stack関係をいじっています。
ログインしてコメントを投稿する