[OCI]流量センサ、マイコン、Autonomous Databaseを用いたクラウド上での水使用量データの追跡・分析 (2020/10/01)

 流量センサ、マイコン、Autonomous Databaseを用いたクラウド上での水使用量データの追跡・分析 (2020/10/01)

https://blogs.oracle.com/developers/tracking-analyzing-water-usage-data-in-the-cloud-with-a-flow-sensor-microcontroller-autonomous-db
投稿者:Todd Sharp

この夏、私は幸運なことに、オラクルの素晴らしいインターンたちとバーチャルな時間を過ごすことができました。
エンジニアリングとテクノロジーに情熱を持った多くの大学生を見ることができて、私は幸せな気分になりました。
彼女は明らかにソフトウェア開発に興味を持っていましたが、最終的には自分のスキルを使って世界をより良い場所にしたいという強いモチベーションを持っていました。

オラクル・インターン・レポートのインタビューを受けたときに、Allisonと仲間の素晴らしいインターンであるNeil Collinsに会いました。
公開されているビデオは数分しかありませんが、Zoomセッションは30分以上も続いたので、
二人と様々なトピックについて話をして、それぞれのことをより深く知ることができました。
あまり知られていないデベロッパーリレーションズの役割の一つにメンターの役割があると思いますが、
私はメンターの帽子をかぶってお手伝いをすることができるといつもワクワクします。

インターンレポートのインタビューの後、私はAllisonとNeilに連絡を取り、可能な限り彼らの指導を続けたいと申し出ました。
Allisonは、UCLAのCreative Labsのチームが取り組んでいたプロジェクトを手伝うことに興味があるかどうかを尋ねました。
チームfLOWは、インラインの水使用量センサーとESP8266(マイクロコントローラとセンサー - 私の情熱の方が強い!)を使って
水の使用量を追跡するプロジェクトに取り組んでいました。彼らの言葉を借りれば、このプロジェクトの動機です。


技術と工学と持続可能性の交差点で仕事をしていきたいと考えています。
私たち一人一人が手を洗ったり、歯を磨いたりしている間に水を浪費しています。
このプロジェクトの目標は、水の使用量を測定し、時間の経過とともにユーザーの傾向を表示する装置を開発することです。
この問題に注目し、意識を向けることで、個人は積極的にライフスタイルを変えることで、水の節約に貢献することができます。


このプロジェクトとUCLAのクリエイティブ・ラボ・クラブについて理解しておく必要があるのは、このプロジェクトはすべて自発的なものであるということです。
これは大学の単位ではなく、教授によってこれらの学生に割り当てられたプロジェクトではありません。
これらの学生は、学習に情熱を持っており、この世界に違いをもたらすことができるプロジェクトで他の学生と協力しています。
若い世代のSTEM学生が、持続可能性や環境に関心を持ち、水の話や社会に影響を与える他の問題に意識を持っていくために
テクノロジーを使う方法を学ぶために実際に自分の時間を費やしているのを見るのは、非常に印象的であり、非常に感激します。
私は、Team fLOWを支援することに大きな喜びを感じていたので、彼らのプロジェクトについて話をしたり、
Oracle Cloud Infrastructure (OCI)がどのようにして彼らのアプリケーションを簡単にホストし、
永続的なデータを保存することができるかについて考えを共有したりすることに時間を割くことができました。
数週間前に電話をして、OCIがどのようにしてデータの永続性を管理するのに役立つかについていくつかのアイデアを話し合いました。
Allisonの情熱とモチベーションを共有するチームメンバーのことをもっと知ることができたし、
新しいアイデアやテクノロジーにオープンな若い世代と同時にOCIを伝道することができたのは素晴らしいことでした。

電話の後、開発者ブログの面白い記事になりそうだと思ったので、同じようなソリューションを構築できないか見てみることにしました。
この記事では、彼らのプロジェクトのために私が考えたオプションのいくつかを見てみましょう。
チームfLOWのプロジェクトサイトと、GitHubで彼らのコードとプロジェクトの概要をチェックしてみてください。

プロジェクトの目標


このプロジェクトの目的は、水の使用量を追跡し、使用量データをクラウド上のAutonomous Transaction Processingインスタンスに永続化して、さらなる分析と報告を行うことです。
私の実装に関わるハードウェアは以下の通りです。



