hattori&ide

Dependencies:   mbed

Loggermain.cpp

Committer:
hattori_atsushi
Date:
18 months ago
Revision:
0:f77369cabd75

File content as of revision 0:f77369cabd75:

/*更新情報
 * 2016/01/20
 * LCD表示 現在時刻から経過時間へ変更
 * LCD表示 単位記号の位置変更
 *
 * 2016/01/29
 * postアドレス変更 TEST→myLogger
 *
 * 2016/01/30
 * サーバー送信データ変更,末尾にデータ取得時間追加
 *
 * 2016/02/08
 * 配列の要素数をconst定数化
 * LapUSBSaveDataのcsvデータがおかしかったので修正
 * ラップをラップタイムが3分30秒以内の場合は加算しないように修正
 * CalculateSet関数を分割
 *
 * 2016/02/22
 * 初期化ルーチンを関数に分割
 * GPS対応
 * データバックアップ,引き継ぎ機能追加,ファイル名を日付に変更
 *
 * 2016/02/24
 * エラー処理追加, メンテナンス
 *
 * 2016/04~
 * タイマー処理測定→http送信による遅延を無害化
 * 加速度センサデータ保存、モーター回転数から速度算出
 *
 * 2016/04/24
 * ラップスイッチ ピン変化割り込み化
 * 配列過剰書き込み 対策済み

 * 2016/05/11
 * ファイル名変更 日付/各データ(ファイル構造化)
 * GPSセンテンス保存 走行ラインGoogle Earth等で閲覧可
 *
 * 2016/06/01
 * スタート前時,積算しないように設定(関数の引数で変更可)
 *
 * 2016/06/10
 * INA226設定レジスタ変更
 * wait変更
 *
 * 2016/06/15
 * MITSUBA CANデータ対応 回転数データ取得
 *
 * 2016/07/03
 * INA226 異常データ対策
 * 絶縁監視(漏れ電流)エラー出力機能追加
 *
 * 2016/07/11
 * DriverEmergency機能追加
 *
 * 2016/07/17
 * Web送信データ変更 平均→積算
 * LAP送信データ追加 コントロールライン通過時刻
 *
 * 2016/07/25
 * Webデータ送信 桁数一部変更
 * CalculateSet関数修正
 *
 * 2016/08/02
 * ReadyStartLCD()にGPSがfixしたらGPS OKと表示するようにした
 *
 * 2017/08/02
 * INA226 測定平均回数 16 → 128
 * 2017/08/05 12:00:00自動スタート
 *
 * 2021/07/10
 * バッテリー容量を3.6 * 3.35 * 30 * 14から3.6 * 3.45 * 26 * 16 に変更
 *
 */

#include "mbed.h"
#include "INA226.hpp"
#include "TextOLED.h"
#include "MSCFileSystem.h"
#include "EthernetNetIf.h"
#include "TCPSocket.h"
#include "TinyHTTP.h"
#include "NTPClient.h"
#include "RTC.h"
#include "MBed_Adafruit_GPS.h"
#include "MITSUBA_CAN.h"
//#include "MMA8652.h"

// INA226用I2Cアドレス I2Cアドレスはmbedの場合1ビット左シフトしたアドレスになる
const int batteryMonitorAddress = 0x80;         // 0b10000000 (GG)
const int panelMonitorAddress = 0x82;           // 0b10000010 (G1)
const int motorMonitorAddress = 0x9E;           // 0b10011110 (CC)
const unsigned short configuration = 0x4897;    // 平均化処理やタイミング

/*------------------------------設定変更厳禁------------------------------*/
const double voltageCalibration =  10 / 1.1;          // 分圧比(10倍に補正)
const unsigned short calibration = 0x1400;      // シャント電圧補正係数
const unsigned short calibrationPanel = 0x2800; // シャント電圧補正係数(パネル)
const double currentCalibration[] = {1.5, 0.5, 1.5};
// [0]: バッテリ [1]: パネル [2]: モータ
/*------------------------------設定変更厳禁------------------------------*/

const double batteryCapacity = 3.6 * 3.45 * 26 * 16;       // 公称電圧*電流容量*直列数*並列数←2021鈴鹿仕様に変更

const int offsetTime = 60 * 60 * 9;     // 9時間(標準時との時差) = 日本時間
const int storageTime = 8;              // 8秒ごとにサーバーへ送信 10秒はサイズ的に厳しい
const int storageOffset = 5;            // 5秒間分余分に配列を用意しておく
const int lapTime_limit = 60 * 4;       // LAPタイム 4分00秒以内はキャンセル
const double kmph = 1.852;              // 1ノット = 1.852 km/h
const double pulseConvert = 0.1303;     // 車速パルス -> 速度変換係数
const double rpmConvert = 0.104399;       // rpm -> km/h 変換係数

/*各配列要素数*/
const int RealtimeData_i = 3;
const int RealtimeData_j = 5;
const int LapoutData_i = 3;
const int LapoutData_j = 3;
const int timeStr_i = 6;
const int preLapdata_i = 3;
const int preLapdata_j = 2;

// バッファーサイズ
const int RequestStr_maxsize = 1210;   // 1600とかでは送信できない メモリ破壊?
const int LapRequestStr_maxsize = 110;
const int SerialSendStr_maxsize = 200;
const int timeStr_maxsize = 32;
const int fileName_maxsize = 32;

