M5Stackでレーズンから天然酵母をおこす

M5Stack

お店でイーストが買えないので、レーズンを使った天然酵母づくりに挑戦してみました。

USBミルクウォーマー

参考にしたのは、レーズンを購入した富沢商店のレシピです。レーズンを計量し、3倍の水と15%の砂糖を加え、ラップで蓋をして発酵させます。

温度を一定にするために、M5Stackに温度センサーとリレー、ヒーターを取り付けます。ヒーターは強力なものでなくて良いので、AliExpressで購入したUSBミルクウォーマー($2.13)を使ってみました。

M5Stackに温度センサーとリレー、ヒーターを接続

Pt100のセンサーとMAX31865のモジュールを使い、瓶の外側で温度を測定し、設定温度より低ければヒーターをオンにします。衛生面から発酵液にセンサーを入れず、外で測定しました。設定は30度にしました。内部の発酵液は、室温と30度の間のどこかで落ち着くだろうと考えてのことです。別なセンサー(DHT22)で室温も測ります。比較のために、ヒーターなし、室温での発酵も同時に行いました。

温度が高すぎてカビだらけになり廃棄

5/16 右側のヒーターありを廃棄

5月7日に発酵開始です。ヒーターありの方は2日でカビが発生。その後もカビとの闘いが続き、5月16日には廃棄となりました。設定温度が高すぎたのと、蓋をせずラップのみだったことが原因と思います。ここからは、比較用に作っていた室温発酵のものを、蓋で密閉して発酵を続けました。見極めが難しかったのですが、開始から2週間経った5月21日に発酵終了としています。

温度のグラフです。最初の3日間はヒーターの発熱体と温度センサーが近すぎて、瓶外側の温度が大きく変動していますが、ヒーターが弱いので、瓶内部は安定していると思います。外気温が上昇した場合、室温のピークはだいぶ遅れて夜0時頃となっているのは新たな発見でした。

中だねから焼成まで


同じ作者のレシピを参考に、まず液種40gに同量の小麦粉をまぜました。約2倍に膨らんだところです。

冷蔵庫で一晩休ませて、さらに60gの小麦粉と水を加え、30時間かけてさらに倍に発酵させませした。

これをすべて使い、強力粉200g、水、塩小さじ1弱、砂糖小さじ半分を加えて、ミキサーでこねます。水の量は適当。ちょっと多すぎたかもしれません。

一次発酵して分割、ベンチ、成形、二次発酵まで来ました。

190℃に設定して13分で焼きあがりです。

おいしくいただきました。

M5Stackのプログラム

M5Stackのプログラムは、アルコール蒸留のものを手直しして、室温の測定やリレーのオンオフを加えて使っています。ご利用される際には、includeするライブラリの準備、最初の方にある”xxxxx”を書き換えることと(SSID, password, exec_urlの3か所)、Google App Script側での準備が必要です

#include <M5Stack.h>
#include <Adafruit_MAX31865.h>
#include <WiFiClientSecure.h>
#include "time.h"
#include <SimpleDHT.h>

//****** ネットワーク関連 ******
const char* ssid     = "xxxxx";   // your network SSID (name of wifi network)
const char* password = "xxxxx";    // your network password

const char* host = "script.google.com";
String exec_url = "https://script.google.com/macros/s/xxxxx/exec";

WiFiClientSecure client;

//****** 時計用 ******
const char* ntpServer =  "ntp.jst.mfeed.ad.jp";
const long  gmtOffset_sec = 9 * 3600; // 時刻取得用
const int   daylightOffset_sec = 0;
struct tm timeinfo;

unsigned long startMillis,   //シート名を作成した時のmillis()
              startSec,      //シート名を作成した時の0:00:00からの秒数
              measureMillis, //測定した時のmillis()
              measureSec;    //測定した時の0:00:00からの秒数


#define n_of_data 12//一度に送信するデータ数
unsigned long timeData[n_of_data];//測定時刻
float bottleTempData[n_of_data];//ボトルの温度
float ambTempData[n_of_data];//周囲の温度
int heaterData[n_of_data];//ヒーター状態

String filename, sheetname;// ファイル名、シート名

// Use software SPI: CS, DI, DO, CLK
Adafruit_MAX31865 max31865 = Adafruit_MAX31865(17, 16, 21, 22);
// use hardware SPI, just pass in the CS pin
//Adafruit_MAX31865 max = Adafruit_MAX31865(10);

