Atsushi Hattori
/
20210627_Logger
hattori&ide
Diff: Loggermain.cpp
- Revision:
- 0:f77369cabd75
diff -r 000000000000 -r f77369cabd75 Loggermain.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Loggermain.cpp Sun Dec 18 08:16:01 2022 +0000 @@ -0,0 +1,979 @@ +/*更新情報 + * 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(¤t_tmp[0]); // バッテリー電流 + PanelMonitor.getCurrent(¤t_tmp[1]); // パネル電流 + MotorMonitor.getCurrent(¤t_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; // エラー + } + } +} +