Serial debug(USBTX, USBRX);
Serial Xbee(p13, p14);
Serial gpsSerial(p28, p27);
Adafruit_GPS myGPS(&gpsSerial);

CAN mitsuba_can(p30, p29);
MITSUBA canlog(mitsuba_can, 250000);
//MMA8652 acc(p9, p10);  // sda, scl

DigitalOut sublogger_sec(p24);
DigitalOut sublogger_min(p25);
// 引数は(rs,e,d4,d5,d6,d7) 接続しないピンはGNDに落としておくと安定する
TextOLED lcd(p15, p16, p17, p18, p19, p20, TextOLED::LCD20x4);

I2C i2c(p9, p10);  // sda, scl
INA226 BatteryMonitor(i2c, batteryMonitorAddress, 10000);
INA226 PanelMonitor(i2c, panelMonitorAddress, 10000);
INA226 MotorMonitor(i2c, motorMonitorAddress, 10000);

MSCFileSystem msc("usb");  // USBメモリには/usb/...でアクセス

EthernetNetIf eth;
NTPClient ntp;

DigitalOut isoError(p6);         // 絶縁監視エラー出力

InterruptIn lapSw(p7);          // lapスイッチ  ON: 0, OFF:1
InterruptIn motorPulse(p12);    // モーターパルス

struct LogData {
  char timeStr[timeStr_i][timeStr_maxsize];
  /* [0]:タイムスタンプ    20160801121356
     [1]:現在時刻          12:13:56
     [2]:経過時間          00:13:56
     [3]:カウント用        00:01:30
     [4]:LCD用ラップタイム 05:15
     [5]:通過時刻          12:05:00*/
  unsigned int lap;                                    // ラップ数
  unsigned int lapTime;                                // ラップタイム
  int totalTime;                              // 経過時間
  double RealtimeData[RealtimeData_i][RealtimeData_j]; // 1秒毎の測定データ
  double LapoutData[LapoutData_i][LapoutData_j];       // ラップ平均等
  double batteryVoltage;              // バッテリー電圧
  double remainBatteryParcent;        // バッテリー残量[%]
  double remainBatteryWh;             // バッテリー残量[Wh]
  double motorSpeed;                  // モーター速度[km/h] CAN
  double motorSpeed_pulse;            // モーター速度[km/h] パルス
  double motorAngle;
  double gpsSpeed;                    // GPS速度[km/h]
  // float accData[3];                // 3軸加速度 0:x, 1:y, 2:z[何Gか]
  double latitude;                    // 緯度
  double longitude;                   // 経度
};

// グローバル変数
bool secTimer = false;
bool lapSave = false;
bool lapSave_forsub=false;
bool emergency = false;
time_t startUnixTime = 0;
struct LogData logdata = {0};
struct LogData Postdata[storageTime + storageOffset] = {0}; // storageTimeかつかつはまずい?
unsigned int postcount = 0;                                 // logdataを貯める時間をカウント
unsigned int lapTimeValue = 0;                              // スイッチ判定用のラップタイム
unsigned int pulseCount = 0;                                // 車速パルスカウント
time_t prelapUnixTime = 0;                                  // 前のラップタイムのUNIXタイム
double preLapdata[preLapdata_i][preLapdata_j] = {0};        // 前のラップデータ

// プロトタイプ宣言
void SetTimer(void);
void MainFuncTimer(void);
void SwFuncInterrupt(void);
void MotorPulseCount(void);
void print(const char *str, int row, int col);
int SetupINA226(void);
int SetupEthernet(void);
int SetupUSB(struct LogData *data, char name[][fileName_maxsize]);
int ReadBackup(struct LogData *data, double predata[][preLapdata_j], time_t *preTime);
int FetchGPSdata(const char name[][fileName_maxsize]);
void CalculateSet(struct LogData *data, bool run);
unsigned int CalculateTimeSet(struct LogData *data, time_t preTime);
void LapCalculateSet(struct LogData *data, double predata[][preLapdata_j], time_t *preTime);
int httpPost(const struct LogData postdata[], int *httpRes);
void LaphttpPost(const struct LogData *data, int *httpRes);
void EmergencyhttpPost(const struct LogData *data);
void LCD(const struct LogData *data);
void ReadyStartLCD(const struct LogData *data);
int USBSaveData(const struct LogData postdata[], const char name[][fileName_maxsize]);
int LapUSBSaveData(const struct LogData *data, const char name[][fileName_maxsize]);
int SaveBackup(const struct LogData *data, const double predata[][preLapdata_j], time_t preTime);
void XbeeSerial(const struct LogData *data);
void InsulateMonitor(const struct LogData *data);

