M5StackCore2とロードセルTAL220B-5kg/HX711とGASで猫が餌を食べた量を自動記録する

# 前記事
https://qiita.com/itortie/items/88009974b232c9624c36

感圧センサだと上手く測定できなかったので、
今回はロードセルを使用する

# 背景
- 猫を2匹飼っている
- 1匹は軽度の脂肪肝の疑い
- 肝臓の負荷を下げるため、毎日食事量を一定に保つ必要がある
- キャットフードは1日3回(朝、昼、夜)
- 1匹あたり目安は45g/日

# 問題
誰がどれだけ食べたか分からない
- 2匹ともムラ食い(食べたいときに食べたい量だけ食べる)
- 給餌皿は分けているが、自分の皿という認識はない(食べたい皿から食べる)

仕方なく、ムラ食いの度に食べた量の記録を付ける
<img width="300" alt="ムラ食いの記録" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/1f85c072-5bdb-fd61-3079-7d145496c051.jpeg">

とても大変

# アプローチ

## 参考記事

色々参考にしたので漏れがあるかも？

構成
https://qiita.com/siroitori0413/items/e45c5b5d3952ef32fff3

プログラム
https://knt60345blog.com/m5stack-googlespreadsheet/
https://hack-tnr.hatenablog.com/entry/2020/06/06/173558

## 必要部品
- M5 Stack Core 2 1個
- ロードセルユニット 1個（Amazonで購入1,698円）
- ジャンパ線　オス―オス 4本
- GROVEケーブル　オス―オス　1本

# 成果物

M5Stackにロードセルを繋いで、Arduino　IDEからプログラムを流し込めば完成
最初UIFlowで実装しようとしましたが、HX711の取り扱いに悩み、サンプルコードが少ないので止めました。
いつか再チャレンジしたい。

## 外観

回路の部分がめっちゃ襲われるのでケースなどで対策必須です

<img width="300" alt="餌皿と猫と計測器" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/f02eff0c-9a87-59f6-d316-1237ef0b178d.jpeg">

<img width="300" alt="猫に襲われる計測器" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/280d36da-191f-337d-1c32-de5d6b066d65.jpeg">