自宅で一緒に遊びたい方は、機材を購入してフォローしていきましょう。



クラウドアカウントはありませんか?問題ありません。
このソリューション全体は、Oracle Cloud Infrastructure(OCI)の「Always Free」ティアでも問題なく機能しますので、
ハードウェアの到着を待つ間にアカウントにサインアップしてください。


想定されるソリューション1 - メッセージングキューを介したセンサーデータの公開


マイクロコントローラからのセンサーデータを扱う際には、デバイスからのセンサー読み取り値を素早く簡単に公開する方法として、
メッセージングキューを利用することがよくあります。
MQTTは非常に軽量であり、Arduinoプロジェクトで使用するための既存のクライアントライブラリがあるので、
非常に簡単に発行することができるため、最も一般的に使用されているプロトコルです。
OCIでRabbitMQを起動して実行する方法をブログで紹介しましたが、これは非常に簡単にできます。
RabbitMQ(または他のMQTTサーバ)を使えば、Node-REDのようなものを使ってセンサーデータを簡単に消費することができますが
別のトピックでブログに書いたことがあります)、その時点では無料のインスタンスを使い切ってしまい、
センサーデータをデータベースに永続化させる方法が必要になります。
これは作業するには楽しいスタックで、特定のユースケースには確かに便利ですが、このユースケースには間違った答えのように思えました。
なぜOracle Streaming Service (OSS)についてまだ触れていないのかと疑問に思うかもしれませんが、それは正しい質問です。
私はOSSについてのブログ記事ビデオを作成し、クラウドネイティブ・アプリケーションで多くの用途がありますが、
このユースケースでは、SDKに依存するか、またはOCI REST APIを使用する必要があり、
このプロジェクトを不必要に複雑にするリクエスト署名のためのかなり複雑なプロセスを持っています。別の可能性のある解決策を見てみましょう。

想定されるソリューション2 - Oracle REST Data Servicesを使用したHTTP経由でのデータ保存