int main(){
  int httpResponce = 0;                       // httpリクエスト送信時のレスポンス
  int LaphttpResponce = 0;
  char fileName[3][fileName_maxsize] = {0};   // USBファイル名  [0]:LOG, [1]:LAP, [2]:GPS

  debug.baud(9600);
  Xbee.baud(57600);  // 安心と信頼()の57600bps
  myGPS.begin(9600);
  myGPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  myGPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
  myGPS.sendCommand(PGCMD_ANTENNA);

  lcd.cls();

sublogger_sec=0;
sublogger_min=0;

  // エラー時は自動的にリセット

  if(SetupINA226()) {
    NVIC_SystemReset();
    return -1;
  }

  if(SetupEthernet()) {
    NVIC_SystemReset();
    return -1;
  }

  // Host server(IpAddr(), 123, "ntp.jst.mfeed.ad.jp");  // NTP時刻取得 調子悪い?
  Host server(IpAddr(), 123, "ntp.nict.jp");
  ntp.setTime(server);       // RTCにセット

  if(SetupUSB(&logdata, fileName)) {
    NVIC_SystemReset();
    return -1;
  }

  wait(1.0);
  lcd.cls();

  RTC::attach(&SetTimer, RTC::Second);       // SetTimer()を1秒毎に実行する

  // スタート前ループ
  // 初回起動時 backup.csvが存在していないときのみ実行
  if(ReadBackup(&logdata, preLapdata, &prelapUnixTime)) {
    while(1) {
      if(!lapSw || time(NULL) == 1564801200) {  // 2019/8/3/12:00:00 自動スタート←2年前になってたかも
        startUnixTime = time(NULL) + offsetTime+600;    // スタート時刻取得
        break;
      }
      if(secTimer) {
        CalculateTimeSet(&logdata, prelapUnixTime);
        CalculateSet(&logdata, false);
        ReadyStartLCD(&logdata);
        InsulateMonitor(&logdata);
        secTimer = false;
      }
    }
  }
  /*
     else {
     // NTP時刻が異常な時(スタートから5時間以上) 自動的にリセット
     if(labs(startUnixTime - (time(NULL) + offsetTime)) > 5 * 3600) {
      print("NTP Time isoError\n", 0, 0);
      RTC::detach(RTC::Second);
      NVIC_SystemReset();
      return -1;
     }
     }
   */
  lapSw.fall(&SwFuncInterrupt);          // ピン立ち下がり割り込みに設定
  motorPulse.fall(&MotorPulseCount);

  RTC::detach(RTC::Second);
  lcd.cls();
  wait(0.5);       // lapSwが0になるのを待つ

  RTC::attach(&MainFuncTimer, RTC::Second);  // MainFuncTimerを1秒ごとに実行

// メインループ
  while(1) {
    FetchGPSdata(fileName);  // GPSセンテンス取得・解析
    if(emergency) {
      EmergencyhttpPost(&logdata);
      emergency = false;
    }
    if(lapSave) {
      LaphttpPost(&logdata, &LaphttpResponce);                 // LAPデータ送信
      LapUSBSaveData(&logdata, fileName);
      lapSave = false;
    }
    if(secTimer) {                                                // fopen関連は割り込みで実行できない
      SaveBackup(&logdata, preLapdata, prelapUnixTime);           // バックアップ
      secTimer = false;
    }
    if(postcount >= storageTime) {                 // storageTime個のデータが集まったら
      postcount = 0;
      USBSaveData(Postdata, fileName);             // USBにデータ保存
      httpPost(Postdata, &httpResponce);
      // debug.printf("\n%d\n", httpResponce);
    }
  }
}

/*--------------------------割り込み実行の関数 ここから-------------------------------*/
// RTCタイマーで1秒ごとに実行(スタート前)
void SetTimer(){
  secTimer = true;
}

// RTCタイマーで1秒ごとに実行(走行時)
void MainFuncTimer(){
  secTimer = true;
  canlog.GetCanData(FRAME0);  // FRAME0のデータを取得
  lapTimeValue = CalculateTimeSet(&logdata, prelapUnixTime);  // 時間に関するデータを計算
  CalculateSet(&logdata, true);                               // データ取得・計算し,格納
  LCD(&logdata);                                              // LCDにデータ表示
  Postdata[postcount++] = logdata;                            // 蓄積
  if(postcount >= storageTime + storageOffset) {              // 配列上限超えると強制リセット
    postcount = 0;
  }
  InsulateMonitor(&logdata);
}

// スイッチ割り込み
// lapTime_limit未満は緊急スイッチとして動作→非表示に(2019.8.1)
void SwFuncInterrupt(){
  if(lapTimeValue >= lapTime_limit) {
    LapCalculateSet(&logdata, preLapdata, &prelapUnixTime);  // LAPデータ計算
    XbeeSerial(&logdata);                                    // LAPデータをxbeeで送信
    lapSave = true;                                          // Web 送信,USB保存のフラグ
    lapSave_forsub=true;
  }
  else{
    //lcd.cls();
    //print("Driver Emergency", 2, 1);
    wait(1.0);
    lcd.cls();
    emergency = true;      // 緊急スイッチ,web送信フラグ
  }
}

void MotorPulseCount(){
  pulseCount++;    // ストリーム等でデバックすると遅延で正確にパルス数を測定できない
}

/*--------------------------割り込み実行の関数 ここまで-------------------------------*/

/*-------------------------------ユーザー定義関数-------------------------------------*/

/** 全てのストリーム系に文字列を出力
 * @param const char *str 送信文字列
 * @param int row 列
 * @param int col 行
 */
