Skip to content

Commit 51dc25b

Browse files
committed
wip refactor
1 parent 78b2f08 commit 51dc25b

24 files changed

+490
-1571
lines changed

src2/App.h

Lines changed: 146 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -1,253 +1,193 @@
11
#pragma once
2-
/**
3-
* @file App.h
4-
* @brief サイクルコンピュータのメインアプリケーションクラス
5-
*
6-
* このクラスは、サイクルコンピュータのすべての機能を統合・制御します。
7-
* - GNSSからの位置情報取得
8-
* - 速度・距離・時間の計算
9-
* - ユーザー入力(ボタン操作)の処理
10-
* - OLED画面への表示
11-
* - EEPROMへのデータ永続化
12-
*
13-
* ダブルバッファリングを使用して、効率的なデータ更新と表示を実現しています。
14-
*/
152

163
#include <Arduino.h>
174
#include <stddef.h>
185

6+
#include "common/DoubleBuffer.h"
197
#include "domain/DataStore.h"
20-
#include "domain/DisplayLogic.h"
21-
#include "domain/GnssAdapter.h"
22-
#include "domain/InputLogic.h"
23-
#include "domain/PersistenceLogic.h"
248
#include "domain/TripLogic.h"
259
#include "domain/VoltageMonitor.h"
2610
#include "hardware/Clock.h"
2711
#include "hardware/Gnss.h"
2812
#include "ui/FrameLogic.h"
2913
#include "ui/UI.h"
3014

