M5Stackで焼酎からエタノールを蒸留

M5Stack

入手しづらくなっている消毒用アルコール(エタノール)を、M5Stackで温度をモニターしながら焼酎から蒸留してみました。M5Stackは使わなくても蒸留自体はできます。方法を変えて2回行った動画はこちら。

動画中でも触れましたが、アルコールは引火性があり、また圧力鍋を改造して使用しています。試してみる場合には、安全に十分注意して、ご自身の責任でお願いします。また蒸留により焼酎のアルコール濃度を高めることは酒税法上の問題があります。イソプロピルアルコール(動画では水抜き剤)を添加することで飲用不可とすれば対策になるようですが、この点についてもご自身での判断をお願いします。

圧力鍋に耐熱シリコンチューブをつけてアルミパイプで冷却

圧力鍋のふたにシリコンチューブと温度センサーを取り付ける

実際の方法です。材料はアルコール度数25%の焼酎。蒸留に使う道具は圧力鍋のおもりの部分に耐熱シリコンチューブ(ホームセンターにありました)をつけたものです。蓋のすきまからワイヤを通し、内部にPt100の温度センサーを仕込み、温度を測定します。温度の測定にはMax31865のモジュールとM5Stackを使用しました。蒸留だけならセンサーやモジュール、M5Stackは不要です。

シリコンチューブの先に、アルミパイプを接続し、ペットボトルにいれた水で冷却してエタノールを液体にします。チューブがペットボトルを貫通する部分の防水は、いわゆるダクトテープを使うとうまくいきました。冷却水はすぐに温まるので、入れ替えができるように工夫が必要です。水道から給水し、流しへ直接排水できると楽でしょう。

消毒用エタノールの蒸留(2回目)

ガスレンジに金属板を敷いた上で、安全に配慮して直火ではなくIHヒーターを使って加熱します。1回目は温度をモニターしながらどこで蒸留をストップするのか判断したのですが、内部の圧力が上がってしまい、目安となる温度が想定と違ってしまいました(沸点上昇)。2回目は、およそ100mlずつ液体を集め、それぞれ比重を測定してアルコール度数を推定しました。

材料の1/3以上の量の消毒液が作れます

低温ではアルコール濃度の高い気体が発生し、蒸留を続けて液体中のアルコールの濃度が下がると沸点が上昇し、気体中のアルコール濃度が下がっていきます。実測してみたところ、最初の300ml (#1~#3) はおよそ60%以上になっていました。#1から#3までをブレンドすると、69%・320ml、#1から#4までの場合は62%・400mlの消毒液ができる計算になります。材料にした焼酎900mlの原価は500円以下、加熱時間は30分以下ですので、道具が揃っていれば十分実用になりますね。

アルコール(vol%)比重(15℃)
60-63%0.91
64-68%0.90
69-72%0.89
73-76%0.88
77-79%0.87
(リンク先より抜粋して作成)

アルコールの濃度を計測する際に、体積と重さをそれぞれ計って比重を計算するのはあまり適切ではないです。家庭用のはかりや計量カップで、100mlや100gを誤差1%で計測するのは難しいからです。左のエタノール水溶液の容量%と比重及び重量%等との関係」(リンク)をみていただくとわかるように、比重が0.01変わるとアルコール濃度は大きく動きます。動画で行った、アルコールの重さと、同体積の水の重さから比重を出す、という方法が一番精度が高いと思います。重さの絶対値が多少ずれていても、かなり正確な比重の数字が出ているはずです。

上のグラフの#1を例にすると、液体の重さは90.1gで、同体積の水の重さは103.1g、したがって比重は0.874であることから濃度76%としています(動画では78%となっていますが76%が正しいです)。

M5Stackで温度を計測してGoogle Spreadsheetに自動保存する

ここからは蒸留と直接関係ありませんが、温度を測定してGoogle Spreadsheetに自動保存する、M5Stackのプログラムを載せておきます。10秒ごとに温度を測定して表示し、10個データがたまったらSpreadsheetに保存するだけの単純なものなのです。WiFiへの接続、日時からのファイル名/シート名の作成など、全部入っているので長くなりました。ご利用される際には、includeするライブラリの準備、最初の方にある”xxxxx”を書き換えることと(SSID, password, exec_urlの3か所)、Google App Script側での準備が必要です

#include <M5Stack.h>
#include <Adafruit_MAX31865.h>
#include <WiFiClientSecure.h>
#include "time.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";//google app scriptのexec url

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(午前零時)からの秒数

#define n_of_data 10//一度に送信するデータ数は10
unsigned long timeData[n_of_data];//測定時刻の配列
float tempData[n_of_data];//測定温度の配列

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

// Use software SPI: CS, DI, DO, CLK
Adafruit_MAX31865 max31865 = Adafruit_MAX31865(17, 16, 21, 22);

#define RREF      426.5           //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

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

//hostに接続し、exec_urlを使ってvalues_to_postをpostする

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";}//1桁の場合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 senddata(){
  LcdInit();
  M5.Lcd.println("Send data to Google Spreadsheet");
  connectingWiFi();

  String values = filename;  // 以下で送信内容をすべてカンマ区切りでvaluesにいれていく
  values += ",";
  values += sheetname;
  values += ", 2";                      // timeDataとtempDataの2つを送信する
  for (int i=0;i<n_of_data;i++){
    values += ",";
    values += String(timeData[i],DEC);
    values += ",";
    values += String(tempData[i],2);
  }

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

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

void setup(){
  M5.begin();
  M5.Lcd.setTextSize(2);
  M5.Lcd.println("Distillation : setup()");
  max31865.begin(MAX31865_2WIRE);  // set to 2WIRE or 4WIRE as necessary
  makeFilename();
  delay(1000);
  M5.Lcd.clear();
}

void loop() {
  LcdInit();
  float temp = readTemp();
  uint16_t rtd = max31865.readRTD();
  tempData[counter] = temp;

  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("temp = "); M5.Lcd.println(temp);
  M5.Lcd.print("rtd = "); M5.Lcd.println(rtd);

  delay(10*1000);//10秒間隔

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

作成されるスプレッドシートは、ファイル名が”TempData_”+年月日が2桁ずつ、シート名が時分秒(HHMMSSの数字6桁)となります。保存されるデータはA列に経過秒数(timeData)、B列に温度(tempData)です。

Google Spreadsheetに自動記録された温度データ

上の例では、ファイル名から測定開始日が2020年5月11日、シート名が作成された時刻が17:50:19で、9行目のデータは、そこから84秒後なので17:51:43、温度39.19℃とわかります。

コメント

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