void print(const char *str, int row, int col){
  debug.printf("%s", str);
  Xbee.printf("%s", str);
  lcd.locate(row, col);
  lcd.printf("%s", str);
}

/** INA226 レジスタ書き込み等
 * @return 0:成功     -1:失敗
 */
int SetupINA226(){
  unsigned short val_1 = 0;
  unsigned short val_2 = 0;
  unsigned short val_3 = 0;

  // INA226の存在を確認
  if(!BatteryMonitor.isExist()) {
    print("INA226B not found!\n", 0, 0);
  }
  if(!PanelMonitor.isExist()) {
    print("INA226P not found!\n", 0, 1);
  }
  if(!MotorMonitor.isExist()) {
    print("INA226M not found!\n", 0, 2);
    return -1;
  }

  // INA226のレジスタの値を正しく読めるか確認
  if(BatteryMonitor.rawRead(0x00, &val_1) != 0) {
    print("INA226B read Error!\n", 0, 0);
    return -1;
  }
  if(PanelMonitor.rawRead(0x00, &val_2) != 0) {
    print("INA226P read Error!\n", 0, 1);
    return -1;
  }
  if(MotorMonitor.rawRead(0x00, &val_3) != 0) {
    print("INA226M read Error!\n", 0, 2);
    return -1;
  }

  // 各INA226にレジスタ値を書き込む
  BatteryMonitor.setConfiguration(configuration);
  PanelMonitor.setConfiguration(configuration);
  MotorMonitor.setConfiguration(configuration);
  BatteryMonitor.setCurrentCalibration(calibration);
  PanelMonitor.setCurrentCalibration(calibrationPanel);
  MotorMonitor.setCurrentCalibration(calibration);

  print("INA226 OK!\n", 0, 0);
  return 0;
}

/** ネット関連 初期化
 * @return 0: 成功 -1: 失敗
 */
int SetupEthernet(){
  EthernetErr ethErr = eth.setup();
  if(ethErr) {
    debug.printf("Error %d in setup.\n", ethErr);
    Xbee.printf("Error %d in setup.\n", ethErr);
    lcd.locate(0, 1);
    lcd.printf("Setup Error %d", ethErr);
    return -1;
  }

  print("Ethernet OK!\n", 0, 1);
  return 0;
}

/** USBメモリ書き込み初期化 ファイル名設定
 * @param struct LogData LogData 構造体
 * @param char name ファイル名が書き込まれる配列
 *
 * @return 0: 成功 -1: 失敗
 */
int SetupUSB(struct LogData *data, char name[][fileName_maxsize]){
  time_t createdTime = time(NULL) + offsetTime;
  // 日付がファイル名
  strftime(name[0], fileName_maxsize, "/usb/%y%m%d_log.csv", localtime(&createdTime));
  strftime(name[1], fileName_maxsize, "/usb/%y%m%d_LAPdata.csv", localtime(&createdTime));
  strftime(name[2], fileName_maxsize, "/usb/%y%m%d_GPSNMEA.log", localtime(&createdTime));

  FILE *fp = fopen(name[0], "a");
  if(fp == NULL) {
    print("USB can\'t be opened!\n", 0, 2);
    return -1;
  }
  else{
    strftime(data->timeStr[0], timeStr_maxsize, "%Y%m%d%H%M%S", localtime(&createdTime));
    fprintf(fp, "%s\n", data->timeStr[0]);    // 試しに現在時刻を書き込み
  }
  fclose(fp);

  print("USB OK!\n", 0, 2);
  return 0;
}

/** バックアップデータを読み込み,LogData構造体に格納
 * @param struct LogData バックアップからLogData 構造体に書き込む
 * @param double predata バックアップから前のラップタイムデータを書き込む
 * @param time_t *preTime バックアップから前のラップタイムを書き込む
 *
 * @return 0:成功     1:バックアップデータが存在していない
 */
int ReadBackup(struct LogData *data, double predata[][preLapdata_j], time_t *preTime){
  int i;
  FILE *fp_back = fopen("/usb/backup.csv", "r");
  if(fp_back == NULL) {
    debug.printf("Backup File No Exist\n");
    return 1;  // 初回起動時 backup.csvはSaveBackup関数が実行された時に作成される
  }
  else{
    fscanf(fp_back, "%d,%d,%d,%d", &(data->lap), &(data->lapTime), &startUnixTime, preTime);
    for(i = 0; i < RealtimeData_i; i++) {
      fscanf(fp_back, "%lf,%lf,", &(data->RealtimeData[i][2]), &(data->RealtimeData[i][3]));  // 電流和,電力和
    }
    for(i = 0; i < preLapdata_i; i++) {
      fscanf(fp_back, "%lf,%lf,", &predata[i][0], &predata[i][1]);
    }
  }
  fclose(fp_back);
  return 0;
}

/** シリアルGPSデータを解析,保存
 * @param const char name ファイル名が保存されている配列
 *
 * @return 0:成功    -1:失敗
 */
