Compare commits

..

No commits in common. "master" and "v1.0.0" have entirely different histories.

24 changed files with 173 additions and 684 deletions

3
.gitignore vendored
View File

@ -1,3 +0,0 @@
tags
*.zip
.geany*

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "boil_kettle/src/Arduino-PID-Library"]
path = boil_kettle/src/Arduino-PID-Library
url = https://git.giacofei.org/Brewery/Arduino-PID-Library.git

View File

@ -1,13 +1,8 @@
# Changelog
All notable changes to this project will be documented in this file.
## [1.0.0] - 2022-01-14
First working release running in the brewery.
### Features
Super simple rotary encoder control.
- Rotary button toggles heat on/off.
- Turning encoder raises and lowers element power (duty cycle).
- rotary speed is used to advance power faster if the knob is turned faster.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.0.1] - 2021-09-13
Initial upload of files.

View File

@ -3,9 +3,9 @@
Simple PWM & (eventually) temperature control for an Arduino based electric brewing system.
## Libraries needed to make this thing work
- ~~[ArduinoJson.h](https://www.arduino.cc/reference/en/libraries/arduinojson/)~~
- [ArduinoJson.h](https://www.arduino.cc/reference/en/libraries/arduinojson/)
- [MD_REncoder.h](https://www.arduino.cc/reference/en/libraries/md_rencoder/)
- [LiquidMenu.h](https://www.arduino.cc/reference/en/libraries/liquidmenu/)
- [LiquidCrystal_I2C.h](https://www.arduino.cc/reference/en/libraries/liquidcrystal-i2c/)
- ~~[MQTT.h](https://www.arduino.cc/reference/en/libraries/mqtt/)~~
- [MQTT.h](https://www.arduino.cc/reference/en/libraries/mqtt/)
- [Adafruit_MAX31865.h](https://www.arduino.cc/reference/en/libraries/adafruit-max31865-library/)

View File

@ -1,127 +1,103 @@
//Built-in
#include <Arduino.h>
#include <SPI.h>
#include <Ethernet.h>
// Additoinal Libraries
#include <ArduinoJson.h>
#include <MQTT.h>
#include <LiquidCrystal_I2C.h>
#include <LiquidMenu.h> // LiquidMenu_config.h needs to be modified to use I2C.
#include <LiquidMenu.h> // LiquidMenu_config.h needs to be modified to use I2C.
#include <MD_REncoder.h>
#include <Adafruit_MAX31865.h>
//#include <EEPROM-Storage.h>
// My Includes
#include "config.h"
#include "src/button/button.h"
#include "src/slowPWM/slowPWM.h"
#include "src/thermoControl/thermoControl.h"
//#include "src/Arduino-PID-Library/PID_v1.h"
#include "button.h"
#include "slowPWM.h"
double KettleDuty = 0;
double KettleSetpoint = 70;
double CurrentTemp;
bool tuning = false;
// Pin definitions
#define encoderCLK 2
#define encoderDT 3
#define encoderBTN 4
#define kettlePWM 5
//EEPROMStorage<double> eepromKp(0, 2.0); // 9 bytes for doubles 8 + 1 for checksum
//EEPROMStorage<double> eepromKi(9, 5.0); // Initialize at zero.
//EEPROMStorage<double> eepromKd(18, 1.0);
// Global variables.
byte KettleDuty = 0;
bool KettleOn = false;
// User I/O objects.
Button Enter;
slowPWM boilPWM;
MD_REncoder rotary = MD_REncoder(encoderDT, encoderCLK);
LiquidCrystal_I2C lcd(0x27, 20, 4);
Adafruit_MAX31865 thermoRTD = Adafruit_MAX31865(KettleRTD);
thermoControl Controller(&CurrentTemp, &KettleSetpoint, &KettleDuty, AUTOMATIC);
LiquidCrystal_I2C lcd(0x27,20,4);
//PID ControllerPID(&CurrentTemp, &KettleDuty, &KettleSetpoint, eepromKp, eepromKi, eepromKd, P_ON_M, DIRECT);
EthernetClient net;
MQTTClient mqtt_client;
unsigned long lastRun = 0;
// Return a character array to represent the
// On/Off state of the kettle.
char* KettleState() {
switch (Controller.Mode()) {
case OFF: return (char*)"OFF";
case AUTOMATIC: return (char*)"AUT";
case MANUAL: return (char*)"MAN";
default: return (char*)"OFF";
if (KettleOn) {
return (char*)"On";
} else {
return (char*)"Off";
}
}
byte setpoint() {
return (byte)KettleSetpoint;
}
byte duty() {
return (byte)KettleDuty;
}
byte current() {
return (byte)CurrentTemp;
}
// LCD menu setup.
LiquidLine KettleState_line(0, 0, "Boil Kettle ", KettleState);
LiquidLine kettle_temp_line(0, 1, "Setpoint (F) ", setpoint);
LiquidLine kettle_power_line(0, 2, "Power (%) ", duty);
LiquidLine kettle_currenttemp_line(0, 3, "Actual Temp (F) ", current);
LiquidScreen home_screen(KettleState_line, kettle_temp_line, kettle_power_line,kettle_currenttemp_line);
LiquidMenu menu(lcd);
// Interrupt function to run when encoder is turned.
//
// Increases/decreases the kettle output to a max
// of 100% and minimum of 0%.
void doEncoder() {
void doEncoder()
{
uint8_t result = rotary.read();
uint8_t inc = 1;
uint8_t inc;
if (result) {
uint8_t speed = rotary.speed();
speed >= 10 ? inc = 5 : inc = 1;
}
if (Controller.Mode() == AUTOMATIC) {
if (result == DIR_CW) {
KettleSetpoint = (KettleSetpoint / inc) * inc + inc;
} else if (result == DIR_CCW) {
KettleSetpoint = (KettleSetpoint / inc) * inc - inc;
}
if (KettleSetpoint > 212) KettleSetpoint=212;
if (KettleSetpoint < 0) KettleSetpoint=0;
} else if (Controller.Mode() == MANUAL) {
if (result == DIR_CW) {
KettleDuty = (KettleDuty / inc) * inc + inc;
} else if (result == DIR_CCW) {
KettleDuty = (KettleDuty / inc) * inc - inc;
}
if (KettleDuty > 100) KettleDuty = 100;
if (KettleDuty < 0) KettleDuty = 0;
if (result == DIR_CW && KettleDuty < 100) {
KettleDuty = (KettleDuty / inc) * inc + inc;
} else if (result == DIR_CCW && KettleDuty > 0) {
KettleDuty = (KettleDuty / inc) * inc - inc;
}
//menu.update();
}
// LCD menu setup.
LiquidLine KettleState_line(0, 0, "Boil Kettle ", KettleState);
LiquidLine kettle_power_line(0, 1, "Kettle Power % ", KettleDuty);
LiquidScreen home_screen(KettleState_line, kettle_power_line);
LiquidMenu menu(lcd);
void setup() {
lastRun = millis() - UpdateInterval;
Serial.begin(115200);
unsigned long lastRun = millis() - UpdateInterval;
Serial.begin(9600);
rotary.begin();
thermoRTD.begin(MAX31865_3WIRE);
Ethernet.begin(mac, ip);
Serial.println("Setting up...");
attachInterrupt(digitalPinToInterrupt(encoderCLK), doEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(encoderDT), doEncoder, CHANGE);
pinMode(encoderCLK, INPUT_PULLUP);
pinMode(encoderDT, INPUT_PULLUP);
Enter.begin(encoderBTN);
boilPWM.begin(kettlePWM, PeriodPWM);
Controller.Mode(OFF);
// if you get a connection, report back via serial:
if (Ethernet.linkStatus() == LinkON) {
SetupMQTT(MQTT_BROKER);
} else {
// if you didn't get a connection to the server:
Serial.println("connection failed");
}
lcd.init();
lcd.backlight();
@ -129,126 +105,40 @@ void setup() {
menu.init();
menu.add_screen(home_screen);
menu.update();
};
void run_kettle() {
void UpdateBoilKettle(){
static byte last_KettleDuty = 0;
if (Enter.pressed()) {
Serial.println("Enter Pressed");
if (Controller.CycleMode() == OFF) {
KettleDuty = 0;
}
KettleOn = !KettleOn;
menu.update();
}
Controller.Compute();
}
/*
void run_tuning() {
if (Enter.pressed()) {
stopAutoTune();
if (last_KettleDuty != KettleDuty) {
last_KettleDuty = KettleDuty;
menu.update();
}
if (KettleOn) {
boilPWM.compute(KettleDuty);
} else {
boilPWM.compute(0);
}
int result = ControllerPID.ComputeTune();
if (result!=0)
{
tuning = false;
}
if(!tuning)
{ //we're done, set the tuning parameters
ControllerPID.SetTunings(ControllerPID.TunedKp(),ControllerPID.TunedKi(),ControllerPID.TunedKd());
ControllerPID.Mode(OFF);
}
}
*/
void loop() {
unsigned long now = millis();
static unsigned long lastTime=now-2000;
unsigned long timeChange = (now - lastTime);
static byte lastoutput = LOW;
static double lastKettleDuty = 0;
static double lastKettleSetpoint = 0;
static byte lastTemperature;
UpdateBoilKettle();
if(timeChange >= 2000) {
CurrentTemp = 32 + 1.8 * thermoRTD.temperature(RNOMINAL_KETTLE, RREF_KETTLE);
lastTime = now;
unsigned long elapsedTime = (millis() - lastRun);
if (Ethernet.linkStatus() == LinkON && elapsedTime >= UpdateInterval) {
mqtt_client.loop();
//if (!mqtt_client.connected()) ConnectMQTT();
SendSensorData();
lastRun = millis();
}
if (!tuning) {
run_kettle();
} else {
//run_tuning();
}
byte output = boilPWM.Compute(KettleDuty);
if (output != lastoutput) {
digitalWrite(kettlePWM, output);
lastoutput = output;
}
if (lastKettleDuty != KettleDuty || lastKettleSetpoint != KettleSetpoint || lastTemperature != (byte)CurrentTemp) {
menu.update();
lastKettleDuty = KettleDuty;
lastKettleSetpoint = KettleSetpoint;
lastTemperature = (byte)CurrentTemp;
}
}
/*
void startAutoTune() {
if(!tuning) {
//Set the output to the desired starting frequency.
KettleDuty=50;
ControllerPID.SetNoiseBand(1);
ControllerPID.SetOutputStep(30);
ControllerPID.SetLookbackSec(20);
ControllerPID.Mode(AUTOMATIC);
tuning=true;
}
}
void stopAutoTune() {
if(tuning) { //cancel autotune
ControllerPID.Cancel();
ControllerPID.Mode(OFF);
tuning=false;
}
}
void SaveTunings() {
eepromKp = ControllerPID.GetKp();
eepromKi = ControllerPID.GetKi();
eepromKd = ControllerPID.GetKd();
}
void SerialSend()
{
Serial.print("setpoint: ");Serial.print(KettleSetpoint); Serial.print(" ");
Serial.print("input: ");Serial.print(CurrentTemp); Serial.print(" ");
Serial.print("output: ");Serial.print(KettleDuty); Serial.print(" ");
if(tuning){
Serial.println("tuning mode");
} else {
Serial.print("kp: ");Serial.print(ControllerPID.GetKp());Serial.print(" ");
Serial.print("ki: ");Serial.print(ControllerPID.GetKi());Serial.print(" ");
Serial.print("kd: ");Serial.print(ControllerPID.GetKd());Serial.println();
}
}
void SerialReceive()
{
if(Serial.available()) {
String myinput;
myinput = Serial.readString();
myinput.trim();
if(myinput=="start"){
startAutoTune();
} else if(myinput=="stop") {
stopAutoTune();
} else if(myinput=="save") {
SaveTunings();
}
}
}
*/

72
boil_kettle/mqtt.ino Normal file
View File

@ -0,0 +1,72 @@
void ConnectMQTT() {
static const char *password = MQTT_PASSWORD;
static const char *user = MQTT_USER;
Serial.println("connecting MQTT...");
while (!mqtt_client.connect("brewhouse", user, password)) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
mqtt_client.subscribe("brewery/setpoint/bk");
}
void MessageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
/** JSON Parser Setup */
StaticJsonDocument<200> doc;
// Deserialize the JSON document
DeserializationError error = deserializeJson(doc, payload);
// Test if parsing succeeds.
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
char buf[30];
strcpy(buf,TOPIC_PREFIX);
strcat(buf,BOIL_SETPOINT_TOPIC);
if (topic == buf) {
// Update PWM setpoint.
String name = doc["entity"];
String setting = doc["setpoint"];
KettleDuty = setting.toInt();
String unit = doc["units"];
Serial.println("Updating setpoint for " + name + " to " + setting + " " + unit);
}
}
void SetupMQTT(const char *broker) {
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported
// by Arduino. You need to set the IP address directly.
Serial.println("Setup MQTT client.");
mqtt_client.begin(broker, net);
mqtt_client.onMessage(MessageReceived);
ConnectMQTT();
}
static void SendSensorData() {
Serial.println("Sending data...");
// NOTE: max message length is 250 bytes.
StaticJsonDocument<200> doc;
doc["entity"] = "boil_kettle";
doc["setpoint"] = KettleDuty;
doc["units"] = "%";
String jstr;
serializeJson(doc, jstr);
String topic = TOPIC_PREFIX;
topic += "sensor/boil_kettle";
mqtt_client.publish(topic, jstr);
}

View File

@ -17,10 +17,11 @@ class slowPWM {
lastSwitchTime = 0;
outputState = LOW;
pinMode(pin, OUTPUT);
Serial.println("Setup PWM");
}
byte Compute(double duty) {
unsigned long onTime = (duty * period) / 1000;
void compute(byte duty) {
unsigned long onTime = (duty * period) / 100;
unsigned long offTime = period - onTime;
unsigned long currentTime = millis();
@ -34,8 +35,7 @@ class slowPWM {
lastSwitchTime = currentTime;
outputState = HIGH;
}
return outputState;
digitalWrite(outputPin, outputState);
}
};
#endif

@ -1 +0,0 @@
Subproject commit 7787498eda8955288e99a18d02581a425203bac5

View File

@ -1,35 +0,0 @@
#ifndef DATATYPES_h
#define DATATYPES_h
struct Vessel {
char name[16];
char setpoint[20];
char sensor[20];
double Rref;
double RNominal;
};
struct Topic {
char root[10];
Vessel mash;
Vessel boil;
};
struct Mqtt {
IPAddress broker;
char user[10];
char password[21];
Topic topic;
};
struct ConfigData {
Mqtt mqtt;
byte mac[6];
IPAddress ip;
int interval;
int period;
double threshold;
double hysteresis;
};
#endif

View File

@ -1,88 +0,0 @@
#include "../globals.h"
// Interrupt function to run when encoder is turned.
//
// Increases/decreases the kettle output to a max
// of 100% and minimum of 0%.
void doEncoder()
{
uint8_t result = rotary.read();
int inc;
if (result) {
uint8_t speed = rotary.speed();
speed >= 10 ? inc = 5 : inc = 1;
if (result == DIR_CCW) inc = inc * -1;
SettingChanged = true;
if (KettleController.Mode() == MANUAL) {
uint8_t KettleDuty = (uint8_t)KettleController.Power();
KettleDuty = max(0, min((KettleDuty / inc) * inc + inc, 100));
KettleController.Power((double)KettleDuty);
} else if (KettleController.Mode() == AUTOMATIC) {
uint8_t KettleTemp = (uint8_t)KettleController.Setpoint();
KettleTemp = max(0, min((KettleTemp / inc) * inc + inc, 220));
KettleController.Setpoint((double)KettleTemp);
}
} else {
SettingChanged = false;
}
}
// Return a character array to represent the
// state of the kettle.
char* ShowKettleState() {
if (KettleController.Mode() == MANUAL) {
return (char*)"Kettle: Manual";
} else if (KettleController.Mode() == AUTOMATIC) {
return (char*)"Kettle: Auto";
} else {
return (char*)"Kettle: Off";
}
}
char* ShowKettleSetting() {
static char LCD_Line[21];
char setting[4];
if (KettleController.Mode() == MANUAL) {
strcpy(LCD_Line, (char*)"Kettle Power: ");
itoa(KettleController.Power(), setting, 10);
strcat(LCD_Line, setting);
strcat(LCD_Line, (char*)"%");
return LCD_Line;
} else if (KettleController.Mode() == AUTOMATIC) {
strcpy(LCD_Line, (char*)"Kettle Temp: ");
itoa(KettleController.Setpoint(), setting, 10);
strcat(LCD_Line, setting);
strcat(LCD_Line, (char*)"F");
return LCD_Line;
} else {
return (char*)"It's Off stoopid";
}
}
void UpdateBoilKettle(){
if (Enter.pressed()) {
Serial.println("Pressed");
KettleController.CycleMode();
SettingChanged = true;
}
if (SettingChanged) {
menu.update();
SettingChanged = false;
}
if (KettleController.Mode() != OFF) {
// Compute will return false if MANUAL is set so duty will not
// be changed here.
KettleController.Compute();
KettleDuty = KettleController.Power();
digitalWrite(O_PWM, boilPWM.compute(KettleDuty));
} else {
digitalWrite(O_PWM, boilPWM.compute(0));
}
}

View File

@ -1,79 +0,0 @@
#include "../config.h"
#include "../globals.h"
void ConnectMQTT() {
ConfigData config;
EEPROM.get(ConfAddress, config);
static const char *user = config.mqtt.user;
static const char *password = config.mqtt.password;
//config.mqtt.topic.root
//config.mqtt.broker
while (!mqtt_client.connect("brewhouse", user, password)) {
Serial.print(".");
delay(1000);
}
char topic[30];
strcpy(topic,config.mqtt.topic.root);
strcat(topic,config.mqtt.topic.boil.setpoint);
mqtt_client.subscribe(topic);
strcpy(topic,config.mqtt.topic.root);
strcat(topic,config.mqtt.topic.mash.setpoint);
mqtt_client.subscribe(topic);
}
void MessageReceived(char* topic, byte* payload, unsigned int length) {
ConfigData config;
EEPROM.get(ConfAddress, config);
char buf[30];
strcpy(buf,config.mqtt.topic.root);
strcat(buf,config.mqtt.topic.boil.setpoint);
char msg[length+1];
for (int i=0;i<length;i++) {
msg[i] = (char)payload[i];
}
msg[length] = 0;
if (strcmp(topic, buf) == 0) {
cJSON *monitor_json = cJSON_Parse(msg);
const cJSON *name = NULL;
const cJSON *setting = NULL;
const cJSON *unit = NULL;
// Update PWM setpoint.
setting = cJSON_GetObjectItemCaseSensitive(monitor_json, "setpoint");
KettleDuty = setting->valueint;
cJSON_Delete(monitor_json);
}
}
static void SendSensorData() {
ConfigData config;
EEPROM.get(ConfAddress, config);
char *string = NULL;
cJSON *entity = NULL;
cJSON *setpoint = NULL;
cJSON *units = NULL;
cJSON *monitor = cJSON_CreateObject();
cJSON_AddStringToObject(monitor, "entity", config.mqtt.topic.boil.name);
cJSON_AddNumberToObject(monitor, "setpoint", KettleDuty);
cJSON_AddStringToObject(monitor, "units", "%");
char *msg = cJSON_Print(monitor);
char topic[30];
strcpy(topic,config.mqtt.topic.root);
strcat(topic,config.mqtt.topic.boil.sensor);
mqtt_client.publish(topic, msg);
cJSON_Delete(monitor);
}

View File

@ -1,82 +0,0 @@
#include <Arduino.h>
#include <Adafruit_MAX31865.h>
#include "thermoControl.h"
thermoControl::thermoControl(double* current_temp, double* setpoint, double* power, modes mode) {
outMax = 100;
outMin = 10;
OpMode = mode;
SampleInterval = 100;
lastTime = millis()-SampleInterval;
output_pwm = power;
control_temp = setpoint;
actual_temp = current_temp;
hysteresis = 1.0;
max_pwr_threshold = 5.0;
Serial.println("Controller Started");
}
bool thermoControl::Compute() {
unsigned long now = millis();
unsigned long timeChange = (now - lastTime);
if(timeChange >= SampleInterval && OpMode == AUTOMATIC) {
double error = *control_temp - *actual_temp;
if (error >= max_pwr_threshold) {
*output_pwm = outMax;
} else if (error > hysteresis) {
*output_pwm = 100 * error / max_pwr_threshold;
if (*output_pwm > 100) *output_pwm = 100;
if (*output_pwm < 0) *output_pwm = 0;
} else {
*output_pwm = 0;
}
lastTime = now;
return true;
} else {
return false;
}
}
void thermoControl::SampleTime(int NewSampleTime) {
if (NewSampleTime > 0) {
SampleInterval = NewSampleTime;
}
}
void thermoControl::PowerLimits(double Max, double Min) {
if(Min >= Max) return;
outMax = Max;
outMin = Min;
}
void thermoControl::Hysteresis(double hys) {
hysteresis = hys;
}
void thermoControl::ThreshPWR(double thresh) {
max_pwr_threshold = thresh;
}
void thermoControl::Mode(modes newMode) {
Serial.println(newMode);
OpMode = newMode;
}
modes thermoControl::Mode() {
return OpMode;
}
modes thermoControl::CycleMode() {
if (OpMode + 1 == OVERFLOW) {
OpMode = (modes)(0);
} else {
OpMode = (modes)(OpMode + 1);
}
return OpMode;
}

View File

@ -1,41 +0,0 @@
#ifndef THERMOCONTROL_h
#define THERMOCONTROL_h
#include <Adafruit_MAX31865.h>
enum modes : uint8_t {OFF, AUTOMATIC, MANUAL, OVERFLOW};
class thermoControl {
private:
double *output_pwm;
double *control_temp;
double * actual_temp;
double current_temp;
double hysteresis;
double max_pwr_threshold;
int outMax;
int outMin;
int SampleInterval;
unsigned long lastTime;
modes OpMode;
public:
thermoControl(double*, double*, double*, modes);
void nope();
bool Compute();
double Power();
void Power(double);
double Setpoint();
void Setpoint(double);
void SampleTime(int);
void PowerLimits(double, double);
void Hysteresis(double);
void Hysteresis();
void ThreshPWR(double);
void ThreshPWR();
void Mode(modes);
modes Mode();
modes CycleMode();
};
#endif

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1,11 +0,0 @@
// Pin Definitions
#define encoderCLK 2
#define encoderDT 3
#define encoderBTN 4
#define kettlePWM 5
#define kettleRTDCS 47
#define mashRTDCS 48
LCD display:
SDA & SCL pins are 20 & 21

View File

@ -1 +0,0 @@
*.h

View File

@ -1,49 +0,0 @@
#include <EEPROM.h>
#include <Ethernet.h>
#include "config.h" // Symlinked from boil_kettle sketch
#include "datatypes.h" //
void setup() {
Vessel BoilKettle;
strcpy(BoilKettle.name, "boil_kettle");
strcpy(BoilKettle.setpoint, BOIL_SETPOINT_TOPIC);
strcpy(BoilKettle.sensor, BOIL_ACTUAL_TOPIC);
BoilKettle.Rref = RREF_KETTLE;
BoilKettle.RNominal = RNOMINAL_KETTLE;
// Vessel MashTun {mashname, MASH_SETPOINT_TOPIC, MASH_ACTUAL_TOPIC, RREF_MASH, RNOMINAL_MASH};
Vessel MashTun;
strcpy(MashTun.name, "mash_tun");
strcpy(MashTun.setpoint, MASH_SETPOINT_TOPIC);
strcpy(MashTun.sensor, MASH_ACTUAL_TOPIC);
MashTun.Rref = RREF_MASH;
MashTun.RNominal = RNOMINAL_MASH;
Topic mqtt_topics;
strcpy(mqtt_topics.root,TOPIC_ROOT);
mqtt_topics.mash = MashTun;
mqtt_topics.boil = BoilKettle;
Mqtt mqtt_data;
mqtt_data.broker = MQTT_BROKER;
strcpy(mqtt_data.user, MQTT_USER);
strcpy(mqtt_data.password, MQTT_PASSWORD);
mqtt_data.topic = mqtt_topics;
ConfigData conf;
conf.mqtt = mqtt_data;
memcpy(conf.mac, mac, sizeof(mac[0])*6);
conf.ip = ip;
conf.interval = UpdateInterval;
conf.period = PeriodPWM;
conf.threshold = ThreshPWR;
conf.hysteresis = Hysteresis;
EEPROM.put(0, conf);
}
void loop() {
}

View File

@ -1,25 +1,22 @@
alias: Send iSpindel
description: ""
trigger:
- platform: state
entity_id: sensor.ispindel001_gravity,sensor.ispindel002_gravity
condition: []
action:
- service: rest_command.send_ispindel
- alias: Set BK Input Value
trigger:
platform: mqtt
topic: "brewery/sensor/boil_kettle"
action:
service: input_select.select_option
data:
entity_id: input_select.boil_kettle_pwm
option: "{{ trigger.payload.setpoint }}"
# This automation script runs when the thermostat mode selector is changed.
# It publishes its value to the same MQTT topic it is also subscribed to.
- alias: Set BK PWM
trigger:
platform: state
entity_id: input_select.boil_kettle_pwm
action:
service: mqtt.publish
data:
name: "{{ device_attr(device_id(trigger.entity_id), \"name\") }}"
temp: >-
{% macro sensor(n,s) -%}sensor.{{ device_attr(device_id(n), "name") |
lower }}_{{s}}{%- endmacro %}
{{states(sensor(trigger.entity_id,"temperature"))}}
temp_unit: C
gravity: >-
{% macro sensor(n,s) -%}sensor.{{ device_attr(device_id(n), "name") |
lower }}_{{s}}{%- endmacro %}
{{states(sensor(trigger.entity_id,"gravity"))}}
gravity_unit: P
battery: >-
{% macro sensor(n,s) -%}sensor.{{ device_attr(device_id(n), "name") |
lower }}_{{s}}{%- endmacro %}
{{states(sensor(trigger.entity_id,"battery_voltage"))}}
mode: single
topic: "brewery/setpoint/bk"
retain: true
payload: "setpoint: {{ states('input_select.thermostat_mode') }}"

View File

@ -1,5 +0,0 @@
send_ispindel:
url: !secret BrewfatherEndpoint
method: POST
content_type: "application/json"
payload: '{ "name":"{{ name }}", "temp":{{ temp }}, "temp_unit":"F", "gravity":{{ gravity }}, "gravity_unit": "P", "battery": {{ battery }} }'

View File

@ -1 +0,0 @@
config.h

View File

@ -1,13 +0,0 @@
# Temperature Control
Enables basic thermostat control for fermentation, kegerator, etc.
## Requirements
Arduino Nano (clone) with ethernet shield.
For programing the board:
- Board : Arduino Nano
- Processor : ATmega328P
- Port : /dev/ttyUSB0
This board/shield combo works with the [EthernetENC](https://github.com/jandrassy/EthernetENC/wiki) library.

View File

@ -1,53 +0,0 @@
/* Built-in */
#include <SPI.h>
/* Extra Libraries */
#include <EthernetENC.h>
/* Local includes */
#include "config.h"
// Set 10 minute compressor delay if not otherwise defined.
#ifndef COMP_DELAY
#define COMP_DELAY 600000
#endif
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 1, 177);
IPAddress myDns(192, 168, 1, 1);
// Initialize the Ethernet client library
// with the IP address and port of the server
// that you want to connect to (port 80 is default for HTTP):
EthernetClient client;
void setup() {
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
// start the Ethernet connection:
Serial.println("Initialize Ethernet with DHCP:");
if (Ethernet.begin(mac) == 0) {
Serial.println("Failed to configure Ethernet using DHCP");
// Check for Ethernet hardware present
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :(");
while (true) {
delay(1); // do nothing, no point running without Ethernet hardware
}
}
if (Ethernet.linkStatus() == LinkOFF) {
Serial.println("Ethernet cable is not connected.");
}
// try to congifure using IP address instead of DHCP:
Ethernet.begin(mac, ip, myDns);
} else {
Serial.print(" DHCP assigned IP ");
Serial.println(Ethernet.localIP());
}
}
void loop() {
}