マイクロコントローラを使用している場合、どのようなソリューションであっても、可能な限り軽量なソリューションを利用することが第一の考慮事項となります。
このプロジェクトのために選択されたESP8266ボードでは、パッケージサイズと必要なメモリが大きすぎるため、
SDKや直接データベースに依存するもの(Oracle Call Interfaceのようなもの)を使用することはできません。
HTTPはArduinoプラットフォーム上で利用可能なので、最適な選択肢の一つだと思いました。
これは、データベースにデータを保存するために、ある種の "プロキシ "が必要になることを意味します。
Oracle REST Data Services (ORDS)については過去に何度もお話してきましたが、これはこの目標を達成するための完璧な方法になるでしょう。
ORDSについてよく知らない場合は、ここに簡単な紹介があります。
もちろん、私たち自身の(ThatJeff Smithがブログを書いたり、記事を書いたりしています。

https://youtu.be/L25KXYvMsRg

ORDSはAutonomous DBに無料で含まれていますので、スキーマとテーブルを作成し、"auto REST "でデータを収集するためのテーブルをRESTエンドポイントとして公開し、
CRUDを完全にサポートすることができます。
あなたも、さらなる分析のためにそれを必要とする任意の形式でデータを取得するためのカスタムエンドポイントを提供することができます。
更なる分析といえば、ORDSを使用する唯一の欠点は、フロントエンドからデータベースとインターフェースするために何らかのサービス層が必要になることです。
Allisonのチームの場合、彼らはすでにNodeベースのバックエンドを持っていて、レガシーデータベースへのデータの取得と永続化に使用されていました。
確かにNodeバックエンドからORDSを利用することは可能ですが、アーキテクチャに追加のコンポーネントを導入するのは不必要だと感じたため、
このプロジェクトではORDSは選択されませんでした。


想定されるソリューション3 - バックエンドサービスを利用したHTTP経由でのデータ保存


このソリューションは簡単です。
NodeベースのバックエンドはAllisonのプロジェクトのためにすでに存在しており、
Node経由でAutonomous DBと連携するのは超簡単なので、バックエンドを設定し、センサーデータを保存するためのエンドポイントを追加するだけです。
このエンドポイントは、データを保存するための簡単で迅速な方法として、ESP8266からHTTP経由で直接呼び出すことができます。
データは単純なリレーショナルテーブルに保存されるので、後でデータを集約/フィルタリング/グループ化するためのSQLクエリを書くのは非常に簡単です。
このプロジェクトでは、Nodeベースのバックエンドを使用することが最良の選択でした。

最終的なソリューション


どのように構築されているかに入る前に、最終的なソリューションを説明し、
アーキテクチャの概要を説明し、最終的なソリューションを実際に見せてくれる2分間のビデオです。


https://youtu.be/E7D6n7hJbkY


バックエンドの構築


さて、このプロジェクトのためのソリューションを選択したので、バックエンドの設定に取り掛かりましょう。
コードに飛び込む前に、データを格納するためのスキーマとテーブルを作成しましょう。

まだAutonomous DBインスタンスを起動していない場合は、今すぐ起動してください。
その方法を知らない場合は、このブログ記事をチェックしてください(その記事のサインアップ情報を読み飛ばしてください)。

今後はAutonoous DBウォレットが必要になるので、それをダウンロードして(行き詰った場合はこのブログ記事の一番下に説明があります)、ディレクトリに入れてください。
それのコピーを解凍しますが、同様にZIPされたコピーも保管しておきましょう。
私はこのようなリソースを /projects/resources というディレクトリに置いておくのが好きですが、
後で使いやすくするために、Mac上ではファイルシステムのルートにシンボリックリンクを作っておくのも好きです。
後でどこにあるか忘れないように気をつけてくださいね。

これが初めてのAutonomous DBインスタンスであれば、スキーマを作成する必要があります。
そのためには、SQLcl (インスタンスの問い合わせによく使う便利なツール) をインストールし、Autonomous DB ウォレットを使用して、管理者ユーザーとして SQLcl で接続します。
あなたの tnsname は、ウォレット内の tnsnames.ora ファイルにリストされているエントリの 1 つです
(サービス名の異なるオプションについて説明しているこのドキュメントを参照してください)。

$ connect username@[tnsname from tnsnames.ora]
password

スキーマを作成:

CREATE USER sensor IDENTIFIED BY “A$tr0ngP@ssw3rd";
GRANT CONNECT, RESOURCE TO sensor;
GRANT UNLIMITED TABLESPACE TO sensor;

新しいスキーマユーザーとしてSQLclに接続

$ connect sensor@[tnsname from tnsnames.ora]
password

センサーデータを収集するために使用するテーブルを作成します。
テーブルにはオートナンバーID、読み込み値のfloat列、現在の日付と時刻をデフォルトにしたタイムスタンプを用意するだけです。

CREATE TABLE WATER
(
ID NUMBER(19) GENERATED BY DEFAULT ON NULL AS IDENTITY,
READING FLOAT NOT NULL,
CAPTURED_ON TIMESTAMP DEFAULT SYSDATE,
CONSTRAINT WATER_PK PRIMARY KEY (ID)
ENABLE
);

Express & Node.JSでバックエンドを作成


Nodeバックエンドには、人気のあるExpressフレームワークを使用します。
プロジェクトを作成し、依存関係をインストールします。

もし、完成したソリューションに直接ジャンプしたいのであれば、このプロジェクトの完全なコードはGitHubで見ることができます。

$ express water-tracker
$ cd water-tracker
$ npm install
$ npm install oracledb

アプリを起動するには、先ほどのコマンドの出力にあったコマンドを使います。
私のはこんな感じでした。

$ DEBUG=water-tracker:* npm start

oracledb nodeモジュールは、Oracle Instant Clientがインストールされている必要があるので、
お使いのOS用の最新の適切なバージョンのリンクを取得して、お好みのディレクトリにインストールしてください。
繰り返しになりますが、私はこのようなリソースを /projects/resources ディレクトリに保存しています。

$ cd /projects/resources
$ wget https://download.oracle.com/otn_software/mac/instantclient/193000/instantclient-basiclite-macos.x64-19.3.0.0.0dbru.zip\\\\ $ unzip instantclient-basiclite-macos.x64-19.3.0.0.0dbru.zip
$ rm instantclient-basiclite-macos.x64-19.3.0.0.0dbru.zip

oracledbモジュールの環境変数と、最終的にデータベース接続に使用する認証情報を設定する必要があります。
ここでは設定する必要がある変数の説明をします。

  • TNS_ADMIN - マシン上の解凍されたウォレットへのパス
  • ORACLEDB_USER - このプロジェクトのために作成したスキーマユーザー
  • ORACLEDB_PASSWORD - スキーマユーザーのパスワード
  • ORACLEDB_CONNECTIONSTRING - 使用する tnsnames.ora ファイルのサービス名 (上記参照)
  • INSTANT_CLIENT_PATH - マシン上の解凍されたインスタント・クライアントへのパス

お使いのオペレーティング・システムに適した方法で設定してください。私のMacでは、このようになっています。

$ export TNS_ADMIN=/wallet
$ export ORACLEDB_USER=sensor
$ export ORACLEDB_PASSWORD=A$tr0ngP@ssw3rd
$ export ORACLEDB_CONNECTIONSTRING=demodb_low
$ export INSTANT_CLIENT_PATH=/projects/resources/instantclient_19_3

Save Endpointの作成


バックエンドのために残された唯一のことは、/saveエンドポイントを追加することです。
プロジェクトでroutes/index.jsを開き、oracledbモジュールをインクルードして、インスタントクライアントへのパスを指定します。

const oracledb = require('oracledb')。
if( process.env.INSTANT_CLIENT_PATH ) {
oracledb.initOracleClient({libDir: process.env.INSTANT_CLIENT_PATH})。
}


リクエストごとに接続を開いたり閉じたりすることもできますが、それではすぐに冗長になってしまいます。
そこで、便利な関数を作ってみましょう。
通常はこれを処理するためにサービスレイヤーを作成しますが、これは単純な例なので、ルータの中に置いておきます。

const getConnection = async () => {
return await oracledb.getConnection({
user: process.env.ORACLEDB_USER,
password: process.env.ORACLEDB_PASSWORD,
connectString: process.env.ORACLEDB_CONNECTIONSTRING
});
};
const closeConnection = async (connection) => {
if (connection) {
try {
await connection.close();
}
catch (err) {
console.error(err);
}
}
};


これで、接続を取得し、挿入を実行し、接続を閉じ、新しく作成されたレコードIDを返すエンドポイントを追加することができます。
読み取り値をパスパラメータとして受け入れます。
これはデータベースに挿入するために必要な唯一の値なので(JSONを渡したりフォームデータを使用したりする代わりに)、パスパラメータとして受け入れます。
物事をすっきりさせるためにプロミスの代わりにasync/awaitを使用するので、呼び出しをtry/catch/finallyでラップする必要があります。
このコードは、NodeでOracleを使うのが初めての方のために、かなり重くコメントされています。

router.post('/save/:reading', async function (req, res, next) {
let connection;
let result;
try {
// get a connection
connection = await getConnection();
/*
use connection.execute() to run our
insert query. first argument is the SQL
statement and the values preceeded by a colon
are "bind" variables.
the second argument passed to execute()
is the bind variables. the first key in that
object is the reading value from the path
parameter and the second key is is the id
returned by the insert statement.
third arg is options - in this case we
tell the DB to auto commit our insert
so we don't have to manually commit it
*/
result = await connection.execute(
"insert into water (reading) values (:reading) return id into :id",
{
reading: req.params.reading,
id: { type: oracledb.NUMBER, dir: oracledb.BIND_OUT }
},
{ autoCommit: true }
);
}
catch (err) {
// print out any errors
console.error(err);
}
finally {
// make sure the connection is closed
await closeConnection();
// return the result as JSON
res.json(result);
}
});


バックエンドで行う必要があるのはこれだけです。ローカルでアプリを起動してテストしてみましょう。

$ curl -s -X POST http://[redacted]/save/0.0000000000 | jq
{
"outBinds": {
"id": [
283
]
},
"lastRowid": "AAApAWAAAAACAMMAAY",
"rowsAffected": 1
}


バックエンドボーナス:デプロイ


PoCとして、ローカルでバックエンドを実行することは確かに「動作します」し、LANが適切に設定されていると仮定すれば、
ローカルでバックエンドを実行し、ハードウェアをローカルIPに指定すれば、期待通りにデータを永続化します。
しかし、クラウドには2つの無料のVMがあるので、そのうちの1つにサービスをデプロイしてみてはどうでしょうか。
これを行う簡単な方法は、VMを起動してDockerをインストールし、新しいVM上でDockerイメージをビルド/プッシュ/実行することです。
もちろん、デプロイする方法は他にもありますが、少なくともVMを起動してDockerをインストールし、VM上でDockerイメージを実行する方法がわかるように、
ここではDockerのバージョンを取り上げてみましょう。

VMの作成


Oracle Cloud 上に VM を作成してみましょう。
これは、通常のVMでも、「Always Free」VMでもよいのですが、このアプリケーションではどちらでも構いません。
最初のステップは、OCI コンソールのバーガーメニューから「Compute」を選択し、「Instance」を選択することです。



次に、インスタンス一覧ページで「インスタンスの作成」をクリックします。



次に、インスタンスに名前を付け(#1)、インスタンスのコンパートメントを選択し(#2)、OSを選択します(#3)。
このプロジェクトでは、Oracle Linux 7.Xを使用しているので、以下のコマンドが動作することを確認してください。



アベイラビリティドメインとインスタンスのシェイプを選択します。
繰り返しになりますが、このアプリケーションでは、"always free"シェイプを選択しても問題ありません。



ネットワークオプションを設定し、インスタンスにパブリックIPを割り当てることを確認します。



下にスクロールして、SSH 鍵のオプションを選択します。既存の鍵を選択したり、鍵の内容を貼り付けたり、新しい鍵を生成してダウンロードしたりすることができます。

注意! SSH 鍵を追加しないと、後で VM にリモート接続できなくなります!



「Create」をクリックして、VMが完了するのを待ちます。起動したら、インスタンスの詳細ページからパブリックIPをコピーします。



このIPを使用してVMにSSH接続

$ ssh -i ~/.ssh/id_oci opc@[VM IP]

VMにDockerをインストール


マシンへの接続を確立したので、Dockerをインストールしてアプリケーションを実行できるようにしましょう。まず、yumが最新であることを確認します。

$ sudo yum update -y

次にDockerをインストールし、サービスを起動して有効化

$ sudo yum-config-manager --enable ol7_addons
$ sudo yum install docker-engine
$ sudo systemctl start docker
$ sudo systemctl enable docker

OPCユーザーとしてsudoを使わずにdockerコマンドを実行できるようにするには、以下のようにします。

$ groupadd docker
$ service docker restart
$ usermod -a -G docker opc

注意! OPCユーザーがsudoなしでDockerコマンドを実行できるようにするためには、この時点でログアウトしてログインし直す必要があります。

これでDockerがインストールされ、イメージを実行する準備が整いました。

Dockerイメージの作成


DockerがインストールされたVMを実行しているので、必要な依存関係をインストールし、
ウォレットをコンテナにコピーしてアプリケーションを実行するDockerfileを作成してみましょう。
DockerイメージのホストにはOracle Cloud Infrastructure Registryを使用する予定なので、必要に応じてOracleドキュメントのOCIRを読んでください。
必要に応じて以下のURLと認証情報を代用するだけで、別のレジストリを選択することができます。

FROM oraclelinux:7-slim
RUN yum -y install oracle-release-el7 oracle-nodejs-release-el7 &&
\ yum-config-manager --disable ol7_developer_EPEL &&
\ yum -y install oracle-instantclient19.3-basiclite nodejs &&
\ rm -rf /var/cache/yum &&
\ mkdir cert
COPY build-resource/wallet/* /usr/lib/oracle/19.3/client64/lib/network/admin/
WORKDIR /app
ADD . /app/
RUN npm install
EXPOSE 3000
ENTRYPOINT ["npm", "start"]


Dockerイメージをビルドします(自分のレポを指すようにしてください)。

$ docker build -t phx.ocir.io/toddrsharp/water-tracker/water-tracker-svc:latest .

イメージをプッシュ


$ docker push phx.ocir.io/toddrsharp/water-tracker/water-tracker-svc:latest

注意してください! セキュリティリストとVMファイアウォールでポート3000が開いていることを確認してください。

また、VMでポート3000を開く必要がありますが、OSに適したコマンドを使用してください。例えば、Oracle Linux 7の場合。

$ sudo firewall-cmd --permanent --zone=public --add-port=3000/tcp$ sudo firewall-cmd --reload

これでコンテナを実行し、必要に応じて環境変数を渡すことができます
(VMを再起動するたびにイメージが実行されるように、'--restart always'フラグに注意してください)。
ローカルで行ったように INSTANT_CLIENT_PATH 変数を渡す必要はありません。
Dockerfile で YUM を使ってインスタントクライアントをインストールすると、Oracle モジュールが期待するデフォルトのディレクトリにすべてがインストールされるようになるからです。

$ docker run -d
\ --env ORACLEDB_USER=sensor
\ --env ORACLEDB_PASSWORD=A$tr0ngP@ssw3rd
\ --env ORACLEDB_CONNECTIONSTRING=demodb_low
\ --restart always
\ --name water-tracker-svc
\ -p 3000:3000
\ phx.ocir.io/toddrsharp/water-tracker/water-tracker-svc:latest


この時点で、バックエンドはOracle CloudのVMで稼働しています。

ハードウェア


バックエンドを作成したので、このプロジェクトのハードウェア部分を見てみましょう。
最初に作成したのは、フローセンサーをインラインで接続したガーデンホースのシンプルなセクションです。



次の作業はESP8266をフローセンサーに接続することでした。配線はとても簡単です。
フローセンサーの赤線は「VIN」(5ボルト)に行き、黒はグランドに行き、黄色(信号)線はD2ピンに接続します。



こちらがNodeMCU ESP8266です。



ThingiverseでNodeMCU用のまともなケースを見つけたので、ESP8266を入れておくためにプリントアウトしてみました。



これでハードウェアが組み上がったので、センサーを読み取るためのArduinoコードを作成します。

ESP8266のコーディング


私はマイコンや他の様々なハードウェアデバイスをいじくり回すのが好きなのですが、
それらをプログラミングするとなると「エキスパート」というよりも「いじり手」になってしまうので、
Arduinoのスケッチに関してはウェブ上の豊富なチュートリアルに頼ってしまうことが多いです。
それは確かにこのプロジェクトの場合だったので、私はスケッチのほとんどのために従った本当に素晴らしいチュートリアルを見つけました。
正確なチュートリアルを使用する必要はありません。読み取り値を取得するチュートリアルのコードにバックエンドへの HTTP 呼び出しを行うコードをいくつか追加し、
彼らが使用していたものとは異なるセンサーで動作するようにチュートリアルのコードを修正しました。それを分解してみましょう。
Arduinoのスケッチには、最低限、setup()とloop()の2つのメソッドがあります。
setup()は一度だけ実行され、loop()は連続して実行されます(ショックですよね)。その前に、いくつかの依存関係をインクルードし、グローバルを宣言する必要があります。

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include "variables.h"

variables.h ファイルのインクルードに注目してください。
このファイルを作成して、WiFi 認証情報が含まれているので、ソースコントロールにチェックされていないことを確認しましょう。
このファイルの例は GitHub レポの同じディレクトリにある variables_template.h ファイルを見てください。
このファイルには、次のような変数が含まれているはずです。

password = “[YOUR WIFI PASSWORD]";
String saveEndpoint = "http://[YOUR VM IP]/save/";

さて、setup()ではWiFiに接続してセンサーの設定をします。
ここで何が起こっているのかはコメントで説明する必要があります。

void setup() { Serial.begin(9600);

フローファンクションは、信号を受信するたびにカウンタをインクリメントするだけです。

ICACHE_RAM_ATTR void Flow()
{
pulses++; //Every time this function is called, increment "pulses" by 1
}

バックエンドへのHTTP呼び出しを行うファンクションを追加してみましょう。

void saveReading(float reading)
{
if( WiFi.status() == WL_CONNECTED )
{
String address = saveEndpoint + String(reading, DEC);
http.begin(address);
Serial.print("Address: ");
Serial.println(address);
int httpCode = http.POST("");
Serial.print("Status Code: ");
Serial.println(httpCode);
String payload = http.getString();
Serial.println(payload);
http.end();
Serial.println("closing connection");
}
}


loop()関数では、最後のチェックから何パルスカウントされたかをチェックし、その値をミリリットルに変換して、読み取り値を保存します。
このコードを最初に試したときは、毎秒ごとにバックエンドを呼び出していましたが、
何が起こっているのかに気づく前に、持続する量が思っていたよりもずっと少なかったので、このテストは少々混乱してしまいました。
HTTP 呼び出しはブロックされているので、かなり高速であったにもかかわらず、
センサーからのパルスをカウントしている割り込み関数を中断させるのに十分な頻度で発生していました。
周波数を5秒に上げたところ、より正確な結果が得られましたが、HTTPコールがまだブロックされているので、まだ完全ではありません(頻度が低いだけです)。
この問題を回避するには、他に2つの方法が考えられます。
1つ目の方法は、パルスが長時間インクリメントされないのを待ってから、HTTPリクエストを実行することです。
しかし、Xミリ秒の間何も読み取れなかったからといって、HTTPリクエストをしている間に何も来ないというわけではありません。
また、リクエストを実行するために、Xミリ秒の間、システムが稼働していない期間が延長されることがないという保証はありません。
この回避策は完璧ではありません。実際、この状況に対処する唯一の適切な方法は、
バックエンドに測定値を永続化するためにノンブロッキング(非同期)HTTP呼び出しを行うライブラリを利用することです。
私の場合、精度は100%必要ではないので、5秒ごとにブロッキングしても問題ありません。
これはloop()関数で、先ほどと同様、かなり重くコメントされています。

void loop()
{
/*
* running too frequently was leading to
* inaccurate readings
* because http request
* is blocking and slowing down the interrupts
* we're still going to get some inaccuracy
* here because the http request still blocks,
* but it will have less impact on the overall
* reading. we could wait until no readings (pulses)
* are read for X seconds before saving, but
* that doesn't guarantee no readings will start
* while it's saving and there is no guarantee
* that there will ever be an extended period of
* X seconds with no readings in order to save.
* the only perfect solution would be to use an async http
* lib such as https://github.com/boblemaire/asyncHTTPrequest\\\\ */
if( millis() - lastMillis >= 5000 )
{
// detach the interrupt while we save, or bad things can happen
detachInterrupt(digitalPinToInterrupt(D2));
lastMillis = millis();


私のメーターは、1リットルあたりのパルスの適切な量を決定するために製品の説明書を参照しなければならなかったことを意味するチュートリアルのものとはわずかに異なっていました。
明らかに、これはあなたの測定値に大きな影響を与えるので、適切な値を使用していることを確認してください。



スケッチをアップロードする準備ができました。
内蔵のLEDが点灯すると、ホースを流れる水が測定され、バックエンドを介してAutonomous DBインスタンスに測定値が保持されます。

アプリの実行


バックエンドがデプロイされ、スケッチがESP8266にデプロイされたので、プロジェクトをテストする準備ができました。
アプリをテストするには、ホースをスピゴットに接続して水を流すだけです。
この投稿のために、データベーステーブルをクリアして、システムを通して簡単なセッションを実行しました。
約2ガロンの水を5ガロンのバケツに流した後、システムを切り離してオフィスに戻り、テーブルに対していくつかのクエリを実行しました。
最初のクエリは、それぞれ約5秒ずつの間隔をおいて測定値のグループを持っていることを確認することでした。

select *from waterorder by captured_on;

次のようなレコードが生成された。



かっこいい。まさに私が予想していた通りで、ホースが「フルストレングス」で運転されているときは、5秒ごとに約1100mLを得ているように見えます。
次に、データを集計して、セッション内での総mLの使用量にして、その値をガロンに変換したいと思いました。

select sum(reading) as mL, sum(reading) * 0.0002641721 as gallonsfrom water;

結果



ちょうど約2ガロン-かなり私が期待していたものです。

TL;DR

この(通常よりも少し長い)ブログ記事では、このプロジェクトのインスピレーションについて話し、
その後、流量センサーを使用して水の使用量を追跡するシンプルなシステムを構築しました。
Oracle CloudのVM上でホストされたNodeバックエンドを介してAutonomous DBインスタンスにデータを永続化するNodeMCU ESP8266ボード。

このプロジェクトを修正して、クラウド上にセンサデータを永続化してみてください。
また、データをチャート化して検索機能を追加するフロントエンドを構築することもできます。可能性は無限大です。

このプロジェクトのフルコードはGitHubにあります。
Photo by Harry Grout on Unsplash

コメント

このブログの人気の投稿

Oracle Database 19cサポート・タイムラインの重要な更新 (2024/11/20)

Oracle APEXのInteractive Gridで、Oracle Formsと比較して、重複行の検証を制御/通過させる方法 (2022/07/21)

Oracle APEX 24.1の一般提供の発表 (2024/06/17)