目覚まし時計はスマホ??
皆さんは目覚まし時計をかけて寝ますか?
目覚まし時計といっても最近は スマートフォン(スマホ) のアラームを目覚まし時計代わりにされている方が多いのではないでしょうか。

しかし寝室にスマホを持ち込むことには、いくつかの問題点があると考えます。
睡眠の質の低下:スマホの青色光は体内時計を狂わせ、メラトニンの分泌を抑制します。これにより、寝つきが悪くなったり、睡眠の質が低下したりする可能性があります。
依存症のリスク:寝る直前までスマホを使用することで、スマホ依存症のリスクが高まります。就寝時間が遅くなったり、夜中に目が覚めてスマホをチェックしたりする習慣につながる可能性があります。
集中力の低下:寝室にスマホがあることで、睡眠中や起床直後にSNSやメールをチェックする誘惑に駆られやすくなります。これにより、朝の時間を有効に使えなくなったり、一日の集中力が低下したりする可能性があります。
いわゆる スマホ脳 ってやつですね。
従来の目覚まし時計は?
ただ、従来の目覚まし時計にも欠点があると考えています。
- アナログ時計の場合、秒針のカチカチ音が気になる
- 電池切れのリスクがある
- 無意識にアラームを切ってしまう

こんなことよくありませんか?
私はよくあります。
ではどうすればよいのか
そこで私はobnizを利用して、以下のような仕組みの目覚まし時計を作製しました。
- 就寝前にLINE Botにアラームを鳴らしたい時刻を送る。
- アラームを鳴らす時刻になったらobnizに接続されたスピーカーが鳴動する。
- 部屋の電気が点灯することでスピーカーの鳴動を止めることができる。
これにより、スマホを寝室に持ち込まず、なおかつ従来の目覚まし時計の欠点を解消できると考えました。
また、スピーカーの鳴動を止めるために物理ボタンではなく光をトリガーとすることで、強制的に電気をつけざるを得ないようにしました。
(電気のリモコンはベッドの近くに置かないこと前提!!)
デモ
仕組み

- make(LINEからGoogleスプレッドシートまで)
-
obniz開発者コンソール
(steinはGoogleスプレッドシートをAPI化するために利用)
使用したもの
- obniz Board 1Y (obnizOS 3.2.1)
- 照度センサー (Macron International Group社製 MI527/MI5527)
- スピーカー (株式会社村田製作所 PKM13EPYH4000-A0)
- 330Ω抵抗 (FAITHFUL LINK INDUSTRIAL社製 CFS50J330RB)
- ミニブレッドボード (Cixi Wanjie Electronic社製 BB-601(白))
制作物

Port | デバイス | 機能 |
---|---|---|
0 | スピーカー | 出力 |
1 | スピーカー | 出力 |
2 | 未接続 | - |
3 | 照度センサー | 入力 |
4 | 未接続 | - |
5 | 未接続 | - |
6 | 未接続 | - |
7 | 未接続 | - |
8 | 未接続 | - |
9 | 未接続 | - |
10 | 未接続 | - |
11 | 未接続 | - |
- | 抵抗GND側 | GND |
3.3V | 未接続 | - |
+ | 照度センサー電源側 | 5V |
makeのシナリオ
シナリオ1

シナリオ2


