本当にスマートボックス
コンポーネントと消耗品
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 1 |
必要なツールとマシン
> |
| |||
|
アプリとオンラインサービス
| ||||
|
このプロジェクトについて
Really Smart Boxプラットフォームは、Really Useful Box(tm)を、在庫監視用のインテリジェントなインターネット接続ストレージボックスに変えます。 Sigfox Arduino MKR FOX 1200に基づいて、ボックスに保管されているものの重量と温度および湿度を検知し、低電力のSigfoxラジオを使用してこの情報を中継します。
ユースケース-3Dプリンターフィラメントストレージ:
3Dプリンターを所有している場合は、フィラメントの保管方法を気にする可能性があります。これは、プリンターフィラメントだけでなく、許容可能な温度と湿度の範囲内で保管する必要がある他の多くのものにも当てはまります(たとえば、塗装業者のコーキングは露出すると使用できなくなる可能性があります)氷点下まで)
地元のメーカースペースで3Dプリンターを保守する責任者の一人として、フィラメントの在庫が十分にあり、乾いた状態に保たれていることを確認する必要があります。
Really Smart Boxを使用すると、フィラメントの重量を監視できるため、シリカゲルの交換が必要かどうかを判断するためにボックス内の湿度レベルを監視するとともに、フィラメントの重量が低下しているかどうかを確認できます。
<図> <図>
ユースケース-消耗品在庫管理:
清掃業者は、クライアントサイトに芳香剤、ハンドソープ、またはその他の消耗品の在庫を保持したい場合があります。クライアントは、請負業者にWiFiアクセスを許可したり、存在しない場合はこのようなデバイスに電力を供給したりすることを許可しない可能性がありますが、請負業者は新しい在庫をいつ送るかを知って、掃除人の時間と誰も好まない余分な事務処理にオーバーヘッドを追加します。
Really Smart Boxプラットフォームは、Sigfoxを使用しているため、クライアントネットワークに接続する必要がなく、低電力であるため、一連のバッテリーで動作するため、単にストレージボックスにドロップします。ボックスの内容が変更されることはめったにないため、Arduinoはほとんどの時間、低電力状態に保つことができ、バッテリーの寿命を延ばすのに役立ちます。
プラットフォームには、ボックスに保管されているアイテムタイプ(つまり、芳香剤)の重量を伝えることができるため、ボックスに何個入っているかを計算できます。その後、これを清掃業者の会社に送信して、顧客のサイトにさらにストークを届ける必要がある場合に警告することができます。
<図> <図>
プラットフォームの作成:
プラットフォームはシンプルな構造で、本体は2枚のレーザーカットアクリル(3mmを使用しましたが、プリンターフィラメントなどの重いものには厚い方が良いです)でできており、間にロードセルが2つあります。
<図>ロードセルのネジ穴を手動で皿穴加工して、より良い仕上がりにしました。皿穴加工を行うレーザーカッターはまだ見つかりません!
アクリルはお好みの箱に合わせて任意のサイズにカットできますが、ロードセルの位置とワイヤーの長さはかなり短いので注意してください。上部のアクリルシートは下部よりもわずかに小さく、ボックスの側面に引っ掛からないようになっています。
下のシートには切り欠きがあり、スペーサーなしで電子機器をアクリルに取り付け、はんだ付けされたデバイスの脚を突き刺すことができます。カットアウトのコーナーセクションでM3タップを使用して、PCBを直接ボルトで固定しました。ロードセルのネジがアクリルと同一平面上にないことを確認するために、3Dプリントされた脚もコーナーに取り付けられました。
<図> <図>重量は、2つの5Kgロードセルを使用して検出されます。体重計では通常4個セットが使用されており、これが良いかもしれませんが、電子機器に必要な間隔を提供するだけでなく、アクリルに固定するための良い方法を見つけることができませんでした。
ロードセルは、わずかな屈曲を可能にするために上下にいくらかのパディングが必要であり、ロードセルの実際のひずみゲージセンサー(写真の白いビット)は、取り付けブロックよりも太いです。これは、ロードセルを持ち上げるための小さなブロックを備えた2つの「ReallySmartBox」3Dプリントエンドプレートを備えたロードセルの下で実現されます。ロードセルの上部には、レーザーカットされたアクリルパッドブロックがあります。
<図>ロードセルはHX711ロードセルアンプに接続されています。これには、この用途に最適な2つのチャネル(AとB)を選択できます。
<図>各ロードセルはホイートストンブリッジ構成のひずみゲージで構成されており、これにより分圧器の不均衡なペアが作成されます。ロードセルに負荷がかかると、ひずみゲージの抵抗が変化するため、2つの分圧器の間に差が生じます。 、これは、アナログからデジタルへの変換を実行するHX711によって増幅および測定されます。
このプロジェクトでは2つの5kgロードセルを使用しました。まったく同じように機能しますが、感度が異なるさまざまな定格(1kgと10kgなど)を取得できます。
ロードセルを配置するときは、セルの端にある矢印が下向き(ロードの方向)になっていることを確認してください。セルには、一方の端(通常は固定端)にM5のタップ穴があり、もう一方の端(負荷をかける側)にはM4の穴があります。
赤/黒のワイヤーは電源であり、これは分圧器の上部と下部に給電し、2つのロードセル間で共有されます。緑と白は分圧器の中央からのセンスワイヤであり、これらはHX711のチャネルAとBに接続されています。
HX711は3つのゲイン係数をサポートしていますが、これらはチャネル選択にも使用されます。 128と64のゲインはAチャネルで使用できますが、32のゲインを選択するとBチャネルが選択されます。これは、2番目のチャネルがメインチャネルほど感度が高くないことを意味します。これは、このアプリケーションには問題ありません。
HX711はArduinoの任意のデジタルピンに接続できます。私はD0(データ)とD1(クロック)を使用しました。アンプはArduinoの3v3電源に接続する必要があります。
<図> <図>ロードセルとHX711の詳細については、SparkFunsの優れたロードセルチュートリアルをご覧ください。
最後に、BME280がI2Cバスに接続され、ボックス内の温度と湿度を検出するために使用されます。これは圧力を検出するためにも使用できますが、これはおそらくほとんど関心がなく、再生できるsigfoxデータは12バイトしかないためです。報告されていません。
電子機器はThingySticksArduinoプロトタイプボードに取り付けられ、バッテリーホルダー(下部のアクリルシートにホットメルト接着剤で接着)を追加し、プラットフォームに最適なフラットなデザインのアンテナを接続しました。
<図>
ロードセルキャリブレーション:
プラットフォームを使用する前に、ロードセルを調整する必要があります。各ロードセルは独自のものであり、ひずみゲージを金属のブロックに取り付け、そこにドリルで穴を開けて、スナップせずに感知できる十分な屈曲を提供します。このため、重量に対する各ロードセルの応答を調整する必要があります。 。
キャリブレーションが完了したら、y =mx + cの式を測定されたADC値(x)に適用して、実際の重量(y)を取得します。したがって、ロードセルのc(オフセット)とm(勾配)を見つける必要があります。
メインプラットフォームの上部を取り外し、各ロードセルに小さなアクリルの正方形を順番に取り付け、測定値を監視しました(これを行うためのファームウェアのルーティングは、シリアルポートに「c」を送信することで開始できます。
最初に空のプラットフォームの読み取り値が測定され、これによりオフセット(c)値が得られ、次にセルに既知の重量の負荷がかかると、読み取り値の差から傾きが得られます。
勾配=(測定-オフセット)/重量(g)。
小さな芳香剤缶(約230g)とプリンターフィラメントのスプール(約1.5kg)の両方を使用して値を確認しましたが、どちらもほぼ同じ勾配で安心感がありました。
<図> <図>当然、小さなアクリルパッドで測定されたオフセットは、フルトップシートで測定されたオフセットとは異なります。同様に、両方のロードセルを使用した場合の傾きの差も小さいため、2次キャリブレーションが必要です。今のところ、1ポイントのゼロオフセット(風袋引き)が使用されています。これはファームウェアで設定されますが、USBシリアル接続を使用するか、展開後にSigfoxダウンリンクメッセージを介して設定することもできます。
Sigfox接続:
Really Smart Boxの配線を使用して、最初はUSBシリアルポートを使用して出力を監視し、システムのデバッグと調整に役立てました。このようにして、個々のロードセル、変化、およびノイズを確認できます。ただし、完全にワイヤレスである必要があるため、これは展開されたボックスでは機能しません。
Sigfoxを使用すると、1日に最大140回12バイトのデータをオンラインサービスに送信できます。これは、Really SmartBoxには十分すぎるほどです。以下のデータ構造はArduinoで使用されており、12バイトの使用方法を説明しています。
typedef struct __attribute __((packed))sigfox_message {uint8_t status; //ステータスフラグint8_t湿度; //湿度::int:8-一部のセンサー(HTU21D)は-ve湿度を読み取ります)int8_t温度; // temperature ::int:8(小数点以下の桁数なし)。 int16_t zeroWeight; // zeroWeight ::int:16:little-endian int16_t weight; // weight ::int:16:little-endian int16_t itemCount; // itemCount ::int:16:little-endian(2.01を可能にする100xの実際のアイテム数(重みが正確に一致しないため)int8_t dragCorrection; //スケールに適用されるゼロの重みの変化に対するドリフト補正。int8_tフィラー; //ここには何も表示されません。移動してください... int8_t lastStatus; //最後のsigfoxステータス} SigfoxMessage;
最初のバイト(ステータス)は、問題を示すためにビットフラグに分割されます:
// status ::uint:8-> 8ビットに分割// B7-初回実行// B6-HX711障害// B5-BME280障害// B4-温度アラーム// B3-湿度アラーム// B2-重量アラーム// B1-在庫不足// B0-スペア
この構造は12バイトに圧縮されますが、Tinamousにプッシュするには、Sigfoxの最後で解凍する必要があります。そのためにカスタムペイロード構成を使用します。データ構造が定義されているので、これを解決するのが最善です。私たちのものは:
firstRun ::bool:7 hx711Fault ::bool:6 bmeFault ::bool:5temperatureAlarm ::bool:4himageAlarm ::bool:3 weightAlarm ::bool:2 lowStock ::bool:1 b0: :bool:0 status ::int:8負債::int:8温度::int:8zeroWeight ::int:16:little-endian weight ::int:16:little-endian itemCount ::int:16:little -endian
カスタムペイロードは、解析時に12バイトを分割します。
Sigfoxのデフォルトはビッグエンディアンであり、Arduinoはリトルエンディアンを使用するため(つまり、最下位バイトがマルチバイトワードの最初にあるため)、1バイトより大きいもののリトルエンディアンの性質を指定する必要があることに注意してください。
また、最初のバイトでブールフラグを分割しても、他のすべての読み取りの場合とは異なり、バイトマーカーが進行しないため、すべてのフラグを保持するステータスバイトも読み取られ、最初のバイトをスキップすることに注意してください。
フラグには、温度、湿度、および重量範囲のアラームフラグが含まれています。オンラインサービス(つまり、Tinamous)を使用して、範囲外の温度、湿度、および重量を監視できますが、これらは短命(数時間)であり、ボックス送信頻度が低い場合(1日1回または2回)、結果として生じる可能性のある有害な環境条件を簡単に見逃す可能性があるため、デバイスにフラグが付けられて送信されます(送信が成功するとリセットされます)。
アイテム数は、実際には実際のアイテム数の100倍に設定されています。四捨五入を強制せずに2.2項目(重量エラーまたはボックス内の他の項目による)などの値を許可したかったのですが、注意しないと2.95が2に切り捨てられる可能性があり、3項目をより示唆します。ボックスと小さなエラー。また、より多くのスペースを必要とするフロートを使用したくなかったので、16ビットワードを使用し、変換を容易にするための係数を適用しました(ゼロエラーを許可するように署名されているため、在庫レベルになる可能性があります-1または-2など)。
Sigfox通信を有効にするために行う必要のあることはほとんどありません。 Arduino内にSigfoxライブラリが追加され、SigfoxライブラリのArduinoの例に従ってデータをプッシュするために適切な関数が呼び出されますが、デバイスをSigfoxに登録する必要があります。
Really Smart Boxのシリアルポートに「s」を送信すると、Sigfox IDとPACコードが出力されます。これらは、Sigfoxバックエンドでデバイスをアクティブ化するために使用されます。次に、Sigfoxバックエンドアクティベーションサービスに移動し、ウィザードに従って、最初にデバイスを選択し、次に国/プロバイダー、次にいくつかの詳細を選択します。
<図> <図> <図> <図>そして最後に、デバイスがアクティブ化されて一覧表示されます:
<図>Sigfoxは、デバイスをデバイスタイプのグループ化に割り当てます。これは、通常、グループとして機能させたい同じデバイスタイプが多数(数百、数千など)ある場合があるため、適切です。デバイスタイプを定義すると、受信したデータをオンラインサービスにプッシュするカスタムコールバックを構成できます。私はこれにTinamousを使用しています(ヒント:プロファイル名を参照してください-選択に偏りがある可能性があります)。
本当にスマートなボックスの使用:
配線し、ボルトで固定し、ファームウェアをフラッシュし、プラットフォームに取り付けられたバッテリーを、Really Useful Box(tm)にドロップするだけで、すぐに使用できます。
デバイスの電源がオンになると、2分後に最初のSigfoxメッセージが送信され、ダウンリンクデータが要求されるため、できるだけ遅く電源を投入する必要があります。このデータには、プラットフォームの重みをゼロにする「ゼロ」コマンドを含めることができます。これに失敗すると、USBシリアル接続が必要になるか、次のダウンリンク要求を待機します。これらは12時間ごとに行われます。
プラットフォームが起動して実行されると、15分ごとにSigfoxメッセージが公開され、重量、アイテム数、温度、湿度、およびアラーム状態が送信されます。温度、湿度、および重量は毎分測定され、これらが範囲外にないことを確認します。アラームが発生した場合は、次の送信のためにアラームがフラグ付けされます。
Tinamousによる監視:
Tinamousは、アカウントに「Sigfox Bot」を追加することで、Sigfoxカスタムコールバックをサポートしています。その方法については、「Get YourSigfoxOn」Hackster.ioチュートリアルを参照してください。
Sigfox BotをTinamousアカウントに追加するときに、API設定を含めると、Sigfox Botはデバイスを検索してTinamousアカウントに追加しますが、データが追加されるとデバイスが自動的に追加されるため、これを行う必要はありません。公開されています。
ボットを追加すると、Sigfoxコールバックの設定に役立つコールバック構成画面が表示されます。
<図>次に、Sigfoxでカスタムコールバックを作成できます。ReallySmartBoxは、通常のUPLINKコールバックとBIDIR(アップおよびダウンリンク)コールバックを処理するDATA-> BIDIRコールバックを使用することに注意してください。
<図>これは、以前のカスタムペイロードが役立つ場所です。これをソースコードからカスタムペイロードに貼り付け、これを反映するようにフィールドセクションを更新します。
LatとLngはこのコールバックで指定され、おおよその位置を示します。ただし、ArduinoのSigfoxは改善された位置設定をサポートしていますが、これには2番目のコールバックが必要です。ジオロケーション機能を使用する場合は、このメッセージで緯度/経度を指定しないでください。ReallySmartBoxがロケーション間を移動しているように見えます。
これを構成したら、ダウンリンクも有効にする必要があります。BIDIRが設定されていても、これはデフォルトで無効になっています。
ダウンリンクオプションの下のスクリーンショットは「チェック」されていることに注意してください。これは手動で行う必要があり、ダウンリンクデータのデバイスタイプが「コールバック」に設定されていない場合は使用できない場合があります(デバイスタイプ->編集->ダウンリンクデータ) 。
<図> <図>ダウンリンクコールバックでは、SERVICE-> ACKNOWLEDGEコールバックを指定して、デバイスがダウンリンクデータを受信したことを確認する必要もあります。 Tinamousの他のコールバック構成でSigfoxBotをクリックすると、ACKNOWLEDGEおよびGEOLOCコールバックの指示に従います。
これはTinamousで一方向に暗号化されたパスワードであり、表示できなくなったため、最初のアップリンク/ビディールコールバックから認証ヘッダーをコピーする必要があることに注意してください。
<図> <図>コールバックが設定されると、デバイスによって公開されたデータがTinamousに送信されるはずです。また、Sigfoxに電子メールのコールバックを追加することもできます。これは、データが通過していることを確認するのに役立ちます(ただし、非常にすぐにノイズが発生する可能性もあります)。
Tinamousデバイスの構成:
SigfoxデバイスがTinamousで(APIルックアップまたはコールバックのいずれかを介して)表示されると、[デバイス]ページに表示されます。ここから、プロパティを編集できます。フィールドはSigfoxコールバックから取得されるときに自動的に追加されるため、デバイスがデータを公開するまで待つのが最善です。
「NotReportingAfter」時間を1時間(現在は15分ごとに公開)に設定したので、デバイスが壊れているかどうかを確認し、オプションでこれについて通知を受け取ることができます。
<図>チャート/詳細ページにデバイスから送信されたすべてのフィールドを表示したくなかったので(すべてのフラグを含めると多くのフィールドが表示されます)、Tinamousは重量とアイテム数のみを表示するように構成されています。人間に優しいラベルとユニットもここに適用されました。
<図>[アイテム数]フィールドは実際のアイテム数の100倍であるため、そのフィールドにキャリブレーションを適用して100分の1に減らします。
<図>いくつかのダウンリンクデータが設定されているため、Really Smart Boxはゼロになり、次にダウンリンクメッセージを要求したときに温度と湿度の範囲の制限が適用されます(電源投入後2分、その後12時間に1回)。
<図>
本当にスマートなボックス情報の表示:
これで、デバイスフィールドが構成され、デバイスの詳細ページで監視できます(現時点ではプラットフォームをゼロに設定していないため、1/2ユニットが存在すると見なされます-アクリルトップも5mmに交換しましたより重いバージョンですが、プリンターフィラメントをより適切に処理します。
<図>SigfoxセクションからSigfoxコールバックの相互作用も確認できます。ここで、ダウンリンクデータが送信され、確認されているが、Arduinoがエラーを報告していることに注意してください。これについては最後に詳しく説明します。
<図>[場所]タブでは、Really Smart Boxがどこにあるかも確認できます。これは、どの顧客にいるかを忘れた場合や、バンに乗っている場合に役立ちます。
<図>そして当然、Really Smart Boxの素晴らしいダッシュボードビューが必要です。下の図は、ボックスの内容物の重量、その中の推定ユニット、およびレポートされていないデバイスの数を示しているため、1つが壊れているかどうかを確認できます。
> <図>
Tinamousで通知を受信する:
次に、アイテム数が少ないときにメールとSMSを送信するようにTinamousを設定しました。これを行うには、アイテム数フィールドに3〜300の作業範囲を指定します。値がこの範囲外の場合、範囲外の測定値が発生します。
<図>Tinamousに通知を追加すると、そのときに通知を受け取ることができます。
<図>関心のあるフィールドだけを指定することもできますが、これを空のままにすると、範囲外のフィールドについて通知が届きます。
<図>同様に、デバイスの場合は、すべてのデバイスで空白のままにします(つまり、現在使用しているデバイスは1つだけです)
<図>リセットされるまで(毎日)、繰り返し通知を1回だけトリガーするように設定します。そうしないと、15分ごとの通知がすぐに煩わしくなります。
<図>次に、通知方法を選択し、メールとSMSに設定して、通知を作成します。
<図> <図> <図>
結論:
これで、Really Smart Boxをデプロイして、(うまくいけば)それを忘れることができます。在庫レベルが低くなると通知が届き、ダッシュボードで状況を確認できます。 Sigfoxを使用すると、バッテリーを時々交換する以外にデバイスへの電力供給について心配する必要がなく、現場でWiFiをセットアップする必要がないため、導入が非常に簡単になります。
このユニットをCambridgeMakespaceのフィラメントストレージボックスの1つに配置して、フィラメントの在庫レベルを監視することを計画しています。
<図> <図>
解決すべき問題:
言うまでもなく、これは本番品質の完成したプロジェクトではありませんが、いくつかの問題を解決する必要があります。
Sigfoxダウンリンク:
Sigfoxでは、アップリンクメッセージに応答して1日に4つのメッセージを送信できます。 Really Smart Boxはこれらを使用して、スケールの再ゼロ化、温度と湿度の上限と下限の範囲、およびアイテムの重量の設定を可能にします。ただし、これを機能させようとしている間、ダウンリンクメッセージが送信され、確認されているように見えても(Sigfoxバックエンドに表示されているように)、Arduinoは62のステータスエラーを報告しています。これはエラーフラグにマップされません。 ATA8520チップにリストされている条件で、ドライバーを掘り下げると、コマンドはデータシートにもリストされていないダウンリンクの要求を使用するため、さらに調査を行う必要があります。
デバッグのみ:
デバッグを無効にしてSigfox通信を実行すると、Arduinoの低電力設定がアクティブになり、USBシリアルポートが強制終了されます。
低電力モード:
Sigfoxのデバッグ設定で説明したように、Arduinoの低電力ライブラリを使用するとUSBシリアルがドロップオフするため、現時点ではこれは有効になっていません。
ドリフト:
ドリフトの補正は追加されていません。一定の負荷がかかったときにロードセルがドリフトすることは間違いありません。
ノイズ/非垂直測定:
Really Smart Boxがバンの後ろにある可能性があります(例:モバイルクリーナー、大工、配管工など)。プラットフォームに加速度計を追加し、ボックスが安定していない場合は測定サイクルをスキップすることをお勧めします。同様に、ボックスが垂直でない場合、重量は期待どおりにロードセルを通過しません。
コード
- 本当にスマートボックスArduinoコード
本当にスマートボックスArduinoコード Arduino
Arduino MKR FOX 1200、HX711、AdaFruit BME280、Arduino低電力用のライブラリを追加します。 ArduinoIDEを使用して通常どおりにプログラムします。//本当にスマートボックス//本当にスマートボックスの内容物の重量を測定します// 2枚のアクリルでできており、間に2つのロードセルがあります//実際に配置されていますスマートボックス。//ボックス内の温度と圧力を測定するためのBME280も含まれています。//作成者:Stephen Harrison //ライセンス:MIT#include#include #include #include #include // ---------------------------------- ---- // I2CポートのBME280.Adafruit_BME280bme; // -------------------------------------- // HX711ロードセルアンプ// 0 :D0-DOUT // 1:D1-CLK //初期ゲイン128.HX711scales(0、1、128); //ロードセル用アレイ。インデックス0 ==チャネルA、インデックス1 ==チャネルB.floatゲイン[] ={128,32}; //キャリブレーション係数// y =mx + c(c =オフセット、m =scaleFactor)を使用します。/ /測定値を重みに変換します。//これをロードセルによって報告されたオフセットに設定します。//重みがない場合。floatoffset[] ={0,54940}; //これをスケールに重みを置いたときに計算された係数に設定します。//最初にオフセットを設定し、これを有効にするためにarduionoを再フラッシュします//スケールに重みを置き、生の測定値をweight .// scaleFactor =測定値/weight.float scaleFactor [] ={378.f、260.9f}; // ----------------------を使用---------------- // Sigfox //これはSigfoxに公開するデータ構造です。//最初のステータスバイトからブールフラグとしてビットを分割しますが、バイトにはまだ必要です。 //含まれる場合、それ以外の場合、湿度はステータスになります// firstRun ::bool:7 hx711Fault ::bool:6 bmeFault ::bool:5temperatureAlarm ::bool:4himageAlarm ::bool:3 weightAlarm ::bool:2 lowStock: :bool:1 b0::bool:0// status::int:8 humidity::int:8 temperature::int:8 zeroWeight::int:16:little-endian weight::int:16:little-endian itemCount::int:16:little-endiantypedef struct __attribute__ ((packed)) sigfox_message { uint8_t status; // status::uint:8 -> Split to 8 bits // B7 - First run, B6 - HX711 fault, B5 BME280 fault, B4 Temperature alarm, B3 - Humidity alarm, B2 - weight alarm, B1 - Low stock, B0 - spare int8_t humidity; // humidity::int:8 (yes some sensors (HTU21D read -ve humidity) int8_t temperature; // temperature::int:8 (no decimal places). int16_t zeroWeight; // zeroWeight::int:16:little-endian int16_t weight; // weight::int:16:little-endian int16_t itemCount; // itemCount::int:16:little-endian (100x actual item count to allow for 2.01 (as weight won't match exactly) int8_t driftCorrection; // Drift Correction for changes in zero weight applied to the scales. int8_t filler; int8_t lastStatus; // Last sigfox status} SigfoxMessage;// Time the last Sigfox message was published atlong lastPublish =0;// Time the last Sigfox downlink was requested.// Allowed max 4 per day of these.long lastDownlink =0;uint8_t lastSigfoxStatus =0;// --------------------------------------// Application/state// If the fist cycle (after a reset) for the measure/publish// cycle (this is used to request a downlink message from Sigfox).// Note that only 4 of them are allowed per day so becareful/ / when deploying code.bool isFirstCycle =true;// Application mode// 0:Normal// 1:Calibrationint mode =0;// Which channel should be read during calibration.int calibrate_channel =0;// The last average value measured for each channel.float lastAverage[] ={0,0};// The current weight of the contents of the boxfloat currentWeight =0;// The weight of the units the box will hold.// Updatable via Sigfox downlink message.float unitWeight =238;// Different to tare as it would be a manual// zero'd at a set reading from scales// This will most likely change with drift (time/temperature/etc)// and should be set once the scale is in place but not loaded.// Updatable via Sigfox downlink message.float zeroWeight =0;bool bmeOk =true;bool hx711Ok =true;// Alarms and alarm rangesfloat minTemperature =5.f;float maxTemperature =60.f;float minHumidity =0.f;float maxHumidity =60.f;float maxWeight =10000; // 10kgbool temperatureAlarm =false;bool humidityAlarm =false;bool weightAlarm =false;float currentTemperature =0;float currentHumidity =0;float stockLevel =0;bool lowStock =false;float minStock =5;// Setup the Arduino.void setup() { pinMode(LED_BUILTIN, OUTPUT); //Initialize serial:Serial.begin(9600); // NB:The sensor I'm using (from random eBay seller) // does not use the default address. bmeOk =bme.begin(0x76); if (!bmeOk) { Serial.println("Could not find a valid BME280 sensor!"); } // Delay for USB Serial connect and for the BME's first reading. delay(5000); Serial.println("Really Smart Box..."); printHeader();}int delayCounter =0;void loop() { switch (mode) { case 0:measureAndPublish(); //Sleep for 1 minutes // Causing problems with USB connected. //LowPower.sleep(1 * 60 * 1000); delay(60 * 1000);壊す; case 1:calibrate(); delay(1000);壊す; } // Check for user input via the serial port. checkSerial(); // measure is done on RTC timer tick (once per minute) delay(100);}void measureAndPublish() { // turn the LED on to indicate measuring. digitalWrite(LED_BUILTIN, HIGH); printBmeValues(); measureTemperatureAndHumidity(); measureWeight(true); // Weight, temperature and humidity are read every minute // however we only publish occasionally. if (shouldPublish()) { publishMeasurements(); } digitalWrite(LED_BUILTIN, LOW); }// Main measurement loop. Reads the weight from the load cells// and stores if no noise from the previous read.void measureWeight(bool printDetails) { scales.power_up(); delay(500); float delta =readDelta(printDetails); if (printDetails) { Serial.print("\t"); Serial.print(delta, 2); } // If the delta is between -1 and 1 (i.e. no noise) // update the change in overall weight and units contained // otherwise ignore and try again later on. // This ensures we use only stable readings when both channels have not changed for // two sets of measurements. if (delta <1.f &&delta> -1.f) { // Remember the previous measured weight so we can get a delta. float lastWeight =currentWeight; // Compute the weight. Take the weight of both load cells // added together then subtract the zero'd weight. currentWeight =lastAverage[0] + lastAverage[1] - zeroWeight; // Compute the difference in weight of the items in the box // compated to the last time we had a stable reading. float itemsWeightDelta =currentWeight - lastWeight; updateStockLevels(); if (printDetails) { Serial.print("\t"); Serial.print("\t"); Serial.print(currentWeight, 2); Serial.print("\t"); // divide by unit weight to estimate the stock level in the box Serial.print(currentWeight / unitWeight, 2); Serial.print("\t"); // the change in weight, (i.e. the weight if the items added) Serial.print(itemsWeightDelta, 2); Serial.print("\t"); // divide by unit weight to estimate the units removed/added Serial.print(itemsWeightDelta / unitWeight, 2); } } checkWeightLimits(); if (printDetails) { Serial.println(); } // put the ADC in sleep mode and switch // off the LED now we're done measuring. scales.power_down(); }void updateStockLevels() { stockLevel =currentWeight / unitWeight; // Unlike other alarms the low stock level // is reset if the stock is re-stocked. lowStock =stockLevel maxWeight ) { weightAlarm =true; } if (lastAverage[0]> (maxWeight /2)) { weightAlarm =true; } if (lastAverage[1]> (maxWeight /2)) { weightAlarm =true; }}// Read the difference in weight from the last // average to this time across both load cells.// average value is stored in the lastAverage array.float readDelta(bool printDetails) { float aDelta =readChannel(0, true); if (printDetails) { Serial.print("\t"); } float bDelta =readChannel(1, true); return aDelta + bDelta;}// Read the weight from a channel. Stores the measured value in // the lastAverage array and retuns the delta of the measured value// from the previous lastAverage. This allows us to know if the weight// has changed.// channel 0 =A// channel 1 =Bfloat readChannel(int channel, bool printDetails) { // Gain:// Channel A supports 128 or 64. Default 128 // Channel B supports 32 // Select Channel B by using gain of 32. scales.set_gain(gain[channel]); // HX711 library only has one set of offset/scale factors // which won't work for use as we use both channels and they // have different gains, so each needs to have it's offset/scale set // before reading the value. scales.set_offset(offset[channel]); scales.set_scale(scaleFactor[channel]); // force read to switch to gain. scales.read(); scales.read(); float singleRead =scales.get_units(); float average =scales.get_units(10); float delta =average - lastAverage[channel]; if (printDetails) { Serial.print(singleRead, 1); Serial.print("\t"); Serial.print(average, 1); Serial.print("\t"); Serial.print(delta, 1); Serial.print("\t"); } lastAverage[channel] =average; return delta;}// print the header for the debug data pushed out when measuring.void printHeader() { Serial.print("BME280\t\t\t\t\t"); Serial.print("Channel A\t\t\t"); Serial.print("Channel B\t\t\t"); Serial.print("\t\t"); Serial.print("Totals \t\t\t"); Serial.println( ""); Serial.print("Temp\t"); Serial.print("Pressure\t"); Serial.print("Humidity\t"); Serial.print("read\t"); Serial.print("average\t"); Serial.print("delta\t"); Serial.print("\t"); Serial.print("read\t"); Serial.print("average\t"); Serial.print("delta\t"); Serial.print("\t"); Serial.print("sum\t"); Serial.print("\t"); Serial.print("weight\t"); Serial.print("items\t"); Serial.print("change\t"); Serial.print("items added"); Serial.println("");}// Calibration - reads/prints selected channel values.void calibrate() { digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level) scales.set_gain(gain[calibrate_channel]); scales.set_offset(offset[calibrate_channel]); scales.set_scale(scaleFactor[calibrate_channel]); // force read to switch to gain Serial.print("\t|CH:\t"); Serial.print(calibrate_channel,1); Serial.print("\traw:\t"); Serial.print(scales.read(),1); Serial.print("\t| raw:\t"); Serial.print(scales.read(),1); Serial.print("\t| units:\t"); Serial.print(scales.get_units(), 1); Serial.print("\t| gain:\t"); Serial.print(gain[calibrate_channel], 1); Serial.print("\t| factor:\t"); Serial.println(scaleFactor[calibrate_channel], 1); digitalWrite(LED_BUILTIN, LOW);}// check the serial port for input from a console to allow us to alter // the device mode etc.void checkSerial() { if(Serial.available()) { char instruction =Serial.read(); switch (instruction) { case '0':calibrate_channel =0; Serial.println("Channel 0 (A) Selected");壊す; case '1':calibrate_channel =1; Serial.println("Channel 1 (B) Selected");壊す; case 'm':// Measurement mode mode =0; Serial.println("Measurement Mode"); printHeader();壊す; case 'c':// Calibration mode mode =1; Serial.println("Calibration Mode");壊す; case 't':// Tare. Teset the scale to 0 Serial.println("Taring"); scales.power_up(); delay(500); scales.tare(5); // Need to do this for each channel // and update our stored offset. Serial.println("Not properly Tared!");壊す; case 'h':printHeader();壊す; case 'z':zeroScales();壊す; case 's':printSigfoxModelDetails();壊す; default:Serial.println("Unknown instruction. Select:0, 1, m, c, t, h, z, or s"); Serial.println("m - measurement mode"); Serial.println("c - Calibration mode"); Serial.println("0 - Channel 0 (A) Calibration"); Serial.println("1 - Channel 1 (B) Calibration"); Serial.println("t - Tare (scale)"); Serial.println("z - Zero (Weight)"); Serial.println("h - print Header"); Serial.println("s - print Sigfox model details");壊す; } }}// Measure (and record) the temperature and humidity levels// Sets alarms if out of rage (we can't use limits on Internet service side// as the messages may only be sent a few times a day and a brief (maybe hours)// out of range temperature/humidity could easily be missed between// message publishing.void measureTemperatureAndHumidity() { if (!bmeOk) { return; } currentTemperature =bme.readTemperature(); if (currentTemperature maxTemperature) { temperatureAlarm =true; } currentHumidity =bme.readHumidity(); if (currentHumidity maxHumidity) { humidityAlarm =true; }}// Print the values read from the BME280 sensorvoid printBmeValues() { //Serial.print("Temperature ="); Serial.print(bme.readTemperature(), 1); Serial.print("\t"); Serial.print(bme.readPressure() / 100.0F, 0); Serial.print("\t\t"); Serial.p rint(bme.readHumidity(),1); Serial.print("\t\t");}// =============================================================// Sigfox handing// =============================================================// Determine if we should publish the Sigfox message.// We may also wish to publish if the stock level has// changed (or a significant weight level has changed)// but we would need to be careful of exceeding the // 140 messages per day for a noisy system.bool shouldPublish() { // Publish every 15 minutes // this doesn't really need to be this often // but whilst developing it helps keep an eye on the system. int messageIntervalMinutes =15; // On first run after reset // allow a 2 minute delay for the platform to be placed into // the box and stabalise before doing first publish // which is also expected to include a check for zeroing the platform. if (isFirstCycle) { messageIntervalMinutes =2; Serial.println("First cycle"); } // How long ago we last publish a Sigfox message long millisAgo =millis() - lastPublish; return millisAgo> (messageIntervalMinutes * 60 * 1000);}// Publish our measurements (weight, temperature, humidity etc)// to Sigfox.void publishMeasurements() { Serial.println("Sending via Sigfox..."); bool useDownlink =shouldUseDownlink(); if (useDownlink) { Serial.println("Using Sigfox downlink..."); } // stub for message which will be sent SigfoxMessage msg =buildMessage(); SigFox.begin(); SigFox.debug(); // Wait at least 30mS after first configuration (100mS before) delay(100); // Clears all pending interrupts SigFox.status(); delay(1); SigFox.beginPacket(); SigFox.write((uint8_t*)&msg, 12); // endPacket actually sends the data. uint8_t statusCode =SigFox.endPacket(useDownlink); printSigfoxStatus(statusCode); // Status =0 for a successful send, otherwise indicates // a failure. // Store when we last published a Sigfox message // to allow for timed message sending. if (statusCode ==0) { resetAlarms(); } // Update the last publish/downlink times // even if an error resonse was received to prevent // repeated publishing lastPublish =millis(); isFirstCycle =false; if (useDownlink) { parseDownlinkData(statusCode); lastDownlink =lastPublish; } // Store the status value lastSigfoxStatus =statusCode; SigFox.end();}void printSigfoxStatus(uint8_t statusCode) { Serial.print("Response status code :0x"); Serial.println(statusCode, HEX); if (statusCode !=0) { Serial.print("Sigfox Status:"); Serial1.println(SigFox.status(SIGFOX)); Serial1.println(); Serial.print("Atmel Status:"); Serial1.println(SigFox.status(ATMEL)); Serial1.println(); }}// Create the message to be publish to Sigfox.SigfoxMessage buildMessage() { SigfoxMessage message; message.status =getStatusFlags(); message.humidity =(int8_t )currentHumidity; message.temperature =(int8_t)currentTemperature; message.zeroWeight =(int16_t)zeroWeight; message.weight =(int16_t)currentWeight; message.itemCount =(int16_t)(stockLevel * 100); message.driftCorrection =0; // TODO message.filler =0; message.lastStatus =lastSigfoxStatus; return message;}// Get the status flags for the Sigfox message.byte getStatusFlags() { byte status =0; // B7 - First run, // B6 - HX711 fault // B5 - BME280 fault // B4 - Temperature alarm // B3 - Humidity alarm // B2 - weight alarm // B1 - Low stock // B0 - spare // Upper Nibble (Charging/Battery) // Battery flat if (isFirstCycle) { status |=0x80; // 1000 0000 } // HX711 fault. // we don't have a way to check this yet. if (!hx711Ok) { status |=0x40; // 0100 0000 } // BME280 fault if (!bmeOk) { status |=0x20; // 0010 0000 } // Over/Under temperature alarm if (temperatureAlarm> 0) { status |=0x10; // 0001 0000 } // Over/Under humidity alarm if (humidityAlarm) { status |=0x08; // 0000 1000 } // Over/under? weight alarm if (weightAlarm) { status |=0x04; // 0000 0100 } // if computed stock level low. if (lowStock) { status |=0x02; // 0000 0010 } return status;}// Determine if we are requesting a downlink message.bool shouldUseDownlink() { // When debugging uncomment this so as to not keep requesting // downlink //return false; // On first run we want to request a downlink // message to help with zero'ing and setup. if (isFirstCycle) { return true; } // How long ago we last did a downlink message. long millisAgo =millis() - lastDownlink; // try every 12 hours, this keeps us under the // maximum 4 per day. return millisAgo> (12 * 60 * 60 * 1000);}// Parse downlinked data.void parseDownlinkData(uint8_t statusMessage) { if (statusMessage> 0) { Serial.println("No transmission. Status:" + String(statusMessage));戻る; } // Max response size is 8 bytes // set-up a empty buffer to store this. (0x00 ==no action for us.) uint8_t response[] ={0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Expect... // Byte 0:Flags // B7:Zero scales // B6:Set Temperature range (ignore min/max temp if 0) // B5:Set Humidity range (ignore min/max humidity if 0) // B4:Set tolerance? // B3:Set ??? // B2:Update unit weight (ignore update if 0) // B1:// B0:// Byte 1:Min T // Byte 2:Max T // Byte 3:Min Humidity // byte 4:Max Humidity // byte 5:Read tolerence??? (+/- x) // byte 6 &7:Unit weight // Parse the response packet from Sigfox if (SigFox.parsePacket()) { Serial.println("Response from server:"); // Move the response into local buffer. int i =0; while (SigFox.available()) { Serial.print("0x"); int readValue =SigFox.read(); Serial.println(readValue, HEX); response[i] =(uint8_t)readValue; i ++; } // byte 0 - flags. // 0b 1000 0000 if (response[0] &0x80 ==0x80) { zeroScales(); } // 0b 0100 0000 if (response[0] &0x40 ==0x40) { updateTemperatureAlarm(response[1], response[2]); } // 0b 0010 0000 if (response[0] &0x20 ==0x20) { updateHumidityAlarm(response[3], response[4]); } // 0b 0000 0100 if (response[0] &0x04 ==0x04) { // Little Endian format. (ff dd -> 0xddff uint16_t weight =response[7] <<8 &response[6]; updateUnitWeight(weight); } } else { Serial.println("No response from server"); } Serial.println();}void printSigfoxModelDetails() { if (!SigFox.begin()) { Serial.println("Shield error or not present!"); return; } // Output the ID and PAC needed to register the // device at the Sigfox backend. String version =SigFox.SigVersion(); String ID =SigFox.ID(); String PAC =SigFox.PAC(); // Display module informations Serial.println("MKRFox1200 Sigfox configuration"); Serial.println("SigFox FW version " + version); Serial.println("ID =" + ID); Serial.println("PAC =" + PAC); Serial.println(""); Serial.print("Module temperature:"); Serial.println(SigFox.internalTemperature()); Serial.println("Register your board on https://backend.sigfox.com/activate with provided ID and PAC"); delay(100); // Send the module to the deepest sleep SigFox.end();}// =============================================================// General helper methods// =============================================================// Reset the alarms after they have been published.void resetAlarms() { temperatureAlarm =false; humidityAlarm =false; weightAlarm =false;}void zeroScales() { zeroWeight =lastAverage[0] + lastAverage[1]; Serial.print("Zero'd:"); Serial.print(zeroWeight, 1); Serial.println();}void updateTemperatureAlarm(int8_t lower, int8_t upper) { Serial.print("Setting temperature alarm. Min:"); Serial.print(lower); Serial.print(", Max:"); Serial.println(upper); minTemperature =lower; maxTemperature =upper;}void updateHumidityAlarm(int8_t lower, int8_t upper) { Serial.print("Setting humidity alarm. Min:"); Serial.print(lower); Serial.print(", Max:"); Serial.println(upper); minHumidity =lower; maxHumidity =upper;}void updateUnitWeight(uint16_t weight) { Serial.print("Setting unit weight:"); Serial.println(weight); unitWeight =weight;}
Really Smart Box Github Repository
https://github.com/Tinamous/ReallySmartBox カスタムパーツとエンクロージャー
Use this to cut the top and bottom acrylic sheets. cuttingguide_e7GNHf980M.svgThis sits between the lower acrylic sheet and load cell to raise it up a little and provide a edge to the platformsPrint 4 of these for each corner of the lower sheet if needed 回路図
Nothing to complex. 製造プロセス