作ったもの
3Dプリンタ製の小さな神社の前で手を叩いて拝むと、slackにおみくじの結果が送られてくるデバイスです。
動機
作ったプログラムが動かない、設計したハードがうまく動かない、3Dプリントが何故か失敗しまくる、そんな神頼みでもいいから何とか動いて欲しいと思う時ってありますよね。
しかしPCや3Dプリンタに向かって祈るのは味気ない、ですよね?
そこでPCや3Dプリンターに乗せられるサイズの神社を作って、拝むと何かしら返答が返ってくるようにして楽しくしたい、と考えました。
システム構成
人感センサで接近を検知した際に、サウンドセンサが一定音量以上を認識するとESP-WROOM-02がWifiでGoogleApplicationScript(以下GAS)に送信します。GAS側でランダムに結果を出力して、slackに結果をメッセージとして送信します。
回路構成
主な材料(入手先はあってるかは分かりませんが...)
部品名 | 個数 | 備考 |
---|---|---|
ESP-WROOM-02 | 1 | 入手先:秋月 |
ESP-WROOM-02 ピッチ変換基板 コンパクト | 1 | 入手先:千石 |
SB412A(焦電型赤外線人感センサー) | 1 | 入手先:秋月 |
GROVE - 音センサ | 1 | 入手先:Switch Science |
三端子レギュレーターNJU7223DL1 | 1 | 入手先:秋月 |
ロープロファイルピンヘッダ 7.7㎜ 40P | 1 | 秋月 |
タクトスイッチ | 2 | 秋月で売ってるもの |
チップ抵抗 2012サイズ 10kΩ | 5 | |
チップコンデンサ 2012サイズ 0.1μF | 2 | |
XHコネクタ(2,3,4ピン) | 少々 | |
PHコネクタ(2ピン) | 少々 | |
lipoバッテリー(1s 400mAh) | 1 | 千石で売ってるもの |
NJM2903D 2回路入りコンパレータ | 1 | 秋月で売ってるもの |
PchチップMOSFET IRLML6402(20V3.7A) | 1 | 秋月で売ってるもの |
抵抗各種(1k,2,2k,10k) | 少々 | |
ユニバーサル基板 | 1 | 72*47サイズ |
基板用マイクロUSBコネクタ(電源専用) | 1 | 秋月で売ってるもの |
スライドスイッチ 1回路2接点 基板用 横向き | 1 | 秋月で売ってるもの |
神社の中にWifiモジュールを納めたかったため、
ESP32ではなく小型のESP-WROOM-02(ESP8266)を使用しています。
ESP-WROOM-02のアナログ入力は0~1Vまでですが、音センサは3.3Vで動いているので1kと2.2kの抵抗で分圧しています。
電源はLipo、USBのどちらからでも取れるようにしています
またlipoバッテリーに保護回路が内蔵されているらしいですが、電圧が一定以下だったらESP側に通電しないように分圧回路とコンパレータで追加で回路を組んでいます。
ですがUSB給電をメインで使うのでこれあんまりいらなさそうなんですよね...
回路図を書きましたが、1枚しか作らないのでユニバーサル基板で製作しました
はんだ付けが汚いですね()
ハードウェア
神社本体は土台と屋根の2パーツ構成となっています。
神社をPCにつける為のマウントはこのようになっています。
PCのディスプレイに挟む形状にしています
設計が終わったら3Dプリンターで印刷
基板と組み合わせるとこのようになります。
焦電センサの固定用のパーツも製作
基板そのものが見えないように全て覆っています
ESP-WROOM-02側のソースコード
人を感知して一定音量以上を検知したら通信するコード
#include <ESP8266WiFi.h>
#include "HTTPSRedirect.h"
#include "DebugMacros.h"
extern "C" {
#include "user_interface.h"
}
const char* ssid = "*****";//繋ぎたいWifiのSSIDを入力
const char* password = "*****";//Wifiのパスワードを入力
String UniqueID = "1";
const char* KEY = "*****";//GAS側のコードを入力
const char* host = "script.google.com";
const char *GScriptId = "*****";//GAS側のコードを入力
const int httpsPort = 443;
const char* fingerprint = "";
String url = String("/macros/s/") + GScriptId + "/exec?UniqueID=" + UniqueID +"&acc=" + 100;
// Fetch Google Calendar events for 1 week ahead
String url2 = String("/macros/s/") + GScriptId + "/exec?cal";
// Read from Google Spreadsheet
String url3 = String("/macros/s/") + GScriptId + "/exec?read";
String payload_base = "{\"command\": \"appendRow\", \
\"sheet_name\": \"Sheet1\", \
\"values\": ";
String payload = "";
HTTPSRedirect* client = nullptr;
unsigned int free_heap_before = 0;
unsigned int free_stack_before = 0;
void setup() {
Serial.begin(115200);
Serial.flush();
free_heap_before = ESP.getFreeHeap();
free_stack_before = ESP.getFreeContStack();
Serial.printf("Free heap: %u\n", free_heap_before);
Serial.printf("Free stack: %u\n", free_stack_before);
Serial.println();
Serial.println(ssid);
Serial.flush();
pinMode(12,INPUT);
}
void loop() {
int detect = digitalRead(12);
float accx = system_adc_read();
if(detect==1){//焦電センサの反応があるかどうか
if(accx>250){//音センサの反応があるかどうか
Serial.println(accx);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// Use HTTPSRedirect class to create a new TLS connection
client = new HTTPSRedirect(httpsPort);
client->setInsecure();
client->setPrintResponseBody(true);
client->setContentTypeHeader("application/json");
Serial.print("Connecting to ");
Serial.println(host);
bool flag = false;
for (int i=0; i<5; i++){
int retval = client->connect(host, httpsPort);
if (retval == 1) {
flag = true;
break;
}
else
Serial.println("Connection failed. Retrying...");
}
if (!flag){
Serial.print("Could not connect to server: ");
Serial.println(host);
Serial.println("Exiting...");
return;
}
String URL1 = "https://script.google.com/macros/s/";
URL1 += KEY;
URL1 += "/exec?";
URL1 += "UniqueID=";
URL1 += UniqueID;
URL1 += "&accx=";
URL1 += accx;
client->GET(URL1, host);
static int error_count = 0;
static int connect_count = 0;
const unsigned int MAX_CONNECT = 20;
//static bool flag = false;
}
}
}
Google Application Script側のソースコード
おみくじ結果出力
var postUrl = '*****';//slack側のURL
var username = '*****'; // 通知時に表示されるユーザー名
var icon = ':*****:'; // 通知時に表示されるアイコン
var message = '運勢'; // 投稿メッセージ
function doGet(e) {
let id = '*****'; //spreadsheet側のURL
let sheetName = 'シート1'; //シートの名前
var result;
// e.parameter has received GET parameters, i.e. temperature, humidity, illumination
if (e.parameter == undefined) {
result = 'Parameter undefined';
} else {
var sheet = SpreadsheetApp.openById(id).getSheetByName(sheetName);
var newRow = sheet.getLastRow() + 1; // get row number to be inserted
var rowData = [];
// get current time
rowData[0] = new Date();
rowData[1] = e.parameter.UniqueID;
rowData[2] = e.parameter.accx;
var newRange = sheet.getRange(newRow, 1, 1, rowData.length);
var unsei = Math.floor(Math.random()*6)+3
var range = sheet.getRange(unsei,5);//セル情報取得(E列に書いた運勢)
var value = range.getValue();
message= " 運勢 : "+value;
var unseip = unsei-2
var ruckyitem = Math.floor(Math.random()*41)+3
var range2 = sheet.getRange(ruckyitem,6);//セル情報取得(F列)
var value2 = range2.getValue();
message = message + "\n" + "ラッキーアイテム : " + value2;
var debug = Math.floor(Math.random()*(unseip))+3
var range3 = sheet.getRange(debug,7);//セル情報取得(G列)
var value3 = range3.getValue();
message = message + "\n" + " デバッグ : " + value3;
var study = Math.floor(Math.random()*(unseip))+3
var range4 = sheet.getRange(study,8);//セル情報取得(H列)
var value4 = range4.getValue();
message = message + "\n" + " 学問 : " + value4;
var lost = Math.floor(Math.random()*(unseip))+3
var range5 = sheet.getRange(lost,9);//セル情報取得(I列)
var value5 = range5.getValue();
message = message + "\n" + " 失物 : " + value5;
var love = Math.floor(Math.random()*1)+3
var range6 = sheet.getRange(love,10);//セル情報取得(J列)
var value6 = range6.getValue();
message = message + "\n" + " 恋愛 : " + value6;
var home = Math.floor(Math.random()*(unseip))+3
var range7 = sheet.getRange(home,11);//セル情報取得(K列)
var value7 = range7.getValue();
message = message + "\n" + " 転居 : " + value7;
var business = Math.floor(Math.random()*(unseip))+3
var range8 = sheet.getRange(business,12);//セル情報取得(L列)
var value8 = range8.getValue();
message = message + "\n" + " 商い : " + value8;
var travel = Math.floor(Math.random()*(unseip))+3
var range9 = sheet.getRange(travel,13);//セル情報取得(M列)
var value9 = range9.getValue();
message = message + "\n" + " 旅行 : " + value9;
var war = Math.floor(Math.random()*(unseip))+3
var range10 = sheet.getRange(war,14);//セル情報取得(N列)
var value10 = range10.getValue();
message = message + "\n" + " 争い : " + value10;
var hitokoto = Math.floor(Math.random()*25)+3
var range11 = sheet.getRange(hitokoto,15);//セル情報取得(O列)
var value11 = range11.getValue();
message = message + "\n" + " 一言コーナー : " + value11;
// 1 x rowData.length cells from (newRow, 1) cell are specified
//var newRange = sheet.getRange(newRow, 1, 1, rowData.length);
// insert data to the target cells
newRange.setValues([rowData]);
result = 'Ok';
}
var jsonData =
{
"username" : username,
"icon_emoji": icon,
"text" : message
};
var payload = JSON.stringify(jsonData);
var options =
{
"method" : "post",
"contentType" : "application/json",
"payload" : payload
};
UrlFetchApp.fetch(postUrl, options);
return ContentService.createTextOutput(result);
}
Googleスプレッドシート側の内容
完成した後での改良点、感想
slackで返事が来るとはいえ、反応しているかがよくわからないのでLEDか何かしらを取り付けて光ると良い気がしました
あと、フィラメントの素材の色そのままで彩りがないので、神社本体の塗装も検討したいですね
おわりに
神社は石や鏡を祀るものが多いらしいですが、この神社のご神体は石(マイコン)と言えますね
Wi-Fiの安定でも祈願しましょう
ちなみにこのデバイスを作る時に神頼みしたくなる時が何度かありました
参考文献
https://qiita.com/marlex/items/3e24a2c56a00421a317a GASとデバイス側(ESP32)の接続の資料
https://qiita.com/chihiro/items/c7b11abc78f5d806c3a8 GASとslackの接続についての資料
https://tonari-it.com/gas-omikuji/ GASでおみくじを出力するときの資料
投稿者の人気記事
-
swerve
さんが
2021/02/10
に
編集
をしました。
(メッセージ: 初版)
-
swerve
さんが
2021/02/10
に
編集
をしました。
-
swerve
さんが
2021/02/10
に
編集
をしました。
-
swerve
さんが
2021/02/11
に
編集
をしました。
-
swerve
さんが
2021/02/13
に
編集
をしました。
-
swerve
さんが
2022/01/04
に
編集
をしました。
(メッセージ: 記事の種類の設定)
ログインしてコメントを投稿する