// The value of the Rref resistor. Use 430.0 for PT100 and 4300.0 for PT1000

#define RREF      421  //0℃の水と100℃の熱湯でセンサ・モジュール毎に校正

//#define RREF      430.0
// The 'nominal' 0-degrees-C resistance of the sensor
// 100.0 for PT100, 1000.0 for PT1000
#define RNOMINAL  100.0

float targetTemp = 25;
boolean heaterOn = true;
int counter = 0;  //まとめてデータを送るため測定回数をカウント

// DHT22関連

// for DHT22, 
//      VCC: 5V or 3V
//      GND: GND
//      DATA: 2
int pinDHT22 = 2;
SimpleDHT22 dht22(pinDHT22);
float DHTtemp, DHThumid;

String postValues(String values_to_post) {
  M5.Lcd.println("postValues");delay(1000);
  if (client.connect(host, 443)){
    LcdInit();
    M5.Lcd.println("Posting data...");
    
    client.println("POST " + exec_url + " HTTP/1.1");
    client.println("HOST: " + (String)host);
    client.println("Connection: close");
    client.println("Content-Type: text/plain");
    client.print("Content-Length: ");
    client.println(values_to_post.length());
    client.println();
    client.println(values_to_post);
    delay(100);

    while (client.available()) {
      char c = client.read();
      M5.Lcd.print(c);
    }
    client.stop();
    delay(1000);
    return "post end";
  } 
  else {
    return "ERROR";
  }
}

void connectingWiFi(){

  boolean WiFiOn;  // WiFi接続したらtrue
  int n_trial, max_trial = 10;  //WiFi接続試行回数とその上限

  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  WiFi.begin(ssid, password);
  LcdInit();
  M5.Lcd.print("Connecting to WiFi");
  // attempt to connect to Wifi network:
  n_trial = 0;
  while (WiFi.status() != WL_CONNECTED && n_trial < max_trial) {
    M5.Lcd.print(".");
    // wait 1 second for re-trying
    n_trial++;
    delay(1000);
  }
  LcdInit();
  if (WiFi.status() == WL_CONNECTED)
    {WiFiOn = true;
    M5.Lcd.print("Connected to ");
    M5.Lcd.println(ssid);
    M5.Lcd.print("IP: ");
    M5.Lcd.println(WiFi.localIP());  }
  else
    {WiFiOn = false;
    M5.Lcd.print("WiFi connection failed");  }
  delay(1000);
}

void makeFilename(){ 
  LcdInit();
  connectingWiFi();
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  M5.Lcd.println("configTime OK.");
  getLocalTime(&timeinfo);  //時刻取得
  startMillis = millis();   //ミリ秒取得
  startSec
         = (timeinfo.tm_hour)*3600+timeinfo.tm_min*60+timeinfo.tm_sec;///シート名にする時刻の0:00:00(午前零時)からの秒数
  M5.Lcd.print("Start Sec =");M5.Lcd.println(startSec);
  M5.Lcd.print("Start Millis =");M5.Lcd.println(startMillis);

  filename = "TempData_";  // ファイル名初期化 最初に"TempData", 以下、年月日からシート名作成
  filename += String(timeinfo.tm_year-100,DEC);//1900年から数えるので調整
  if(timeinfo.tm_mon<9){filename += "0";}//0からはじまるので調整
  filename += String(timeinfo.tm_mon+1,DEC);//0からはじまるので調整
  if(timeinfo.tm_mday<10){filename += "0";}
  filename += String(timeinfo.tm_mday,DEC);
  filename += ".csv";
  M5.Lcd.print("Filename =");M5.Lcd.println(filename);
  
  sheetname ="";  // シート名初期化 以下、時分秒からシート名作成
  if(timeinfo.tm_hour<10){sheetname = "0";}
  sheetname += String(timeinfo.tm_hour,DEC);
  if(timeinfo.tm_min<10){sheetname += "0";}
  sheetname += String(timeinfo.tm_min,DEC);
  if(timeinfo.tm_sec<10){sheetname += "0";}
  sheetname += String(timeinfo.tm_sec,DEC);
  M5.Lcd.print("Sheetname =");M5.Lcd.println(sheetname);
  WiFi.disconnect();
  delay(1000);
}

void LcdInit(){
  M5.Lcd.clear(BLACK);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(WHITE, BLACK);
  M5.Lcd.setCursor(0, 0);
}