今回のソースコード
←の三角マークを押すと確認することができます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Obniz Alarm Control</title>
<script src="https://unpkg.com/obniz@3.31.0/obniz.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
</head>
<body>
<div id="obniz-debug"></div>
<h1>Obniz Alarm Control</h1>
<script>
const SPREADSHEET_URL = 'https://api.steinhq.com/v1/storages/***'; //***はスプレッドシートIDを入力
const LIGHT_THRESHOLD = 0.2;
const obnizId = 'YOUR_OBNIZ_ID';
const obniz = new Obniz(obnizId);
let buzzerStartTime = null;
let speaker = null;
let currentAlarmTimeout = null;
async function getAlarmTime() {
try {
const response = await axios.get(`${SPREADSHEET_URL}/Sheet1`);
console.log("Response from Google Sheets:", response.data);
if (!response.data || !response.data[5] || !response.data[5].value) {
throw new Error('Missing or invalid data structure in the response');
}
const alarmTimeString = response.data[5].value;
console.log("Alarm time from B7:", alarmTimeString);
const alarmTime = moment(alarmTimeString, "HH:mm");
if (!alarmTime.isValid()) {
throw new Error('Invalid time format in B7 cell');
}
return alarmTime;
} catch (error) {
console.error('Error fetching alarm time:', error.message);
if (error.response) {
console.error('Error response data:', error.response.data);
}
return null;
}
}
async function checkAlarmTime() {
const alarmTime = await getAlarmTime();
if (alarmTime === null) return;
const now = moment();
let timeUntilAlarm = moment.duration(alarmTime.diff(now));
if (timeUntilAlarm.asMilliseconds() < 0) {
timeUntilAlarm.add(1, 'day');
}
console.log(`Alarm will sound in ${timeUntilAlarm.asMilliseconds()} milliseconds`);
if (currentAlarmTimeout) {
clearTimeout(currentAlarmTimeout);
}
currentAlarmTimeout = setTimeout(() => {
speaker = obniz.wired("Speaker", {signal:0, gnd:1});
speaker.play(440, 0.5);
buzzerStartTime = moment();
}, timeUntilAlarm.asMilliseconds());
}
obniz.onconnect = async () => {
await checkAlarmTime();
setInterval(checkAlarmTime, 60000);
obniz.ad3.start((value) => {
if (value > LIGHT_THRESHOLD && buzzerStartTime) {
if (speaker) {
speaker.stop();
}
const buzzerStopTime = moment();
const durationMs = buzzerStopTime.diff(buzzerStartTime);
const duration = (durationMs / 1000).toFixed(3);
updateDurationInSpreadsheet(duration);
console.log(duration);
buzzerStartTime = null;
}
});
};
// 修正された updateDurationInSpreadsheet 関数
async function updateDurationInSpreadsheet(duration) {
try {
// B8セルに実行時間を記録 (この処理は以前と同じ)
const durationResponse = await axios.put(`${SPREADSHEET_URL}/Sheet1`, {
"condition": { "device": "Duration" },
"set": { "value": duration }
});
console.log('Duration updated successfully in B8:', durationResponse.data);
// 新しい行を追加し、T、duration、現在の日時を記録
const currentTime = moment().format('YYYY-MM-DD HH:mm:ss');
const newRowResponse = await axios.post(`${SPREADSHEET_URL}/Sheet1`, [{
"device": "T",
"value": duration,
"last update": currentTime
}]);
console.log('New row added successfully:', newRowResponse.data);
} catch (error) {
console.error('Error updating spreadsheet:', error.message);
if (error.response) {
console.error('Error response data:', error.response.data);
}
}
}
obniz.connect();
</script>
</body>
</html>
あとがき
今回は今までとは打って変わって アート思考 ではなく、デザイン思考 のモノづくりに挑戦しましたが、アイデア出しには非常に苦労しました。
普段の何気ない生活に疑問を持つことがデザイン思考につながるのではないかと感じました。
WISH
実は上記のソースコードでは、アラームが鳴ってからアラームを止める秒数を計測しており、その時間をGoogleスプレッドシートのB8セルに転記するようになっています。
その時間をLINE Botからユーザーに返すことで、起床をタイムアタックのように楽しめるのではないかと考えていました。
今回は、その転記した秒数をユーザーに返すという機能まで実装しきれず、悔しい結果となってしまいました・・・
また次回以降挑戦していきたいですね。
アラームを止めた秒数を通知する機能を追加することができました。
ただ、この方法ではアラームを止めた秒数の通知まで 最大1分間 かかってしまいます・・・
もっとユーザーが使いやすいものを目指していきたいですね。
それこそがデザイン思考に通ずるのではないかと思う次第です。
修正1
以下のコードに修正することで 最大1分間の待ちが不要 になります。
簡単に解説すると上記makeのシナリオでは、
- シナリオ1でアラームを止めた秒数をGoogleスプレッドシートに転記
↓ - シナリオ2でアラームを止めた秒数の更新を 1分間隔 で監視する仕組みでした。
そんな回りくどいことをせず、obnizから直接LINE Botに秒数を返せばいいのでは?と思うかもしれませんが、
obniz開発者コンソールのWebブラウザ上からは CORS(Cross-Origin Resource Sharing)に阻まれて、Webブラウザから直接LINE Botにメッセージを送れない ため上記のような仕組みとなっていました。
CORSに関しては以下の記事が分かりやすいです。
改善内容
- makeで アラームを止めた秒数をユーザーに返す 中継API を作成する(シナリオ2の先頭のモジュールをWebhookに変えるだけ)
- obnizがアラームを止めた秒数をGoogleスプレッドシートに転記するとともに、作成した中継APIを呼び出す
- 転記した秒数がLINE Botから送られる