int FetchGPSdata(const char name[][fileName_maxsize]){
  // char c;
  while(1) {
    myGPS.read();                            // ただ読むだけでなくて,GPSセンテンスを文字列化している
    // if (c) debug.printf("%c", c);
    if (myGPS.newNMEAreceived()) {           // センテンスが一文取得できたかどうか
      if (!myGPS.parse(myGPS.lastNMEA())) {  // 解析が完了したかどうか していない場合はもう一度ループ(breakスキップ)
        continue;
      }
      break;                                 // 解析終了した場合はループを抜ける
    }
  }
  // 直前のGPSセンテンスをUSBに保存
  FILE *fp = fopen(name[2], "a");
  if(fp == NULL) {
    debug.printf("USB Error\n");
    return -1;
  }
  else{
    fprintf(fp, "%s", myGPS.lastNMEA());
  }
  fclose(fp);
  return 0;
}

/** 1秒毎に電圧・電流測定,計算後のデータを構造体に格納
 * @param struct LogData
 * @param  bool run : true 積算する false 積算しない
 */
void CalculateSet(struct LogData *data, bool run){
  int i;
  bool retry = false;
  int count = 0;

  double voltage_tmp = 0;      // 仮に電圧,電流データを保存しておく
  double current_tmp[RealtimeData_i] = {0};

  // 異常な値の場合は,再度データを取り直す
  do {
    retry = false;
    BatteryMonitor.getVoltage(&voltage_tmp);     // 電圧
    BatteryMonitor.getCurrent(&current_tmp[0]); // バッテリー電流
    PanelMonitor.getCurrent(&current_tmp[1]);   // パネル電流
    MotorMonitor.getCurrent(&current_tmp[2]);   // モーター電流

    if(voltage_tmp > 200) retry = true;         // 電圧が200V以上は異常
    for(i = 0; i < RealtimeData_i; i++) {
      if(current_tmp[i] > 200) retry = true;    // 電流が200A以上は異常
    }
    count++;
  } while(retry && count < 5);  // 5回とっても異常な場合はさすがにループを抜ける

  // 0割の有無を確認した後,配列に格納する
  if(data->totalTime != 0) {
    data->batteryVoltage = voltage_tmp * voltageCalibration;             // 分圧電圧を補正
    for(i = 0; i < RealtimeData_i; i++) {
      data->RealtimeData[i][0] = current_tmp[i] * currentCalibration[i];  // 電流補正
      data->RealtimeData[i][1] = data->batteryVoltage * data->RealtimeData[i][0];   // 電力
      if(run) data->RealtimeData[i][2] += data->RealtimeData[i][0];                 // 電流の和
      if(run) data->RealtimeData[i][3] += data->RealtimeData[i][1];                 // 電力の和
      data->RealtimeData[i][4] = data->RealtimeData[i][3] / 3600;                   // 積算電力
    }
    data->remainBatteryWh = batteryCapacity - data->RealtimeData[0][4];     // バッテリー残量[Wh]
    data->remainBatteryParcent = (data->remainBatteryWh / batteryCapacity) * 100;    // バッテリー残量[%]
    data->motorSpeed = canlog.rpmMotor * rpmConvert;  // CAN通信で回転数取得 km/h変換
    data->motorAngle = canlog.angle;  // 進角
    debug.printf("angle:%.2f\n", canlog.angle);
    data->motorSpeed_pulse = pulseCount * pulseConvert;     // 1秒間でカウントしたパルスを速度に変換
    pulseCount = 0;
    data->gpsSpeed = myGPS.speed * kmph;  // GPS速度
    data->latitude = myGPS.latitude;     // GPS緯度
    data->longitude = myGPS.longitude;   // GPS経度
    //acc.ReadXYZ(data->accData);          // 3軸加速度
  }
}

/** 時刻関連のデータを計算,格納
 * @param struct LogData
 * @param time_t preTime  前のラップタイム
 *
 * @return unsigned int ラップタイム
 */
unsigned int CalculateTimeSet(struct LogData *data, time_t preTime){
  time_t nowTime;              // 現在時刻
  time_t laptime_temp;         // ラップタイム算出用
  time_t totalTime_temp;       // 経過時間用

  nowTime = time(NULL) + offsetTime;                // RTCからUNIXタイムを取得し,日本時間に修正 現在時刻
  totalTime_temp = nowTime - startUnixTime;         // 経過時間 (現在時刻とスタート時刻の差)
  data->totalTime = (int) totalTime_temp;  // 経過時間(秒)
  if(!data->lap) {
    if(nowTime < startUnixTime){
      laptime_temp = 0;
    }
    else{
      laptime_temp = nowTime - startUnixTime;
    }         // 現在時刻とスタート時刻の差 = 初回のラップタイム
  }
  else{
    laptime_temp = nowTime - preTime;               // 現在時刻と前のラップタイムの時刻の差 = ラップタイム
  }
  if(laptime_temp<0){
    laptime_temp = 0;
  }
  // strftimeでUNIXタイムを適切にフォーマット
  strftime(data->timeStr[0], timeStr_maxsize, "%Y%m%d%H%M%S", localtime(&nowTime));   // PHP,USB用
  strftime(data->timeStr[1], timeStr_maxsize, "%T", localtime(&nowTime));             // LCDの現在時刻用
  strftime(data->timeStr[2], timeStr_maxsize, "%T", localtime(&totalTime_temp));      // LCDの経過時間用
  strftime(data->timeStr[3], timeStr_maxsize, "%M\'%S", localtime(&laptime_temp));    // LCDのラップ経過時刻用


if(lapSave_forsub==true){
    sublogger_min=1;
    lapSave_forsub=false;
    }
else{    sublogger_min=0;}
sublogger_sec=1;
sublogger_sec=0;



  return (unsigned int) laptime_temp;
}