float readTemp(){
  // エラーチェックは残しておく エラーが出た場合には10秒表示
  // Check and print any faults
  uint8_t fault = max31865.readFault();
  if (fault) {
    M5.Lcd.print("Fault 0x"); M5.Lcd.println(fault, HEX);
    if (fault & MAX31865_FAULT_HIGHTHRESH) {
      M5.Lcd.println("RTD High Threshold"); 
    }
    if (fault & MAX31865_FAULT_LOWTHRESH) {
      M5.Lcd.println("RTD Low Threshold"); 
    }
    if (fault & MAX31865_FAULT_REFINLOW) {
      M5.Lcd.println("REFIN- > 0.85 x Bias"); 
    }
    if (fault & MAX31865_FAULT_REFINHIGH) {
      M5.Lcd.println("REFIN- < 0.85 x Bias - FORCE- open"); 
    }
    if (fault & MAX31865_FAULT_RTDINLOW) {
      M5.Lcd.println("RTDIN- < 0.85 x Bias - FORCE- open"); 
    }
    if (fault & MAX31865_FAULT_OVUV) {
      M5.Lcd.println("Under/Over voltage"); 
    }
    max31865.clearFault();
    delay(1000);
  }
  
//測定値を返す  
  return max31865.temperature(RNOMINAL, RREF);
}

void setHeater(int heaterPower){
  if (heaterPower == 100){
    digitalWrite(26, HIGH);//Relay ON
    M5.Lcd.println("RelayOn");
  }
  else {digitalWrite(26, LOW);//Relay OFF
  M5.Lcd.println("RelayOff");
  }
  delay(5 * 60 * 1000);//5分間隔
}

void senddata(){
  LcdInit();
  M5.Lcd.println("Send data to Google Spreadsheet");
  connectingWiFi();

  String values = filename;
  values += ",";
  values += sheetname;
  values += ", 4";
  for (int i=0;i<n_of_data;i++){
    values += ",";
    values += String(timeData[i],DEC);
    values += ",";
    values += String(bottleTempData[i],2);
    values += ",";
    values += String(ambTempData[i],2);
    values += ",";
    values += String(heaterData[i],2);
  }

  M5.Lcd.print("values =");
  M5.Lcd.println(values);
  M5.Lcd.print("Making Data Done\n");
  delay(1000);
  
  String response = postValues(values);

  M5.Lcd.print("Response : ");
  M5.Lcd.println(response);
  delay(1000);
  WiFi.disconnect();
  M5.Lcd.clear();
}

void read_DHT22(){
  int err = SimpleDHTErrSuccess;
  if ((err = dht22.read2(&DHTtemp, &DHThumid, NULL)) != SimpleDHTErrSuccess) {
    M5.Lcd.println("Read DHT22 failed, err="); M5.Lcd.println(err);delay(2000);
    return;
  }
  return;  
}

void setup(){
  pinMode(26, OUTPUT);//Relay pin : 26

  M5.begin();
  M5.Lcd.setTextSize(2);
  M5.Lcd.println("Temp Control Test this is setup()");
  max31865.begin(MAX31865_3WIRE);  // set to 2WIRE or 4WIRE as necessary
  makeFilename();
  delay(1000);
  M5.Lcd.clear();
  
}

void loop() {
  LcdInit();
  float temp = readTemp();
  bottleTempData[counter] = temp;
  read_DHT22();
  ambTempData[counter] = (float)DHTtemp;
  
  if (temp < targetTemp){
    heaterOn = true;
  }
  else{heaterOn = false;}
  heaterData[counter] = heaterOn;

  timeData[counter] = (millis() - startMillis )/1000;  //シート名作成からの経過秒数

  
  M5.Lcd.setCursor(0,50);
  M5.Lcd.print("counter = "); M5.Lcd.println(counter);
  M5.Lcd.print("timeData = "); M5.Lcd.println(timeData[counter]);
  M5.Lcd.print("bottleTemp = "); M5.Lcd.println(bottleTempData[counter]);
  M5.Lcd.print("ambientTemp = "); M5.Lcd.println(ambTempData[counter]);
  M5.Lcd.print("targetTemp = "); M5.Lcd.println(targetTemp);
  M5.Lcd.print("heaterOn = "); M5.Lcd.println(heaterOn);

  if (heaterOn){setHeater(100);}
  else {setHeater(0);}

  LcdInit();
  counter++;
  if (counter >= n_of_data){senddata(); counter = 0;};
}

コメント

タイトルとURLをコピーしました