Хочеш смачного пива, Arduino в допомогу

Дана публікація навіяна іншого. Вона називається «Контролер для домашньої пивоварні Mega Brewery. Part I» і опублікована за авторством megadenis.

З чого все почалося?
Я студент технічного Вузу. Одного разу, сидячи у кафе з товаришем, який тоді навчався в медичному університеті, вирішили відкрити ресторан. Було багато ідей, які, в принципі, заслуговують певної уваги. Наприклад, тнц-пол, який змінює кут нахилу залежно від стилю музики… Але, поряд з усім різноманіттям ідей, так само була ще одна —

… а не зварити нам своє пиво?
Через пару тижнів я зварив пиво, взявши інгредієнти з пивзаводу, на якому працював один батька. Але багато технологічні процеси були порушені, тому замість пива вийшло щось з не дуже приємним запахом.

Через кілька років вирішив повторити процес, трохи автоматизувавши його з допомогою Arduino UNO. І ось, що вийшло.

Почну з того, що повинна робити вся установка.

  • Перевіряти себе — все працює, все підключено;
  • Чистити себе;
  • Підготовлятися до процесу пивоваріння;
  • Варити пиво в напівавтоматичному режимі;
  • Варити пиво в ручному режимі;
  • Варити пиво в автоматичному режимі (як пральна машина пере білизну).


Перший пункт в даний момент не реалізований. Поки навіть думок немає, як реалізувати.
Другий — теж не реалізований, але найближчим часом дороблю, тільки дочекаюся, коли доставлять насоси з ebay.
Третій пункт — досить простий.

Підготовка до процесу пивоваріння
Повідомлення від системи про те, що необхідно залити воду в чан -> програма чекає натискання клавіші ОК — > програма посилає arduino команду на включення твердотільного реле -> твердотільне реле включає однокиловатный антен в чані, доводить до температури 37 градусів, відсилає програмі команду, що все готово для варіння. Підтримує температуру 37 градусів.

Хочеться, щоб була перевірка на наявність води, але датчик ще чекає свого відправлення від «китайських братів».

Варіння пива в напівавтоматичному режимі
В принципі, нескладна процедура:

— Натискаємо в керуючій програмі кнопку «Прогрів», інші керуючі кнопки не активні;
— Після прогріву програма виводить повідомлення «Все готово, можна варити»;
— Засипаємо інгредієнти, вибираємо програму для варіння — кнопка «Варіння пива» стає активною;
— Натискаємо кнопку «Варіння пива», процес пішов;
— Далі система періодично буде сповіщати інформаційними повідомленнями, що робити і коли.

Потрібно слідувати інструкціям.

Варіння пива в ручному режимі
Даний процес дозволяє задавати параметри варіння, змінювати їх у циклі варіння. До нього ще не дійшов.

Варіння в автоматичному режимі
Це мрія. В даний момент немає компонентів для реалізації. Не вистачає насосів, датчиків рівня води. Не знаю, як міряти щільність сусла, скільки алкоголю в молодому пиві і ще багато чого. Але я не втрачаю надії і буду автоматизувати поступово, поки варіння пива не буде виглядати так:

Закинув інгредієнти у відповідні лотки, натиснув кнопку і… через півтора місяці отримав готове пиво.

Це коротко про процесі, а тепер перейдемо до технічне стороні.

Технічна сторона процесу
Як сказано вище за керуючий мікроконтролер виступає arduino UNO. До нього підключені 2 реле, 2
цифрових термометра DS18B20.

Arduino спілкується з основною програмою через com порт. Т.к. у мене немає шильда реального часу до arduino, довелося таймери брати з visual c#. Досвіду написання програм немає, так що, якщо раптом у кого є ідеї, критика — пошту за честь. Критикуйте, дербаньте, тому сказати, що раптом не подобається.

Ось текст програми на arduino
#include <OneWire.h>

OneWire ds(8); // датчики температури сидять на 8 піне
int reley1 = 13;
int reley2 = 12;
int reley3 = 11;
int reley4 = 10;
int reley5 = 7; //включення тен
int reley6 = 6; // тен на заторном чані

float temp1;
float temp2;

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