/** ラップ平均・積算値を計算し構造体に格納
 * @param struct LogData
 * @param double predata  前のラップタイムのデータ
 * @param time_t *preTime 前のラップタイムを書き込む
 */
void LapCalculateSet(struct LogData *data, double predata[][preLapdata_j], time_t *preTime){
  int i, j;
  time_t laptime_temp;

  // 0周目から1周目になるときの動作
  if(!data->lap) {
    data->lapTime = (int)(time(NULL) + offsetTime - startUnixTime);
    if(time(NULL) + offsetTime - startUnixTime<0){
      laptime_temp = 0;
    }
    else{
      laptime_temp = time(NULL) + offsetTime - startUnixTime;
    }            // 現在時刻とスタート時刻の差 = 初回のラップタイム
    strftime(data->timeStr[4], timeStr_maxsize, "%M\'%S", localtime(&laptime_temp));     // LCD用にフォーマット
  }
  // それ以外の動作
  else{
    data->lapTime = (unsigned int)(time(NULL) + offsetTime - *preTime);
    laptime_temp = time(NULL) + offsetTime - *preTime;
    strftime(data->timeStr[4], timeStr_maxsize, "%M\'%S", localtime(&laptime_temp));
  }
  data->lap = data->lap + 1;             // ラップ加算
  *preTime = time(NULL) + offsetTime;    // 現在時刻を前のラップタイムのUNIXタイムとする
  strftime(data->timeStr[5], timeStr_maxsize, "%T", localtime(preTime));  // コントロールライン通過時刻

// 0割の有無を確認した後,配列に格納 前のデータとの差で一周分を算出
  if(data->lapTime) {
    for(i = 0; i < LapoutData_i; i++) {
      data->LapoutData[i][0] = (data->RealtimeData[i][2] - predata[i][0]) / data->lapTime;   // ラップ平均電流
      data->LapoutData[i][1] = (data->RealtimeData[i][3] - predata[i][1]) / data->lapTime;   // ラップ平均電力
      data->LapoutData[i][2] = (data->RealtimeData[i][3] - predata[i][1]) / 3600;            // ラップ電力量
    }
    for(i = 0; i < preLapdata_i; i++) {
      for(j = 0; j < preLapdata_j; j++) {
        predata[i][j] = data->RealtimeData[i][j + 2];                 // 前のデータを記憶しておく
      }
    }
  }
}

/*この関数では構造体を引数に,httpリクエスト文を作成しサーバーにポストします
 * リクエスト文(POSTデータ) key[]=value1&key[]=value2&...
 * keyがp[]でvalueがカンマで結合した1秒間データ(8秒分なので8個作ることになる)
 * keyをkey[]形式にするとphp側で添字配列として受け取ることができる
   ---骨格---
   構造体のデータをchar型に変換してsnprintfでフォーマットしていく(追記方式)
   そうして,httpRequest関数に渡す
 */
/** データをネット送信
 * @param const struct LogData
 * @param *httpRes   HTTPレスポンスコード
 */
int httpPost(const struct LogData postdata[], int *httpRes){
  IpAddr ip(133, 68, 205, 140);
  Host host;
  host.setName("solar-car.club.nitech.ac.jp");
  host.setIp(ip);

  int i, j, k;
  char RequestStr[RequestStr_maxsize] = {0};

  // リクエスト文を作成していく
  for(k = 0; k < storageTime; k++) {
    snprintf(RequestStr, RequestStr_maxsize, "%sp[]=%s,%d,%d,%d,%.2f",              // pがkey
             RequestStr,
             postdata[k].timeStr[1],
             postdata[k].lap,
             postdata[k].lapTime,
             postdata[k].totalTime,
             postdata[k].batteryVoltage);
    for(j = 0; j < 2; j++) {
      for(i = 0; i < RealtimeData_i; i++) {
        snprintf(RequestStr, RequestStr_maxsize, "%s,%.1f", RequestStr, postdata[k].RealtimeData[i][j]);     // 電流,電力
      }
    }
    for(i = 0; i < RealtimeData_i; i++) {
      snprintf(RequestStr, RequestStr_maxsize, "%s,%.1f", RequestStr, postdata[k].RealtimeData[i][4]);       // 積算電力
    }
    snprintf(RequestStr, RequestStr_maxsize, "%s,%.2f,%.2f,%.1f,%.4f,%.4f,%s",
             RequestStr,
             postdata[k].remainBatteryParcent,
             postdata[k].remainBatteryWh,
             postdata[k].motorSpeed,
             postdata[k].latitude,
             postdata[k].longitude,
             postdata[k].timeStr[0]);
    if(k < (storageTime - 1)) {
      snprintf(RequestStr, RequestStr_maxsize, "%s&", RequestStr);        // 最後のデータの末尾には&は不要
    }
  }
  *httpRes = httpRequest(METHOD_POST, 8000, &host,"/myLogger/Get/GetRealtimeData.php", "Content-Type: application/x-www-form-urlencoded\r\n", RequestStr);
  // debug.printf("%s", RequestStr);

  return 0;
}