31-
/**
32-
* @class App
33-
* @brief アプリケーションのメインクラス
34-
*
35-
* setup()でbegin()を呼び、loop()でupdate()を呼ぶだけで動作します。
36-
*/
3715
class App {
3816
private:
39-
// ==================== ハードウェア関連 ====================
40-
Gnss gnss; ///< GNSSモジュール制御
41-
Clock systemClock; ///< RTC(リアルタイムクロック)
42-
DataStore dataStore; ///< EEPROMデータ管理
43-
VoltageMonitor voltageMonitor; ///< バッテリー電圧監視
44-
UI userInterface; ///< ユーザーインターフェース(ボタン + OLED)
45-
46-
// ==================== 状態管理 ====================
47-
Mode currentMode = Mode::SPD_TIM; ///< 現在の表示モード
48-
49-
/**
50-
* GNSSから取得した最新データ
51-
*/
52-
GnssData gnssData;
53-
54-
/**
55-
* 走行状態のダブルバッファ
56-
* [0]と[1]を交互に使用し、前回値との差分を効率的に検出します。
57-
*/
58-
TripState tripState[2];
59-
60-
/**
61-
* 表示フレームのダブルバッファ
62-
* 画面更新が必要かどうかを判定するために使用します。
63-
*/
64-
DisplayFrame frames[2];
65-
66-
/**
67-
* 保存データのダブルバッファ
68-
* 前回保存時からの変更有無を判定するために使用します。
69-
*/
70-
SaveData saveBuffers[2];
71-
72-
int currentIdx = 0; ///< 現在の走行状態バッファインデックス
73-
int frameIdx = 0; ///< 現在の表示フレームバッファインデックス
74-
int saveIdx = 0; ///< 現在の保存データバッファインデックス
75-
76-
unsigned long lastSaveMs = 0; ///< 最後にEEPROMに保存した時刻
77-
unsigned long lastUiUpdateMs = 0; ///< 最後にUI更新した時刻
17+
Gnss gnss;
18+
Clock systemClock;
19+
DataStore dataStore;
20+
VoltageMonitor voltageMonitor;
21+
UI userInterface;
22+
23+
Mode currentMode = Mode::SPD_TIM;
24+
25+
DoubleBuffer<TripState> tripBuffer;
26+
DoubleBuffer<DisplayFrame> frameBuffer;
27+
DoubleBuffer<SaveData> saveBuffer;
28+
29+
unsigned long currentTime = 0;
30+
GnssData currentGnss = {};
31+
Input::Event currentButton = Input::Event::NONE;
32+
SpGnssTime currentClock = {};
33+
float currentVoltage = 0.0f;
34+
35+
unsigned long lastSaveMs = 0;
36+
unsigned long lastUiUpdateMs = 0;
7837

7938
public:
80-
App() = default;
81-
82-
/**
83-
* @brief アプリケーションの初期化
84-
*
85-
* setup()で1回だけ呼び出してください。
86-
* - 各ハードウェアの初期化
87-
* - EEPROMからの保存データ読み込み
88-
* - 走行状態の初期化
89-
*/
9039
void begin() {
9140
gnss.begin();
9241
systemClock.begin();
9342
voltageMonitor.begin();
9443
userInterface.begin();
44+
loadFromStorage();
45+
}
9546

96-
// EEPROMから前回の走行データを読み込む
47+
void update() {
48+
collectInputs();
49+
updateState();
50+
processOutputs();
51+
}
52+
53+
private:
54+
void loadFromStorage() {
9755
SaveData saved = dataStore.load();
9856

99-
// 両方のバッファに同じ初期値を設定
100-
for (auto &state : tripState) {
101-
state.resetAll();
102-
state.distance.total = saved.totalDistance;
103-
state.distance.trip = saved.tripDistance;
104-
state.time.moving = saved.movingTimeMs;
105-
state.speed.max = saved.maxSpeed;
57+
TripState state;
58+
state.resetAll();
59+
state.distance.total = saved.totalDistance;
60+
state.distance.trip = saved.tripDistance;
61+
state.time.moving = saved.movingTimeMs;
62+
state.speed.max = saved.maxSpeed;
63+
64+
tripBuffer.initialize(state);
65+
saveBuffer.initialize(saved);
66+
lastSaveMs = millis();
67+
}
68+
69+
void collectInputs() {
70+
currentTime = millis();
71+
currentButton = userInterface.getInputEvent();
72+
currentClock = systemClock.now();
73+
currentVoltage = voltageMonitor.update();
74+
75+
bool updated = gnss.update();
76+
currentGnss.status = updated ? UpdateStatus::Updated : UpdateStatus::NoChange;
77+
currentGnss.navData = gnss.navData;
78+
79+
if (updated && (SpFixMode)currentGnss.navData.posFixMode != FixInvalid) {
80+
systemClock.sync(currentGnss.navData.time);
10681
}
82+
}
10783

108-
saveBuffers[0] = saved;
109-
saveBuffers[1] = saved;
84+
void updateState() {
85+
tripBuffer.prepare();
86+
tripBuffer.current().resetMeta();
11087

111-
lastSaveMs = millis();
88+
if (currentButton != Input::Event::NONE) {
89+
if (handleButton()) return;
90+
}
91+
92+
TripLogic::computeTrip(tripBuffer.current(), currentGnss, currentTime);
11293
}
11394

114-
/**
115-
* @brief メインループ処理
116-
*
117-
* loop()で繰り返し呼び出してください。
118-
* 以下の処理を順番に実行します:
119-
* 1. GNSSデータの取得
120-
* 2. ボタン入力の処理
121-
* 3. RTCの同期(GPS時刻から)
122-
* 4. 走行データの計算
123-
* 5. データの保存(一定間隔で)
124-
* 6. 画面の更新(必要な場合のみ)
125-
*/
126-
void update() {
127-
const unsigned long now = millis();
95+
bool handleButton() {
96+
TripState &state = tripBuffer.current();
12897

129-
// ダブルバッファのインデックスを切り替え
130-
// prevIdx: 前回の状態, currIdx: 今回更新する状態
131-
const int prevIdx = currentIdx;
132-
const int currIdx = 1 - currentIdx;
98+
switch (currentButton) {
99+
case Input::Event::SELECT:
100+
currentMode = static_cast<Mode>((static_cast<int>(currentMode) + 1) % 3);
101+
state.forceUpdate();
102+
break;
133103

134-
// 前回の状態をコピーしてから更新
135-
tripState[currIdx] = tripState[prevIdx];
136-
tripState[currIdx].resetMeta(); // 更新フラグをリセット
104+
case Input::Event::PAUSE:
105+
state.status =
106+
state.isPaused() ? TripStateBase::Status::Stopped : TripStateBase::Status::Paused;
107+
state.forceUpdate();
108+
break;
137109

138-
// GNSSデータを取得
139-
gnssData = GnssAdapter::collect(gnss);
140-
Input::Event event = userInterface.getInputEvent();
110+
case Input::Event::RESET:
111+
applyReset(currentMode);
112+
break;
141113

142-
// GPS信号が有効なら、RTCをGPS時刻で同期
143-
if (gnssData.status == UpdateStatus::Updated &&
144-
(SpFixMode)gnssData.navData.posFixMode != FixInvalid) {
145-
systemClock.sync(gnssData.navData.time);
114+
case Input::Event::RESET_LONG:
115+
state.resetAll();
116+
dataStore.clear();
117+
userInterface.showResetMessage();
118+
frameBuffer.initialize(DisplayFrame());
119+
saveBuffer.initialize(createSaveData(state, 0.0f));
120+
return true;
121+
122+
default:
123+
break;
146124
}
125+
return false;
126+
}
147127

148-
// ボタンイベントの処理
149-
if (event != Input::Event::NONE) {
150-
auto result = InputLogic::handleEvent(tripState[currIdx], currentMode, event);
151-
currentMode = result.newMode;
152-
153-
// 全データリセットが要求された場合
154-
if (result.shouldClearStorage) {
155-
dataStore.clear();
156-
userInterface.showResetMessage();
157-
158-
// 表示フレームをクリア
159-
frames[0] = DisplayFrame();
160-
frames[1] = DisplayFrame();
161-
162-
// 保存バッファもクリア
163-
TripState emptyState;
164-
emptyState.resetAll();
165-
SaveData emptySave = PersistenceLogic::create(emptyState, 0.0f);
166-
saveBuffers[0] = emptySave;
167-
saveBuffers[1] = emptySave;
168-
}
128+
void applyReset(Mode mode) {
129+
TripState &state = tripBuffer.current();
130+
switch (mode) {
131+
case Mode::SPD_TIM:
132+
state.resetTrip();
133+
break;
134+
case Mode::AVG_ODO:
135+
state.resetAll();
136+
break;
137+
case Mode::MAX_CLK:
138+
state.resetMaxSpeed();
139+
break;
169140
}
141+
}
170142

171-
// 走行データを計算(速度・距離・時間)
172-
TripLogic::computeTrip(tripState[currIdx], gnssData, now);
143+
void processOutputs() {
144+
outputToDisplay();
145+
outputToStorage();
146+
}
173147

174-
// 定期的にデータを保存
175-
handleSave(tripState[currIdx], now);
148+
void outputToDisplay() {
149+
if (!shouldUpdateUI()) return;
176150

177-
// 必要に応じて画面を更新
178-
handleUI(tripState[prevIdx], tripState[currIdx], now);
151+
DisplayFrame nextFrame =
152+
FrameLogic::buildFrame(tripBuffer.current(), currentGnss, currentClock, currentMode);
179153

180-
// 現在のバッファインデックスを更新(次回は逆のバッファを使う)
181-
currentIdx = currIdx;
154+
if (frameBuffer.apply(nextFrame)) {
155+
userInterface.draw(frameBuffer.current());
156+
lastUiUpdateMs = currentTime;
157+
}
182158
}
183159

184-
private:
185-
/**
186-
* @brief データ保存処理
187-
* @param state 現在の走行状態
188-
* @param now 現在時刻(ミリ秒)
189-
*
190-
* 以下の条件がすべて満たされた場合にのみ保存します:
191-
* - 前回の保存から一定時間(30秒)経過
192-
* - GNSSがアイドル状態(更新処理中でない)
193-
* - 前回保存時からデータに変更がある
194-
*/
195-
void handleSave(const TripState &state, unsigned long now) {
196-
// 保存間隔のチェック
197-
if (now - lastSaveMs < DataStore::SAVE_INTERVAL_MS) return;
198-
199-
// GNSS更新中は保存しない(CPUリソースを節約)
200-
if (gnssData.status != UpdateStatus::NoChange) return;
201-
202-
// 現在のバッテリー電圧を取得
203-
float v = voltageMonitor.update();
204-
SaveData pData = PersistenceLogic::create(state, v);
205-
206-
// ダブルバッファで変更を検出
207-
const int prevSaveIdx = saveIdx;
208-
saveIdx = 1 - saveIdx;
209-
saveBuffers[saveIdx] = pData;
210-
211-
// 変更があった場合のみ実際に保存(EEPROM書き込み回数を削減)
212-
if (saveBuffers[saveIdx] != saveBuffers[prevSaveIdx]) dataStore.save(saveBuffers[saveIdx]);
213-
lastSaveMs = now;
160+
bool shouldUpdateUI() const {
161+
return (currentButton != Input::Event::NONE) || (currentTime - lastUiUpdateMs >= 500) ||
162+
TripLogic::isChanged(tripBuffer.previous(), tripBuffer.current()) ||
163+
(currentGnss.status == UpdateStatus::Updated);
214164
}
215165

216-
/**
217-
* @brief UI更新処理
218-
* @param prev 前回の走行状態
219-
* @param curr 現在の走行状態
220-
* @param now 現在時刻(ミリ秒)
221-
*
222-
* 以下のいずれかの条件で画面を更新します:
223-
* - 走行データに変更があった
224-
* - 強制更新フラグが設定されている
225-
* - GNSSデータが更新された
226-
* - 前回の更新から500ms以上経過(定期更新)
227-
*/
228-
void handleUI(const TripState &prev, const TripState &curr, unsigned long now) {
229-
bool periodic = (now - lastUiUpdateMs >= 500); // 500ms間隔の定期更新
230-
bool changed = TripLogic::isChanged(prev, curr); // データ変更検出
231-
bool forced = (curr.updateStatus == UpdateStatus::ForceUpdate); // 強制更新
232-
bool gnssUpd = (gnssData.status == UpdateStatus::Updated); // GNSS更新
233-
234-
if (changed || forced || gnssUpd || periodic) {
235-
// RTCから現在時刻を取得
236-
SpGnssTime currentTime = systemClock.now();
237-
238-
// 表示用データを生成
239-
DisplayState dData = DisplayLogic::create(curr, gnssData, currentTime, currentMode);
240-
241-
// フレームを生成(ダブルバッファリング)
242-
const int prevFrameIdx = frameIdx;
243-
frameIdx = 1 - frameIdx;
244-
frames[frameIdx] = FrameLogic::buildFrame(dData);
245-
246-
// フレームに変更があった場合のみ描画
247-
if (frames[frameIdx] != frames[prevFrameIdx]) {
248-
userInterface.draw(frames[frameIdx]);
249-
lastUiUpdateMs = now;
250-
}
251-
}
166+
void outputToStorage() {
167+
if (!shouldSave()) return;
168+
169+
SaveData nextSave = createSaveData(tripBuffer.current(), currentVoltage);
170+
171+
if (saveBuffer.apply(nextSave)) { dataStore.save(saveBuffer.current()); }
172+
lastSaveMs = currentTime;
173+
}
174+
175+
bool shouldSave() const {
176+
const bool shouldUpdate = (currentTime - lastSaveMs >= DataStore::SAVE_INTERVAL_MS);
177+
const bool gnssStable = (currentGnss.status == UpdateStatus::NoChange);
178+
return shouldUpdate && gnssStable;
179+
}
180+
181+
static SaveData createSaveData(const TripState &state, float voltage) {
182+
SaveData data;
183+
data.magicNumber = SAVE_DATA_MAGIC_NUMBER;
184+
data.totalDistance = state.distance.total;
185+
data.tripDistance = state.distance.trip;
186+
data.movingTimeMs = state.time.moving;
187+
data.maxSpeed = state.speed.max;
188+
data.voltage = voltage;
189+
data.updateStatus = state.updateStatus;
190+
data.crc = 0;
191+
return data;
252192
}
253193
};

0 commit comments

Comments
 (0)