pinMode(reley1,OUTPUT);
pinMode(reley2,OUTPUT);
pinMode(reley3,OUTPUT);
pinMode(reley4,OUTPUT);
pinMode(reley5,OUTPUT);
pinMode(reley6,OUTPUT);
digitalWrite(reley1,LOW);
digitalWrite(reley2,LOW);
digitalWrite(reley3,LOW);
digitalWrite(reley4,LOW);
digitalWrite(reley5,LOW);
digitalWrite(reley6,LOW);




}

void loop(void) {
if (Serial.available()) {

switch (Serial.read()){ 
case 'i':
infuz();
break;

case 'p':
progrev();
break;

case 'a':
avariya();
break;

case 'v':
varka();
break;

case 't':
temperature();
break;

}
}
}

void varka() {
digitalWrite(reley6, HIGH);
while(Serial.read()!='m') {
temperature();
if (temp1 >= 52.00)
digitalWrite(reley6,LOW);
else 
digitalWrite(reley6,HIGH);
}

while(Serial.read()!='n') {
//digitalWrite(reley6,HIGH);
temperature();
if(temp1>= 62.00)
digitalWrite(reley6,LOW);
else
digitalWrite(reley6,HIGH);
}

while(Serial.read()!='b') {
//digitalWrite(reley6,HIGH);
temperature();
if(temp1 >= 75.00)
digitalWrite(reley6,LOW);
else
digitalWrite(reley6,HIGH);
}
digitalWrite(reley6,LOW);

while(Serial.read()!='c')

delay(1000);

while(Serial.read()!='x') {
digitalWrite(reley5,HIGH);
temperature();
}
digitalWrite(reley5,LOW);

}

void infuz() {
//temperature();
//Serial.available();
while (Serial.read()!='s'){ //вимкнення циклу інфузійної варіння
if (temp<=69.50)
digitalWrite(reley5,HIGH);
else
digitalWrite(reley5,LOW);
}
digitalWrite(reley5,LOW);
}


void progrev() {
while (temp1 <=36.00)
temperature();
digitalWrite(reley6,HIGH);
digitalWrite(reley6,LOW);
//while (temperature() >40.0)
//delay(1000);
Serial.println('s');
}

void avariya(){
digitalWrite(reley1,LOW);
digitalWrite(reley2,LOW);
digitalWrite(reley3,LOW);
digitalWrite(reley4,LOW);
digitalWrite(reley5,LOW);
}


void temperature() {
byte i;
byte present = 0;
byte data[12];
byte addr[8];
byte zator[8] = {40, 23, 218, 43, 6, 0, 0, 22}; // адреса температурного датчика в заторі
byte varilka[8] = {40, 255, 240, 115, 59, 4, 0, 234}; //адреса температурного датчика в варильном чані
float celsius;
// float temp;
boolean gde;

if ( !ds.search(addr)) {
//Serial.println("No more addresses.");
//Serial.println();
ds.reset_search();
delay(250);
// return;
}





if (OneWire::crc8(addr, 7) != addr[7]) {
Serial.println("CRC is not valid!");
// return;
}
ds.reset();
ds.select(addr);
ds.write(0x44, 1); // start conversion, with parasite power on at the end

delay(840); // maybe 750ms is enough, maybe not
// we might do a ds.depower() here, but the reset will take care of it.

present = ds.reset();
ds.select(addr); 
ds.write(0xBE); // Read Scratchpad

for ( i = 0; i < 9; i++) { // we need 9 bytes
data[i] = ds.read();

}

int16_t raw = (data[1] << 8) | data[0];

byte cfg = (data[4] & 0x60);

if (cfg == 0x00) raw = raw & ~7; // 9 bit resolution, 93.75 ms
else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms

celsius = (float)raw / 16.0;

for(i = 0; i < 8; i++) {
if (addr[i] == zator[i]) 
gde = true;
else {
gde = false;
break;
}
}
if (gde) {
Serial.print("t2 "); //temperatura варочника
temp2 = celsius;
Serial.print(temp2);
Serial.println();
}
else
{

Serial.print("t1 "); //температура затору
temp2 = celsius;
Serial.print(temp1);


} 

}






