-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLightSensor.cpp
More file actions
221 lines (195 loc) · 7.75 KB
/
Copy pathLightSensor.cpp
File metadata and controls
221 lines (195 loc) · 7.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
#include "LightSensor.h"
#include "Backlight.h"
#include "CameraCapture.h"
#include "Config.h"
#include "DeviceStore.h"
#include "SettingsStore.h"
namespace {
// 今どの段階にいるか。
//
// Stopped …… 止まっている
// Waiting …… 次の測定の時刻を待っている(届く絵は見ずに捨てる)
// Skipping … 感度を変えた直後の1枚を捨てている
// Measuring 1枚届くのを待って、測る
//
// カメラは開いている間ずっと絵を送り続けます。受け取りを止めることはできない
// ので、測らない間は届いたものを見ずに捨てます。
enum class State { Stopped, Waiting, Skipping, Measuring };
State g_state = State::Stopped;
uint32_t g_nextMeasureMillis = 0;
int g_settleCount = 0;
long g_value = -1;
uint32_t g_measuredMillis = 0;
String g_lastError;
bool g_dark = false;
// 段の番号に対応する明るさ指標。
//
// おおよそ1.6倍ずつ増える並びです。人の目は明るさの比で感じるので、等間隔に
// 並べるよりこのほうが目盛りとして使いやすくなります。
//
// 実測では、照明の点いた部屋が490〜680、暗くした部屋が12〜20です。
// その両方を含み、どちらの側にも余裕があるように3000まで伸ばしてあります。
const long kThresholdTable[] = {1, 2, 3, 5, 8, 12, 20,
30, 50, 80, 120, 200, 300, 500,
800, 1200, 2000, 3000};
const int kThresholdCount = int(sizeof(kThresholdTable) / sizeof(kThresholdTable[0]));
// 新しい値を、前の値と混ぜてならします。
void blendValue(long fresh) {
if (g_value < 0) {
g_value = fresh;
return;
}
g_value = (g_value * LIGHT_SMOOTH_WEIGHT_PCT + fresh * (100 - LIGHT_SMOOTH_WEIGHT_PCT)) / 100;
}
// 次の測定へ向かいます。受け取りは止めたままにしておきます。
void goWaiting() {
g_state = State::Waiting;
g_nextMeasureMillis = millis() + LIGHT_MEASURE_INTERVAL_MS;
}
// 暗いかどうかを判定し、変わっていれば画面の明るさに反映します。
//
// 暗くなる境目と明るくなる境目を分けています。同じ値で行き来させると、
// 閾値の近くで画面がちらつくためです。
void updateDarkness() {
if (g_value < 0) return;
long threshold = LightSensor::thresholdValueOf(SettingsStore::lightThresholdStep());
long recover = threshold * LIGHT_THRESHOLD_RECOVER_PCT / 100;
bool dark = g_dark ? (g_value < recover) : (g_value < threshold);
g_dark = dark;
// 判断が変わっていなくても、毎回伝えます。
//
// 変わったときだけ伝えると、画面側の状態が何かの拍子にずれたときに、
// こちらは「変わっていない」と見なして直せなくなります。毎回伝えておけば、
// ずれても次の測定で揃います。同じ値を入れ直すだけなので害はありません。
Backlight::applyAuto(dark);
}
// 段階を1つだけ進めます。待ちません。
void step() {
switch (g_state) {
case State::Stopped:
return;
case State::Waiting:
if (int32_t(millis() - g_nextMeasureMillis) < 0) return;
// 待っている間に届いた古い絵は見たことにして、次に届く1枚を測ります。
CameraCapture::releaseFrame();
g_settleCount = 0;
g_state = State::Measuring;
return;
case State::Skipping:
// 感度を変える前の設定で撮られた1枚です。測らずに捨てます。
if (!CameraCapture::frameReady()) return;
CameraCapture::releaseFrame();
g_state = State::Measuring;
return;
case State::Measuring:
if (!CameraCapture::frameReady()) return;
if (!CameraCapture::measureFrame()) {
g_lastError = "映像が届きません";
CameraCapture::releaseFrame();
goWaiting();
return;
}
g_settleCount++;
if (CameraCapture::settleStep() || g_settleCount >= LIGHT_SETTLE_MAX_FRAMES) {
// 感度が合いました。この1枚の値を採用します。
//
// 回数の上限で抜けた場合も採用します。合いきらないのは明るさが極端な
// ときで、そのとき出る値もそれなりに使えるためです。
long fresh = CameraCapture::lightValue();
if (fresh >= 0) {
blendValue(fresh);
g_measuredMillis = millis();
g_lastError = "";
updateDarkness();
}
goWaiting();
return;
}
// 感度を変えたので、次の1枚は捨ててから測り直します。
CameraCapture::releaseFrame();
g_state = State::Skipping;
return;
}
}
} // namespace
namespace LightSensor {
bool begin() {
if (g_state != State::Stopped) return true;
// 疑似的な機器として登録します。既に登録されていれば何もしません。
if (!DeviceStore::isRegistered(LIGHT_DEVICE_ID)) {
DeviceStore::addDevice(LIGHT_DEVICE_ID, LIGHT_DEVICE_NAME, LIGHT_DEVICE_NAME);
}
if (!CameraCapture::beginSession()) {
g_lastError = CameraCapture::status();
Serial.printf("[LIGHT] 開始できません: %s\n", g_lastError.c_str());
return false;
}
g_lastError = "";
g_settleCount = 0;
g_state = State::Measuring;
return true;
}
void end() {
if (g_state == State::Stopped) return;
CameraCapture::endSession();
g_state = State::Stopped;
}
void update() {
#if DIAG_LIGHT_TIMING
// この処理が本体を止めていないかを確かめるため、かかった時間を測ります。
// 見積りではなく実測で言えるようにするためです。
uint32_t enteredUs = micros();
State before = g_state;
#endif
step();
#if DIAG_LIGHT_TIMING
uint32_t tookUs = micros() - enteredUs;
if (tookUs >= 1000) {
Serial.printf("[LIGHT] update %uus(%d→%d)\n", unsigned(tookUs), int(before),
int(g_state));
}
#endif
}
void reevaluate() {
// 境目を動かしたときに、次の測定を待たずに判断し直します。
//
// 判断が変わらなくても画面へ反映します。自動調整を切から入に戻した直後は、
// 判断は同じでも画面の明るさが合っていないためです。
updateDarkness();
Backlight::applyCurrent();
}
long thresholdValueOf(int step) {
int index = step - LIGHT_THRESHOLD_STEP_MIN;
if (index < 0) index = 0;
if (index >= kThresholdCount) index = kThresholdCount - 1;
return kThresholdTable[index];
}
bool isDark() { return g_dark; }
bool isLightDevice(const String &id) { return id == LIGHT_DEVICE_ID; }
bool getReading(const String &id, SensorReading &out) {
if (!isLightDevice(id) || g_measuredMillis == 0) return false;
SensorReading r;
r.light = g_value;
r.lightMillis = g_measuredMillis;
r.lastSeenMillis = g_measuredMillis;
// 本体が測るのは明るさだけです。温度・湿度・電池はどれも持ちません。
// タイルにはいずれも「—」と出ます。
r.hasTempHum = false;
r.hasBattery = false;
// 測定が止まっていれば「未受信」にします。判定の時間はBLEの機器と同じです。
r.online = (millis() - g_measuredMillis) < DEVICE_OFFLINE_TIMEOUT_MS;
out = r;
return true;
}
long value() { return g_value; }
uint32_t measuredMillis() { return g_measuredMillis; }
String status() {
if (g_state == State::Stopped) return "止まっています";
if (g_lastError.length() > 0) return g_lastError;
if (g_value < 0) return "測定中です";
char buf[96];
snprintf(buf, sizeof(buf), "明るさ指標 %ld(露出%u ゲイン%d段)", g_value,
unsigned(CameraCapture::exposureValue()), CameraCapture::gainIndex());
return String(buf);
}
} // namespace LightSensor