void LaphttpPost(const struct LogData *data, int *httpRes){
  IpAddr ip(133, 68, 205, 140);
  Host host;
  host.setName("solar-car.club.nitech.ac.jp");
  host.setIp(ip);

  char LapRequestStr[LapRequestStr_maxsize] = {0};
  int i, j;

  snprintf(LapRequestStr, LapRequestStr_maxsize, "q=%s,%d,%d", data->timeStr[5], data->lap, data->lapTime);
  for(j = 0; j < LapoutData_j; j++) {
    for(i = 0; i < LapoutData_i; i++) {
      snprintf(LapRequestStr, LapRequestStr_maxsize, "%s,%.2f", LapRequestStr, data->LapoutData[i][j]);                   // 列に関して出力
    }
  }
  snprintf(LapRequestStr, LapRequestStr_maxsize, "%s,%s", LapRequestStr, data->timeStr[0]);
  *httpRes = httpRequest(METHOD_POST, HTTP_TIMEOUT, &host, "/myLogger/Get/GetLapoutData.php", "Content-Type: application/x-www-form-urlencoded\r\n", LapRequestStr);
  // debug.printf("%s", LapRequestStr);
}

void EmergencyhttpPost(const struct LogData *data){
  IpAddr ip(133, 68, 205, 140);
  Host host;
  host.setName("solar-car.club.nitech.ac.jp");
  host.setIp(ip);

  char sendStr[32] = {0};

  snprintf(sendStr, 32, "p=%s,%s", data->timeStr[1], data->timeStr[0]);
  httpRequest(METHOD_POST, HTTP_TIMEOUT, &host, "/myLogger/Get/GetEmergency.php", "Content-Type: application/x-www-form-urlencoded\r\n", sendStr);
}

/*LCDへのデータ表示を行う locate関数は0行0列からはじまり,引数は(列,行)*/
void LCD(const struct LogData *data){
  lcd.locate(0, 0);
  lcd.printf("SP");
  lcd.locate(3, 0);
  lcd.printf("%3.0f", data->motorSpeed);
  lcd.locate(7, 0);
  lcd.printf("km/h");
  lcd.locate(12, 0);
  lcd.printf("%s", data->timeStr[1]);

  lcd.locate(0, 1);
  lcd.printf("B");
  lcd.locate(3, 1);
  lcd.printf("%5.1f", data->remainBatteryParcent);
  lcd.locate(9, 1);
  lcd.printf("%%");
  lcd.locate(15, 1);
  lcd.printf("%s", data->timeStr[3]);

  lcd.locate(0, 2);
  lcd.printf("V");
  lcd.locate(3, 2);
  lcd.printf("%5.1f", data->batteryVoltage);
  lcd.locate(9, 2);
  lcd.printf("V");
  lcd.locate(12, 2);
  lcd.printf("LAP");
  lcd.locate(15, 2);
  lcd.printf("%s", data->timeStr[4]);

  lcd.locate(0, 3);
  lcd.printf("M");
  lcd.locate(1, 3);
  lcd.printf("%7.1f", data->RealtimeData[2][1]);
  lcd.locate(9, 3);
  lcd.printf("W");
  lcd.locate(12, 3);
  lcd.printf("%3.0f", data->LapoutData[2][2]);
  lcd.locate(15, 3);
  lcd.printf("Wh");
  lcd.locate(18, 3);
  lcd.printf("%d", data->lap);
}

void ReadyStartLCD(const struct LogData *data){
  lcd.locate(2, 0);
  lcd.printf(">>Push to Start<<");

  lcd.locate(0, 1);
  lcd.printf("B");
  lcd.locate(2, 1);
  lcd.printf("%5.1f", data->remainBatteryParcent);
  lcd.locate(8, 1);
  lcd.printf("%%");
  lcd.locate(11, 1);
  lcd.printf("Now Time");

  lcd.locate(0, 2);
  lcd.printf("V");
  lcd.locate(2, 2);
  lcd.printf("%5.1f", data->batteryVoltage);
  lcd.locate(8, 2);
  lcd.printf("V");
  lcd.locate(11, 2);
  lcd.printf("%s", data->timeStr[1]);

  lcd.locate(0, 3);
  lcd.printf("P");
  lcd.locate(1, 3);
  lcd.printf("%6.1f", data->RealtimeData[0][1]);
  lcd.locate(8, 3);
  lcd.printf("W");
  if(myGPS.fix) {
    lcd.locate(11, 3);
    lcd.printf("GPS OK");
  }
}