Все б добре, але чому-то третій датчик показує постійно 85 градусів. Поки не можу сказати, чому. А потрібен він для подальшої автоматизації — підтримання температури в холодильнику.

Коротко опишу, що робить програма.

Програма розділена на підпрограми, кожна з яких активується, якщо на com-порту з'являється певний символ. Наприклад, якщо в порт потрапляє літера «p», то включається режим «Прогрів». Або ж, якщо «a» — то викликається підпрограма avariya() і все відключається. При виклику підпрограми temperature() дані записуються в глобальні змінні temp1, temp2. Звідти і потрапляють в потрібні підпрограми.

У майбутньому з'являться підпрограми для варіння різних сортів і навіть самогону.

Тепер, що стосується основної програми управління.

Основна програма управління
Вона написана на visual studio c#.

Вихідний код програми:

Вихідний код програми
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;

namespace WindowsFormsApplication1
{


public partial class Form1 : Form
{

// String portnumber;
SerialPort Port1 = new SerialPort("COM5", 9600);
int s=0;
public Form1()
{
InitializeComponent();
}

/*private const int CP_NOCLOSE_BUTTON = 0x200;
protected override CreateParams CreateParams
{
get
{
CreateParams myCp = base.CreateParams;
myCp.ClassStyle = myCp.ClassStyle | CP_NOCLOSE_BUTTON;
return myCp;
}
}*/
private void Form1_Load(object sender, EventArgs e)
{
label2.Text = Port1.PortName;
Port1.Open();

}
//Перевірка обладнання
private void button1_Click(object sender, EventArgs e)
{
if (Port1.IsOpen == false)
{
try
{
//програму можна доопрацювати інформація з podrugomu.com/node/987

Port1.PortName = label2.Text;
Port1.Open();
Port1.Write("Check");
//SerialPort Port2 = new SerialPort("COM4", 9600);
//Port2.Open();
//label3.Text = Convert.ToString(Port2.ReadByte()); //перевірка показань порту


MessageBox.Show("Процес перевірки обладнання запущено", "Инфорамационное повідомлення");
richTextBox1.Text = richTextBox1.Text + "\n" + "Запуск процесу перевірки обладнання"+" "+DateTime.Now.ToString("HH:mm");


button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = false;


if (Port1.ReadByte() == 1000)
{
richTextBox1.Text = richTextBox1.Text + "\n" + "Закінчення процесу перевірки обладнання"+" "+DateTime.Now.ToString("HH:mm");
button1.Enabled = true;
button2.Enabled = true;
button3.Enabled = true;


Port1.Close();
MessageBox.Show("Кінець Циклу перевірки обладнання" +" "+ DateTime.Now.ToString("HH:mm"));
richTextBox1.SaveFile("CheckLOG.rtf");
}
}
catch
{
richTextBox1.Text = richTextBox1.Text + "\n" + "Помилка процесу перевірки обладнання" +" "+ DateTime.Now.ToString("HH:mm");
MessageBox.Show("Невірно обраний порт пристрою. Процес перевірки не може бути запущений", "Warninig");
richTextBox1.SaveFile("log/Check_"+DateTime.Now.ToString("ddMMyyyy")+".rtf");
}
}
}
// Чищення обладнання
private void button3_Click(object sender, EventArgs e)
{
if (Port1.IsOpen == false)
{
try
{
//програму можна доопрацювати інформація з podrugomu.com/node/987

MessageBox.Show("Ви точно налили дезінфектор", "WARNING", MessageBoxButtons.OK, MessageBoxIcon.Warning);
Port1.PortName = label2.Text;
Port1.Open();
Port1.Write("Clean");

MessageBox.Show("Процес чищення обладнання запущено", "Инфорамационное повідомлення");

button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = false;


if (Port1.ReadByte() == 1000)
{
button1.Enabled = true;
button2.Enabled = true;
button3.Enabled = true;


Port1.Close();
MessageBox.Show("Кінець Циклу чищення обладнання");
}
}
catch
{
MessageBox.Show("Невірно обраний порт пристрою. Процес чищення не може бути запущений", "Warninig");
}
}
}

private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
{

}

private void cOM1ToolStripMenuItem_Click(object sender, EventArgs e)
{
SerialPort Port1 = new SerialPort("COM1", 9600);
// MessageBox.Show("Обраний порт COM1");
label1.Visible = true;
label2.Text = "COM1";

}

private void label2_Click(object sender, EventArgs e)
{

}

private void cOM2ToolStripMenuItem_Click(object sender, EventArgs e)
{
SerialPort Port1 = new SerialPort("COM2", 9600);
// MessageBox.Show("Обраний порт COM2");
label1.Visible = true;
label2.Text = "COM2";
}

private void cOM3ToolStripMenuItem_Click(object sender, EventArgs e)
{
SerialPort Port1 = new SerialPort("COM3", 9600);
// MessageBox.Show("Обраний порт COM3");
label1.Visible = true;
label2.Text = "COM3";
}

private void cOM4ToolStripMenuItem_Click(object sender, EventArgs e)
{
SerialPort Port1 = new SerialPort("COM4", 9600);
// MessageBox.Show("Обраний порт COM4");
label1.Visible = true;
label2.Text = "COM4";

}

private void cOM5ToolStripMenuItem_Click(object sender, EventArgs e)
{
SerialPort Port1 = new SerialPort("COM5", 9600);
// MessageBox.Show("Обраний порт COM5");
label1.Visible = true;
label2.Text = "COM5";
}

private void cOM6ToolStripMenuItem_Click(object sender, EventArgs e)
{
SerialPort Port1 = new SerialPort("COM6", 9600);
// MessageBox.Show("Обраний порт COM6");
label1.Visible = true;
label2.Text = "COM6";
}

private void cOM7ToolStripMenuItem_Click(object sender, EventArgs e)
{
SerialPort Port1 = new SerialPort("COM7", 9600);
// MessageBox.Show("Обраний порт COM7");
label1.Visible = true;
label2.Text = "COM5";
}

private void программаВаркиToolStripMenuItem_click(object sender, EventArgs e)
{

}
// Варіння пива
private void button2_Click(object sender, EventArgs e)
{
// if (Port1.IsOpen == false)
//{
// try
//{
//програму можна доопрацювати інформація з podrugomu.com/node/987
//Port1.PortName = label2.Text;
//Port1.Open();
switch (label3.Text)
{
case "Вибрано інфузійне затирання":
MessageBox.Show("Процес інфузійного затирання Запущено", "Инфорамационное повідомлення");
Port1.WriteLine("і");
timer1.Start();
break;

case "Обрана варіння Cooper":
MessageBox.Show("Процес затирання Cooper запущено", "Инфорамационное повідомлення");
Port1.WriteLine("v");
timer3.Start();
break;

}



button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = false;
// button5.Enabled = false;


// richTextBox1.Text = Port1.ReadLine()+"\n";


/* if (Port1.ReadLine() == "e\r") {

button1.Enabled = true;
button2.Enabled = true;
button3.Enabled = true;
button5.Enabled = true; */
//Port1.Close();
// MessageBox.Show("Кінець Циклу варіння пива");

// }
// }





// catch
//{
// MessageBox.Show("Невірно обраний порт пристрою. Процес варіння не може бути запущений", "Warninig");
//}
// }









}

private void button5_Click(object sender, EventArgs e)
{
Port1.Write("p");
button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = false;
timer2.Start();
richTextBox1.Text = richTextBox1.Text + "\n" + "Запуск підігріву води в заторном чані до 37 градусів" + " " + DateTime.Now.ToString("HH:mm");


/* Port1.Open();

//Port1.Open();

//richTextBox1.Text = richTextBox1.Text + "\n" + Port1.ReadLine();

Port1.WriteLine("o");
Port1.Close(); */
}


private void выходToolStripMenuItem_Click(object sender, EventArgs e)
{
Close();
}

private void button4_Click(object sender, EventArgs e)
{

}

private void инфузионноеЗатираниеToolStripMenuitem_click(object sender, EventArgs e)
{
label3.Text = "Вибрано інфузійне затирання";
}

private void timer1_Tick(object sender, EventArgs e)
{
textBox1.Text = Convert.ToString(Convert.ToInt32(s / 60));
textBox2.Text = Convert.ToString(Convert.ToInt32((s)));
s++;
label5.Text = Port1.ReadLine();

if (s==4200){ //70 хвилин це 4200 секунд
timer1.Stop();
//timer2.Start();
//Port1.Open();
Port1.WriteLine("s");
// Port1.Close();
MessageBox.Show("інфузійне затирання закінчено необхідно відфільтрувати сусло");
button1.Enabled = true;
button2.Enabled = true;
button3.Enabled = true;
button5.Enabled = true;
textBox1.Text = "";
textBox2.Text = "";

}


//Port1.Close();



}

private void label4_Click(object sender, EventArgs e)
{

}

private void timer2_Tick(object sender, EventArgs e)
{
label5.Text = Port1.ReadLine();

if (Port1.ReadLine() == "s\r")
{
timer2.Stop();
MessageBox.Show("Вода має температуру 37 градусів. Можна засипати солод і включати режим затирання");
richTextBox1.Text = richTextBox1.Text + "\n" + "Закінчення процесу підготовки mash-up чана. Т=37 градусів" + " " + DateTime.Now.ToString("HH:mm");
button2.Enabled = true;
}


}

private void аварийноеОтключениеToolStripMenuitem_click(object sender, EventArgs e)
{
Port1.WriteLine("a");
MessageBox.Show("Користувач аварійно відключив всю систему");
}

private void заторногоКотлаToolStripMenuItem_click(object sender, EventArgs e)
{
Port1.WriteLine("k");
MessageBox.Show("Включений режим кип'ятіння mash-up чана. Чекайте 60 хвилин");
richTextBox1.Text = richTextBox1.Text + "\n" + "Включений режим кип'ятіння mash-up чана. Чекайте 60 хвилин" + " " + DateTime.Now.ToString("HH:mm");

}

private void timer3_Tick(object sender, EventArgs e)
{
string s1 = "";
Port1.ReadLine();
if (s1.Substring(0, 2) == "t1")
label5.Text = s1.Substring(4, 5);
if (s1.Substring(0, 2) == "t2")
label9.Text = s1.Substring(4, 5);

s++;
if (s == 900)
{
Port1.WriteLine("m");
MessageBox.Show("Етап затирання при 62 градусах");
richTextBox1.Text = richTextBox1.Text + "\n" + "Етап затирання при 62 градусах" + " " + DateTime.Now.ToString("HH:mm");

}
if(s== 2250)
{
Port1.WriteLine("n");
MessageBox.Show("Етап затирання при 78 градусах");
richTextBox1.Text = richTextBox1.Text + "\n" + "Етап затирання при 78 градусах" + " " + DateTime.Now.ToString("HH:mm");


}

if (s ==2700)
{
Port1.WriteLine("b");
MessageBox.Show("Етап затирання закінчений, можна зливати сусло");
richTextBox1.Text = richTextBox1.Text + "\n" + "Етап затирання закінчено. Можна зливати сусло" + " " + DateTime.Now.ToString("HH:mm");


}

}

private void обычнаяToolStripMenuItem_Click(object sender, EventArgs e)
{
label3.Text = "Обрана варіння Cooper";
}



}
}




Програма спілкується з arduino через com порт. Єдине, ніяк не можу перемогти, щоб при опитуванні com порту програма не підвисає на момент виконання підпрограми. Після закінчення підпрограми програма відвисає, але поки це не критично і навіть якийсь плюс. Захист від дурня — щоб не натиснути нічого під час певної процедури.

Отже, програма вміє
— готувати обладнання до варінні;
— варити;
— писати простенький лог роботи (дуже корисно при подальшому аналізі варіння);
— вибирати порт для підключення до керуючого контролера(arduino UNO);
кип'ятити варочнік і заторний чани.

Коли прийдуть помпи і солинойдные клапани, буду автоматизувати далі. А поки кожне воскресіння буду додавати по одній програмі варіння. В сумі 5 програм. Так само чекає своєї черги реалізація ручного режиму. Як говориться,

to be continued...

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.