前回でLチカができました。
ここから、マイコンからサーバーへデータを送信し、データベースに記録する機能を作っていきます。
Firebaseの設定
GoogleのFirebaseにアクセスし、プロジェクトを作成し、Firestoreでデータベースを作成します(※ 最初は間違えてRealtime Databaseを作成していました)。
データベースの設定をします。
無料の料金プランだとAPIが使えないので有料プランBlazeを設定します。
ローカルPCでFirebase CLIのインストールをします。
npm install -g firebase-tools
適当なディレクトリに移動し、firebase login を実行、Yesと答えて認証します。
$ cd firebase_directory$ firebase login
i Firebase optionally collects CLI and Emulator Suite usage and error reporting information to help improve our products. Data is collected in accordance with Google's privacy policy (https://policies.google.com/privacy) and is not used to identify you.
? Allow Firebase to collect CLI and Emulator Suite usage and error reporting information? Yes
i To change your data collection preference at any time, run `firebase logout` and log in again.
Visit this URL on this device to log in:
ログイン - Google アカウント
Waiting for authentication...
✔ Success! Logged in as xxx@gmail.com
初期化コマンドを入力し、それぞれの質問に回答します。
% firebase init functions
######## #### ######## ######## ######## ### ###### ########
## ## ## ## ## ## ## ## ## ## ##
###### ## ######## ###### ######## ######### ###### ######
## ## ## ## ## ## ## ## ## ## ##
## #### ## ## ######## ######## ## ## ###### ########
You're about to initialize a Firebase project in this directory:
/Users/user/firebase
=== Project Setup
First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.
? Please select an option: Use an existing project
? Select a default Firebase project for this directory: temperature-measurement---test (Temperature measurement - test)
i Using project temperature-measurement---test (Temperature measurement - test)
=== Functions Setup
Let's create a new codebase for your functions.
A directory corresponding to the codebase will be created in your project
with sample code pre-configured.
See https://firebase.google.com/docs/functions/organize-functions for
more information on organizing your functions using codebases.
Functions can be deployed with firebase deploy.
? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? Yes
✔ Wrote functions/package.json
✔ Wrote functions/.eslintrc.js
✔ Wrote functions/index.js
✔ Wrote functions/.gitignore
? Do you want to install dependencies with npm now? Yes
added 588 packages, and audited 589 packages in 58s
66 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
i Writing configuration info to firebase.json...
i Writing project information to .firebaserc...
i Writing gitignore file to .gitignore...
✔ Firebase initialization complete!
これで指定のディレクトリ配下にあるindex.jsを修正します。
const {onRequest} = require("firebase-functions/v2/https");
const admin = require("firebase-admin");
// 必要に応じてFirebase Admin SDKを初期化します
// この行は、まだ初期化されていない場合にのみ必要です
if (admin.apps.length === 0) {
admin.initializeApp();
}
// 新しい関数の定義
exports.addMessage = onRequest(async (req, res) => {
// リクエストからデータを抽出
const text = req.query.text;
// 現在の日時を取得
const timestamp = new Date().toISOString();
// データと日時をFirebaseデータベースに保存
const writeResult = await admin.firestore().collection("messages")
.add({text: text, timestamp: timestamp});
// クライアントにレスポンスを送信
res.json({result: `Message with ID: ${writeResult.id} added.`});
});
ターミナルで作成した関数をFirebaseにデプロイします。
$ firebase deploy --only functions
=== Deploying to 'temperature-measurement---test'...
i deploying functions
Running command: npm --prefix "$RESOURCE_DIR" run lint
> lint
> eslint .
✔ functions: Finished running predeploy script.
i functions: preparing codebase default for deployment
i functions: ensuring required API cloudfunctions.googleapis.com is enabled...
i functions: ensuring required API cloudbuild.googleapis.com is enabled...
i artifactregistry: ensuring required API artifactregistry.googleapis.com is enabled...
✔ functions: required API cloudbuild.googleapis.com is enabled
✔ functions: required API cloudfunctions.googleapis.com is enabled
✔ artifactregistry: required API artifactregistry.googleapis.com is enabled
i functions: Loading and analyzing source code for codebase default to determine what to deploy
Serving at port 8504
i functions: preparing functions directory for uploading...
i functions: packaged /Users/user/program/firebase/functions (146.31 KB) for uploading
i functions: ensuring required API run.googleapis.com is enabled...
i functions: ensuring required API eventarc.googleapis.com is enabled...
i functions: ensuring required API pubsub.googleapis.com is enabled...
i functions: ensuring required API storage.googleapis.com is enabled...
✔ functions: required API eventarc.googleapis.com is enabled
✔ functions: required API pubsub.googleapis.com is enabled
✔ functions: required API run.googleapis.com is enabled
✔ functions: required API storage.googleapis.com is enabled
i functions: generating the service identity for pubsub.googleapis.com...
i functions: generating the service identity for eventarc.googleapis.com...
✔ functions: functions folder uploaded successfully
i functions: updating Node.js 18 (2nd Gen) function xxx(us-central1)...
✔ functions[xxx(us-central1)] Successful update operation.
Function URL (xxx(us-central1)): https://xxx-pfv42ema6a-uc.a.run.app
i functions: cleaning up build files...
✔ Deploy complete!
Project Console: https://console.firebase.google.com/project/temperature-measurement---test/overview
Firebaseのサイトでデプロイした関数を確認できました。
ここからリクエストのURLを確認します。
例:https://[region]-[project-id].cloudfunctions.net/[function-name]
ここからが大変でした。
micropythonの送信プログラムでエラーが頻発していました。
import machine
import onewire
import ds18x20
import urequests
from machine import Pin
import time
import network
import ubinascii
# LEDを点灯
def light_on(pin_num):
# LEDを接続
led = Pin(pin_num, Pin.OUT)
# LEDをONにする
led.value(1)
# GPIOピンを出力モードに設定し、内部プルダウン抵抗を有効化
led = Pin(pin_num, Pin.OUT, Pin.PULL_DOWN)
# LEDを点灯
def light_off(pin_num):
# LEDを接続
led = Pin(pin_num, Pin.OUT)
# LEDをONにする
led.value(0)
# GPIOピンを出力モードに設定し、内部プルダウン抵抗を有効化
led = Pin(pin_num, Pin.OUT, Pin.PULL_DOWN)
# 温度を計測
def read_temperature(pin_num):
# ESP32のデータピンを設定(例:GPIO 4)
dat_pin = machine.Pin(pin_num)
# 1-Wireバスを初期化
ds_sensor = ds18x20.DS18X20(onewire.OneWire(dat_pin))
# センサーのロムコードを取得
roms = ds_sensor.scan()
print('Found DS18B20 sensors:', roms)
ds_sensor.convert_temp()
for rom in roms:
temperature = ds_sensor.read_temp(rom)
print("rom :",rom)
print("temperature :", temperature, "度")
return temperature
# マイコンのMACアドレスを取得
def get_unique_id():
mac = network.WLAN().config('mac')
unique_id = ubinascii.hexlify(mac).decode()
print("unique_id :",unique_id)
return unique_id
# Firebase関数にデータを送信する関数
def send_data_to_firebase(temperature, mac_id):
# Wi-Fiの設定
ssid = 'xxx'
password = 'xxx'
# Wi-Fiステーションモードを有効化
station = network.WLAN(network.STA_IF)
station.active(True)
station.connect(ssid, password)
# 接続が完了するまで待機
while not station.isconnected():
pass
print('Wi-Fiに接続しました')
# Firebase関数のURLをここに入力
url = "https://...."
headers = {'Content-Type': 'application/json'}
payload = {'temperature': temperature, 'macAddress': mac_id}
print("Sending payload:", payload)
try:
response = urequests.post(url, json=payload, headers=headers)
print("Data sent. Response:", response.json())
except Exception as e:
print("Error sending data:", e)
# MACアドレスを取得
mac_id = get_unique_id()
print("mac_id type :", type(mac_id))
print("mac_id :", mac_id)
# LEDを点灯
light_on(14)
# 温度を読み取る
temperature = read_temperature(25)
print("temperature type :", type(temperature))
print("temperature :", temperature)
# データ送信
send_data_to_firebase(temperature, mac_id)
# 待つ
time.sleep(1)
# LEDをOFFにする
light_off(14)
・エラー情報
$ ampy --port /dev/tty.usbserial-0001 run /Users/user/micropython/main.py
unique_id : 08d1f9e822ec
mac_id type : <class 'str'>
mac_id : 08d1f9e822ec
Found DS18B20 sensors: [bytearray(b'(\xf5\tC\xd4\xe1<7')]
rom : bytearray(b'(\xf5\tC\xd4\xe1<7')
temperature : 25.0 度
temperature type : <class 'float'>
temperature : 25.0
Wi-Fiに接続しました
Sending payload: {'macAddress': '08d1f9e822ec', 'temperature': 25.0}
Data sent. Response: {'error': 'Invalid input: Temperature is not a number'}
どうも送る温度データtemperatureの形式が想定通りの数値になっていないため、エラーになっているようです。
postmanをインストールしてきちんとデータが送信されるか確認します。
ここで以下のように設定してsendして確認します。
- リクエストのタイプをPOSTに設定
- URLフィールドにFirebase関数のURLを入力
- リクエストのヘッダーセクションにContent-Typeとしてapplication/jsonを追加
- rawタイプを選択して、JSON形式を選ぶ
- JSONオブジェクトを以下のように入力
そこでsendを押下し送信します。
すると以下のエラーが出ました。
{
"error": "7 PERMISSION_DENIED: Cloud Firestore API has not been used in project temperature-measurement---test before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/firestore.googleapis.com/overview?xxx then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry."
}
これはGoogle Cloud のFirestore APIが有効でないため発生しているエラーらしいので、記載されたURLにアクセスしてAPI有効にします。
API有効後もpostmanで以下のエラーが出ました。
{
"error": "5 NOT_FOUND: "
}
認証を実施
Firebaseのプロジェクトの設定からサービスアカウントを選択し、示されたコードをコピーします。
「新しい秘密鍵を生成」をクリックし、jsonファイルをダウンロード、コードに指定します。
以下のコードをPythonプログラムに追加し、Firebaseの認証を行います。
import requests
import firebase_admin
from firebase_admin import credentials
cred = credentials.Certificate("/Users/user/program/temperature-measurement---test-firebase-adminsdk-h1irq-e903f7c425.json")
firebase_admin.initialize_app(cred)
firebase_admin をインストールしようとするとエラーが出ました。
conda install -c conda-forge firebase-admin
Channels:
- conda-forge
- defaults
Platform: osx-arm64
Collecting package metadata (repodata.json): done
Solving environment: failed
PackagesNotFoundError: The following packages are not available from current channels:
- firebase-admin
Current channels:
- https://conda.anaconda.org/conda-forge
- defaults
To search for alternate channels that may provide the conda package you're
looking for, navigate to
Just a moment...
and use the search bar at the top of the page.
ARMのMacについて、Anacondaが対応していないためエラーになっているようです。
pip3 install firebase-admin
上記コマンドを実行しインストールしてもダメだったので調べたところ、Anaconda Navigatorから設定できるようです。
Environmentsからターミナルを起動します。
ここで
$ conda list firebase-admin
$ pip list | grep firebase-admin
$ pip3 list | grep firebase-admin
でfirebase-adminがインストールされているか確認しますが、インストールされていませんでした。
インストールを試みます。
$ conda install -c conda-forge firebase-admin
$ pip3 install firebase-admin
condaコマンドはfirebase-adminパッケージがAnacondaの現在のチャンネルで利用可能でないためできませんでした。
pip3のコマンドは実行でき、インストールができました。
$ pip3 list | grep firebase-admin
firebase-admin 6.2.0
そこでPythonプログラムを実行したところ、前回と違うエラーが出ました。
・前回のエラー
Data sent. Response: {'error': 'Invalid input: Temperature is not a number'}
・今回のエラー
Data sent. Response: {'error': '5 NOT_FOUND: '}
URLの指定に誤りがあったようです。
コードで記載したCloud Run URLをfuncitonデプロイ時に出力された、
Project Console: https://console.firebase.google.com/project/xxxx
に変更したところ、以下のようにログが変わりました。
Sending payload: {'temperature': 29.5, 'macAddress': '08d1f9e822ec'}
Error sending data: Expecting value: line 1 column 1 (char 0)
Node.jsの未インストールが原因かと思いインストールしても変わりませんでした。
conda install -c conda-forge nodejs
ここで、Firebaseのエミュレータを試してみます。
firebase emulators:start --only functions
・エミュレータの起動ログ
✔ functions: Loaded functions definitions from source: xxx.
✔ functions[us-central1-xxx]: http function initialized (http://127.0.0.1:5001/xxx/us-central1/xxx).
上記より取得したCloud Run URL、「http://127.0.0.1:5001/xxx/us-central1/xxx」をPythonプログラムに追加します。それで試すと、
Sending payload: {'temperature': 29.5, 'macAddress': '08d1f9e822ec'}
Data sent. Response: {'error': 'error: 7, 7 PERMISSION_DENIED: Cloud Firestore API has not been used in project xxx before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/firestore.googleapis.com/overview?project=temperature-measurement-b3fcd then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.'}
というエラーが出ました。ここでAPIの設定を行ってリトライします。
Sending payload: {'temperature': 29.5, 'macAddress': '08d1f9e822ec'}
Data sent. Response: {'error': 'error: 5, 5 NOT_FOUND: The database (default) does not exist for project temperature-measurement-b3fcd Please visit https://console.cloud.google.com/datastore/setup?project=xxx to add a Cloud Datastore or Cloud Firestore database. '}
データベースがないというエラーが出たので、エラー記載のURLに遷移し、Cloud Datastoreの設定をします。
Sending payload: {'temperature': 29.5, 'macAddress': '08d1f9e822ec'}
Data sent. Response: {'result': 'Temperature data with ID: ZdKleB3JTcmZkUmUpqSB added.'}
すると上記のように意図するように書き込まれたようです。
Cloud Run URLについてはコードの記載で2種類あり、ここからわかることは、実際にデプロイした関数についてはURLが間違っていたようです。
しかし、エミュレータのUIにアクセスするとデータベースが作成されておらず、見ることができませんでした。
調べると、「firebase emulators:start –only functions」のコマンドでFunctions Emulatorのみを起動してしまっているのが原因のようです。以下のコマンドでエミュレータを起動すると問題は解決しました。
firebase emulators:start
・データベースの指定の間違い
constwriteResult=awaitadmin.firestore()
関数のプログラムで上記のようにFirestoreデータベースを指定しているのに、デバッグ時はRealtime Databaseの画面を注視していました。そのため見当違いなデバッグをしてしまっていました。
誤:https://[region]-[project-id].cloudfunctions.net/[function-name]
正:https://xxx-uc.a.run.app(Function画面で確認)
Cloud Runにはバージョンが2つあり、以下のコード記述だと、「https://xxx.a.run.app(Firebase Functionsバージョン2)」になります。
const {onRequest} = require("firebase-functions/v2/https");
以下のコード記述だと、「https://us-central1-xxx.cloudfunctions.net/xxx(Firebase Functionsバージョン1)」になります。
const functions = require("firebase-functions");
const moment = require("moment-timezone");
そのエラーは以下になります。
$ firebase deployi functions: Loading and analyzing source code for codebase default to determine what to deploy
Serving at port 8225i functions: updating Node.js 18 (2nd Gen) function addTemperatureData(us-central1)...
Could not create or update Cloud Run service addtemperaturedata, Container Healthcheck failed. Revision 'addtemperaturedata-00003-geb' is not ready and cannot serve traffic. The user-provided container failed to start and listen on the port defined provided by the PORT=8080 environment variable. Logs for this revision might contain more information.Logs URL: https://console.cloud.google.com/...
For more troubleshooting guidance, see https://cloud.google.com/run/docs/troubleshooting#container-failed-to-startFunctions deploy had errors with the following functions:
xxx(us-central1)
i functions: cleaning up build files...Error: There was an error deploying functions
npm install moment-timezone --save
npm uninstall moment-timezone
npm install moment-timezone
rm -rf node_modules
rm package-lock.json
npm install
emulators: The Emulator UI is not starting, either because none of the running emulators have a UI component or the Emulator UI cannot determine the Project ID. Pass the --project flag to specify a project.
上記のようなエラーが出ていました。
"emulators": {
"ui": {
"enabled": true
},
$ firebase emulators:start
i emulators: Starting emulators: functions, firestore, database, hosting, extensions
⚠ firestore: Port 8080 is not open on localhost (127.0.0.1,::1), could not start Firestore Emulator.
⚠ firestore: To select a different host/port, specify that host/port in a firebase.json config file:
{
// ...
"emulators": {
"firestore": {
"host": "HOST",
"port": "PORT"
}
}
}
i emulators: Shutting down emulators.
⚠ ui: Emulator UI unable to start on port 4000, starting on 4001 instead.
⚠ hosting: Hosting Emulator unable to start on port 5000, starting on 5002 instead.Error: Could not start Firestore Emulator, port taken.
ポート8080が使用中なので以下のコマンドで確認します。
lsof -i :8080
例えば、java 93818 と表示されたら、
kill 93818
と入力してポートを開放します。
const timestamp = moment().tz("UTC").format("YYYY-MM-DD HH:mm:ss");
// タイムゾーン環境変数を指定
const timezone = 'Asia/Tokyo';
process.env.TZ = timezone;
これでおおよそができたので、今度はデータベースのデータを表示するアプリケーションを作っていきます。