/*構造体のデータをUSBメモリへデータを保存(csvファイル)*/
int USBSaveData(const struct LogData postdata[], const char name[][fileName_maxsize]){
  int i, j, k;
  FILE *fp = fopen(name[0], "a");

  if(fp == NULL) {
    debug.printf("USB can\'t open\n");
    return -1;
  }
  else{
    for(k = 0; k < storageTime; k++) {
      fprintf(fp, "%s,%d,%d,%d,%.2f,", postdata[k].timeStr[1], postdata[k].lap, postdata[k].lapTime, postdata[k].totalTime, postdata[k].batteryVoltage);
      for(j = 0; j < RealtimeData_j; j++) {
        for(i = 0; i < RealtimeData_i; i++) {
          fprintf(fp, "%.3f,", postdata[k].RealtimeData[i][j]);                         // 列に関して出力(全データ)
        }
      }
      fprintf(fp, "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f\n", postdata[k].remainBatteryParcent, postdata[k].remainBatteryWh, postdata[k].motorSpeed, postdata[k].motorSpeed_pulse, postdata[k].gpsSpeed, postdata[k].motorAngle);
/*
      for(i = 0; i < 3; i++) {
        if(i == 2) {
          fprintf(fp, "%.5f\n", postdata[k].accData[i]);
        }
        else{
          fprintf(fp, "%.5f,", postdata[k].accData[i]);
        }
      }
 */
    }
  }
  fclose(fp);
  return 0;
}

int LapUSBSaveData(const struct LogData *data, const char name[][fileName_maxsize]){
  int i, j;

  FILE *fp = fopen(name[1], "a");
  if(fp == NULL) {
    debug.printf("USB can\'t open\n");
    return -1;
  }
  else{
    fprintf(fp, "\n%s,%d,%d,%d", data->timeStr[5], data->lap, data->lapTime, data->totalTime);
    for(j = 0; j < LapoutData_j; j++) {
      for(i = 0; i < LapoutData_i; i++) {
        fprintf(fp, ",%7.2f", data->LapoutData[i][j]);                         // 列に関して(ラップ平均等の全データ)
      }
    }
  }
  fclose(fp);

  return 0;
}

/** 蓄積されているデータをcsvファイルにバックアップ
 * @param const strct LogData
 * @param const double predata
 * @param time_t preTime
 */
int SaveBackup(const struct LogData *data, const double predata[][preLapdata_j], time_t preTime){
  int i, j;

  FILE *fp_back = fopen("/usb/backup.csv", "w");
  if(fp_back == NULL) {
    debug.printf("USB can\'t open!\n");
    return -1;
  }
  else{
    fprintf(fp_back, "%d,%d,%d,%d\r\n", data->lap, data->totalTime, (unsigned int)startUnixTime, (unsigned int)preTime);
    for(i = 0; i < RealtimeData_i; i++) {
      for(j = 0; j < 2; j++) {
        fprintf(fp_back, "%f,", data->RealtimeData[i][j+2]);  // 電流和,電力和
      }
      fprintf(fp_back, "\r\n");
    }
    for(i = 0; i < preLapdata_i; i++) {
      for(j = 0; j < preLapdata_j; j++) {
        fprintf(fp_back, "%f,", predata[i][j]);
      }
      fprintf(fp_back, "\r\n");
    }
  }
  fclose(fp_back);

  return 0;
}

/*基本はhttp通信を使いますが,何かあったときにシリアル通信できるようにしておきます
 *送信データは基本的にラップ数,ラップタイム,LapoutData
 */
void XbeeSerial(const struct LogData *data){
  int i, j;
  char SerialSendStr[SerialSendStr_maxsize] = {0};

  snprintf(SerialSendStr, SerialSendStr_maxsize, "\nLAP: %d, %s, BV:%6.2f[V],%6.2f[%%],%7.2f[Wh],\n",
           data->lap,
           data->timeStr[4],
           data->batteryVoltage,
           data->remainBatteryParcent,
           data->remainBatteryWh);
  snprintf(SerialSendStr, SerialSendStr_maxsize, "%s            Battery, Panel, Motor\nAllAve[W]: ", SerialSendStr);
  for(i = 0; i < RealtimeData_i; i++) {
    snprintf(SerialSendStr, SerialSendStr_maxsize, "%s%7.2f,", SerialSendStr, data->RealtimeData[i][4]);  // 平均電力
  }
  snprintf(SerialSendStr, SerialSendStr_maxsize, "%s\nlapAve[W]: ", SerialSendStr);   // 改行
  for(j = 1; j < LapoutData_j; j++) {
    for(i = 0; i < LapoutData_i; i++) {
      snprintf(SerialSendStr, SerialSendStr_maxsize, "%s%7.2f,", SerialSendStr, data->LapoutData[i][j]);   // ラップ平均,積算
    }
    if(j == 1) snprintf(SerialSendStr, SerialSendStr_maxsize, "%s\nlapWh[Wh]: ", SerialSendStr);  // 改行
  }
  Xbee.printf("%s\n", SerialSendStr);   // Xbeeでシリアル送信
}

// 絶縁監視機能(漏れ電流測定)
// 電流の値が0.5A以下の場合は例外
// バッテリ+パネル電流ーモータ電流が1A以上になった場合、エラー出力
void InsulateMonitor(const struct LogData *data){
  bool stopDecison = false;

  for(int i = 0; i < RealtimeData_i; i++) {
    if(data->RealtimeData[i][0] < 0.5) {
      stopDecison = true;
      break;
    }
  }
  if(!stopDecison) {
    if((data->RealtimeData[0][0] + data->RealtimeData[1][0] - data->RealtimeData[2][0]) < 1) {
      isoError = 0;    // 正常
    }
    else{
      // isoError = 1;    // エラー
    }
  }
}