# プログラム
M5Stack側
```c:sample.c
#include <M5Core2.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

// loadcell
void AE_HX711_Init(void);
void AE_HX711_Reset(void);
long AE_HX711_Read(void);
long AE_HX711_Averaging(long adc,char num);
float AE_HX711_getGram(char num);

#define pin_dout  32    // GROVE Yellow
#define pin_slk   33    // GROVE White

// LOAD CELL (TAL220B-5kg)
#define OUT_VOL   0.001f   //定格出力 [V]
#define LOAD      5000.0f    //定格容量 [g]

float offset;

// wifi
char const *ssid = "your ssid";
char const *password = "your password";

// gas
char const *published_url = "your gas endpoint";

void setup_serial(){
  Serial.begin(115200);
  while (!Serial) continue;
}

void setup_wifi(){
  
  Serial.println("Connecting to ");
  Serial.print(ssid);

  // WiFi開始
  WiFi.begin(ssid, password);
 
  // Wi-Fi接続待ち
  while (WiFi.status() != WL_CONNECTED){
    delay(1000);
    M5.Lcd.print(".");
  }

  // WiFi接続成功メッセージの表示
  Serial.println("\nWiFi Connected.");
  M5.Lcd.setCursor(10, 40);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(2);
  M5.Lcd.println("WiFi Connected.");

  // M5StackのIPアドレスを表示
  M5.Lcd.print("IP address: ");
  M5.Lcd.println(WiFi.localIP());
}

void setup(){

  // M5Stack objectの初期化
  M5.begin();

  setup_serial();

  // 画面初期設定
  M5.Lcd.fillScreen(TFT_NAVY);
  M5.Lcd.setCursor(10, 10);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(3);
  M5.Lcd.printf("START");

  // Wi-Fi処理の開始
  Serial.println("setup wifi");
  setup_wifi();

  Serial.println("setup AE-HX711");
  AE_HX711_Init();
  AE_HX711_Reset();
  offset = AE_HX711_getGram(50); 
}

void loop(){

    StaticJsonDocument<500> doc;
    char pubMessage[256];
    
    M5.update();
    
    float data;
    char S1[20];
    char s[20];
    data = AE_HX711_getGram(10);
    sprintf(S1,"%s",dtostrf((data-offset), 5, 1, s));
 
    M5.Lcd.setCursor(0, 0, 1);
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.println("Sending...");
    
    doc["weight"] = S1;
    M5.Lcd.println(S1);
    
    serializeJson(doc, pubMessage);
    
    // HTTP通信開始
    HTTPClient http;
    
    Serial.print(" HTTP通信開始　\n");
    http.begin(published_url);
    
    Serial.print(" HTTP通信POST　\n");
    int httpCode = http.POST(pubMessage);
    
    if(httpCode > 0){
      M5.Lcd.printf(" HTTP Response:%d\n", httpCode);
    
      if(httpCode == HTTP_CODE_OK){
      //if(httpCode == 302){
        M5.Lcd.println(" HTTP Success!!");
        String payload = http.getString();
        Serial.println(payload);
      }
    }else{
      M5.Lcd.println(" FAILED");
      Serial.printf("　HTTP　failed,error: %s\n", http.errorToString(httpCode).c_str());
    }
    
    http.end();
    delay(30000);
}

void AE_HX711_Init(void)
{
  pinMode(pin_slk, OUTPUT);
  pinMode(pin_dout, INPUT);
}
 
void AE_HX711_Reset(void)
{
  digitalWrite(pin_slk,1);
  delayMicroseconds(100);
  digitalWrite(pin_slk,0);
  delayMicroseconds(100); 
}
 
long AE_HX711_Read(void)
{
  long data=0;
  while(digitalRead(pin_dout)!=0);
  delayMicroseconds(10);
  for(int i=0;i<24;i++)
  {
    digitalWrite(pin_slk,1);
    delayMicroseconds(5);
    digitalWrite(pin_slk,0);
    delayMicroseconds(5);
    data = (data<<1)|(digitalRead(pin_dout));
  } 
  digitalWrite(pin_slk,1);
  delayMicroseconds(10);
  digitalWrite(pin_slk,0);
  delayMicroseconds(10);
  return data^0x800000; 
}
 
 
long AE_HX711_Averaging(long adc,char num)
{
  long sum = 0;
  for (int i = 0; i < num; i++) sum += AE_HX711_Read();
  return sum / num;
}
 
float AE_HX711_getGram(char num)
{
  #define HX711_R1  20000.0f
  #define HX711_R2  8200.0f
  #define HX711_VBG 1.25f
  #define HX711_AVDD      4.2987f//(HX711_VBG*((HX711_R1+HX711_R2)/HX711_R2))
  #define HX711_ADC1bit   HX711_AVDD/16777216 //16777216=(2^24)
  #define HX711_PGA 128
  #define HX711_SCALE     (OUT_VOL * HX711_AVDD / LOAD *HX711_PGA)
   
  float data;
 
  data = AE_HX711_Averaging(AE_HX711_Read(),num)*HX711_ADC1bit;   
  data =  data / HX711_SCALE;
 
  return data;
}
```

GAS側
```js.sample.js
var sheet_id="your sheet id";

//デバイスからのPOSTで実行される関数
function doPost(e) {
  
  //POSTのpayloadを格納
  var parsed_data;
  
  try { 
    parsed_data = JSON.parse(e.postData.contents); 
  } catch(_exception){ 
    // JSON以外がPOSTされたら例外を返して終了
    return ContentService.createTextOutput("JSONデータの解釈が出来ません。: " + _exception.message);　
  }
  
  
  var spread_file_object = SpreadsheetApp.openById(sheet_id);
  var sheet_name = "debug";   
  var sheet_object = spread_file_object.getSheetByName(sheet_name);
  
  // スプレッドに行を追記
  sheet_object.appendRow([new Date(), parsed_data.weight]);
  
  SpreadsheetApp.flush(); //描かなくても動く。一応、遅延対策。
  
  //ESP32にレスポンスを返す
  return ContentService.createTextOutput("post success");
  
}

function doGet(e) {  
  //ESP32にレスポンスを返す
  return ContentService.createTextOutput("get success");
  
}
```

# 課題
圧力センサシートを使っていた時と比較して、1g単位で測れるようになりました。
一方で、基盤が猫に襲われたり、ご飯を食べていないときの無駄データの多さが気になります。
重量に変化があったタイミングだけデータを飛ばす、襲われないようにカバーをつける等の工夫が必要そうです。