修正2
修正2では以下 2点 の修正を行っていきます。
-
上記では、Googleスプレッドシートからアラーム時刻を取得したり、逆にアラーム実行時間を記録するために axios のライブラリを使用しています。
しかし axios はライブラリのインストールが必要ということに加え、ファイルサイズが大きいという点がデメリットになります。
そこで fetch を使えば、JavaScriptの標準機能として組み込まれているので、追加のライブラリをインストールする必要はありません。 -
また、日付や時刻の取得は Moment.js を使用しています。
しかし Moment.js は開発が既に終了していることに加え、ファイルサイズが大きいという点がデメリットになります。
そこで Day.js を使えば、いまも活発に開発が進められているため、新規機能の追加などにも柔軟に対応することができます。
修正したソースコード
←の三角マークを押すと確認することができます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Obniz Alarm Control</title>
<script src="https://unpkg.com/obniz@3.31.0/obniz.js"></script>
<script src="https://unpkg.com/dayjs@1.10.7/dayjs.min.js"></script>
<script src="https://unpkg.com/dayjs@1.10.7/plugin/customParseFormat.js"></script>
<script src="https://unpkg.com/dayjs@1.10.7/plugin/duration.js"></script>
<script src="https://unpkg.com/dayjs@1.10.7/plugin/utc.js"></script>
<script src="https://unpkg.com/dayjs@1.10.7/plugin/timezone.js"></script>
</head>
<body>
<div id="obniz-debug"></div>
<h1>Obniz Alarm Control</h1>
<script>
dayjs.extend(dayjs_plugin_customParseFormat);
dayjs.extend(dayjs_plugin_duration);
dayjs.extend(dayjs_plugin_utc);
dayjs.extend(dayjs_plugin_timezone);
const SPREADSHEET_URL = 'https://api.steinhq.com/v1/storages/6678afaa4d11fd04f00baaeb';
const LIGHT_THRESHOLD = 0.2;
const obnizId = 'YOUR_OBNIZ_ID'; // あなたのobniz IDに置き換えてください
const WEBHOOK_URL = 'https://hook.us1.make.com/wonmuw5mrp3noj1ngv3buux751vyavbs';
const obniz = new Obniz(obnizId);
let buzzerStartTime = null;
let speaker = null;
let currentAlarmTimeout = null;
async function getAlarmTime() {
try {
const response = await fetch(`${SPREADSHEET_URL}/Sheet1`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log("Response from Google Sheets:", data);
if (!data || !data[5] || !data[5].value) {
throw new Error('Missing or invalid data structure in the response');
}
const alarmTimeString = data[5].value;
console.log("Alarm time from B7:", alarmTimeString);
const alarmTime = dayjs(alarmTimeString, "HH:mm");
if (!alarmTime.isValid()) {
throw new Error('Invalid time format in B7 cell');
}
return alarmTime;
} catch (error) {
console.error('Error fetching alarm time:', error.message);
return null;
}
}
async function checkAlarmTime() {
const alarmTime = await getAlarmTime();
if (alarmTime === null) return;
const now = dayjs();
let timeUntilAlarm = dayjs.duration(alarmTime.diff(now));
if (timeUntilAlarm.asMilliseconds() < 0) {
timeUntilAlarm = dayjs.duration(alarmTime.add(1, 'day').diff(now));
}
console.log(`Alarm will sound in ${timeUntilAlarm.asSeconds().toFixed(2)} seconds`);
displayAlarmTime(alarmTime);
if (currentAlarmTimeout) {
clearTimeout(currentAlarmTimeout);
}
currentAlarmTimeout = setTimeout(() => {
speaker = obniz.wired("Speaker", {signal:0, gnd:1});
speaker.play(440, 0.5);
buzzerStartTime = dayjs();
console.log("Alarm start");
obniz.display.clear();
obniz.display.print("Alarm start");
}, timeUntilAlarm.asMilliseconds());
}
function displayAlarmTime(alarmTime) {
obniz.display.clear();
obniz.display.print(`Alarm at ${alarmTime.format('HH:mm')}`);
}
obniz.onconnect = async () => {
console.log("Obniz connected");
await checkAlarmTime();
setInterval(checkAlarmTime, 60000);
obniz.ad3.start((value) => {
if (value > LIGHT_THRESHOLD && buzzerStartTime) {
if (speaker) {
speaker.stop();
}
const buzzerStopTime = dayjs();
const durationMs = buzzerStopTime.diff(buzzerStartTime);
const duration = (durationMs / 1000).toFixed(3);
console.log(`Alarm duration: ${duration} seconds`);
obniz.display.clear();
obniz.display.print(`Duration: ${duration}s`);
updateDurationInSpreadsheet(duration)
.then(() => {
setTimeout(() => sendToWebhook(duration), 1000);
setTimeout(() => checkAlarmTime(), 10000); // 10秒後に次のアラーム時刻を表示
})
.catch(error => {
console.error('Error in update and webhook process:', error);
});
buzzerStartTime = null;
}
});
};
async function updateDurationInSpreadsheet(duration) {
try {
const currentTime = dayjs().tz("Asia/Tokyo").format('YYYY-MM-DD HH:mm:ss');
const response = await fetch(`${SPREADSHEET_URL}/Sheet1`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"condition": { "device": "Duration" },
"set": {
"value": duration,
"last update": currentTime
}
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
console.log('Google Spreadsheet update successful.');
return true;
} catch (error) {
console.error('Error updating spreadsheet:', error.message);
return false;
}
}
async function sendToWebhook(duration) {
try {
const response = await fetch(WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ duration: duration })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
console.log('Webhook call successful');
return true;
} catch (error) {
console.error('Error sending data to webhook:', error);
return false;
}
}
obniz.connect();
</script>
</body>
</html>