Merging in current state of code. #3

Merged
cgiacofei merged 8 commits from dev into master 2023-01-27 11:06:22 -05:00
16 changed files with 356 additions and 746 deletions

3
.gitignore vendored
View File

@ -1,5 +1,5 @@
.DS_Store
bin/*/
bin/*
*build-*
*build*
*~
@ -9,4 +9,5 @@ doc/
debug.txt
*.geany
*.tags
tags
*.bin

View File

@ -17,6 +17,8 @@ TARGET_DIR := $(BASE_DIR)/$(subst :,.,build/$(FQBN))
BIN_DIR := $(BASE_DIR)/bin
SRC := $(wildcard $(SKETCH_DIR)/*.ino)
HDRS := $(wildcard $(SKETCH_DIR)/*.h)
LIBH := $(wildcard $(BASE_DIR)/lib/**/*.h)
LIBS := $(LIBH) $(wildcard $(BASE_DIR)/lib/**/*.cpp)
BIN := $(notdir $(SRC)).bin
$(info FQBN is [${FQBN}])
@ -36,7 +38,7 @@ all: $(BIN_DIR)/$(BIN) upload
compile: $(BIN_DIR)/$(BIN)
upload: $(BIN_DIR)/$(BIN)
$(ESPTOOL) -v -cd nodemcu -b 115200 -p $(MONITOR_PORT) -ca 0x00000 -cf $(TARGET_DIR)/$(SKETCH).bin
$(ESPTOOL) --port $(MONITOR_PORT) --chip esp8266 --baud 460800 write_flash -fm dio -fs 16MB 0x0 $(TARGET_DIR)/$(SKETCH).bin
ota: $(BIN_DIR)/$(BIN)
$(ESPOTA) -d -r -i $(ESP_IP) -I $(OTA_SERVER) -p 8266 -P 8266 -a $(OTA_PASSWD) -f $(TARGET_DIR)/$(SKETCH).bin
@ -59,7 +61,7 @@ new-library:
.PHONY: all compile test library upload ota clean monitor
$(TARGET_DIR)/$(BIN): $(SRC) $(HDRS)
$(TARGET_DIR)/$(BIN): $(SRC) $(HDRS) $(LIBS)
@ echo $(FQBN)
@ mkdir -p $(TARGET_DIR)
@ -73,4 +75,4 @@ $(TARGET_DIR)/$(BIN): $(SRC) $(HDRS)
$(BIN_DIR)/$(BIN): $(TARGET_DIR)/$(BIN)
@ echo "$(BIN) $(BIN_DIR)/"
@ mv $(TARGET_DIR)/$(BIN) $(BIN_DIR)/
@ cp $(TARGET_DIR)/$(BIN) $(BIN_DIR)/

Binary file not shown.

View File

@ -1,11 +1,20 @@
#include "communicator.h"
#include "Communicator.h"
Communicator::Communicator(WiFiClient& network, void (*cmd)(char *topic, byte *payload, unsigned int length)) {
Communicator::Communicator(WiFiClient& network, CALLBACK_SIGNATURE) {
this->_net = network;
this->mqttCallback = cmd;
this->mqttCallback = mqttCallback;
this->_mqtt_client.setBufferSize(1536);
}
Communicator::Communicator(WiFiClient& network) {
this->_net = network;
this->_mqtt_client.setBufferSize(1536);
}
void Communicator::connectCallback(CALLBACK_SIGNATURE) {
this->mqttCallback = mqttCallback;
}
void Communicator::loop() {
_mqtt_client.loop();
}
@ -80,6 +89,11 @@ bool Communicator::ConnectMQTT(const String &server, const String &name, const S
}
void Communicator::Subscribe(const char* topic) {
Serial.println("Subscribe to: " + String(topic));
_mqtt_client.subscribe(topic);
}
void Communicator::mqtt_discovery(const String topic, StaticJsonDocument<1536> &entity) {
String payload;
serializeJson(entity, payload);
@ -89,21 +103,31 @@ void Communicator::mqtt_discovery(const String topic, StaticJsonDocument<1536> &
{
Serial.print("Sending discovery payload to ");
Serial.println(topic);
Serial.println("");
Serial.println(payload);
Serial.println("");
_mqtt_client.publish(topic.c_str(), payload.c_str(), false);
if (entity.containsKey("mode_cmd_t")) _mqtt_client.subscribe(entity["mode_cmd_t"]);
if (entity.containsKey("temp_cmd_t")) _mqtt_client.subscribe(entity["temp_cmd_t"]);
if (entity.containsKey("temp_hi_cmd_t")) _mqtt_client.subscribe(entity["temp_hi_cmd_t"]);
if (entity.containsKey("temp_lo_cmd_t")) _mqtt_client.subscribe(entity["temp_lo_cmd_t"]);
const char* mode = entity["mode_cmd_t"];
if (mode) {
Subscribe(mode);
}
const char* temp = entity["temp_cmd_t"];
if (temp) {
Subscribe(temp);
}
const char* temp_hi = entity["temp_hi_cmd_t"];
if (temp_hi) {
Subscribe(temp_hi);
}
const char* temp_lo = entity["temp_lo_cmd_t"];
if (temp_lo) {
Subscribe(temp_lo);
}
_mqtt_client.loop();
}
}
void Communicator::publish_data(String topic, String value) {
_mqtt_client.publish(topic.c_str(), value.c_str(), false);
_mqtt_client.publish(topic.c_str(), value.c_str(), true);
}

View File

@ -0,0 +1,31 @@
#ifndef MyClass_h
#define MyClass_h
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <Global.h>
#include <secrets.h>
#define CALLBACK_SIGNATURE void (*mqttCallback)(char *topic, byte *payload, unsigned int length)
class Communicator {
public:
Communicator(WiFiClient&);
Communicator(WiFiClient&, CALLBACK_SIGNATURE);
CALLBACK_SIGNATURE;
void connectCallback(CALLBACK_SIGNATURE);
bool ConnectMQTT(const String&, const String&, const String&, const String&);
void mqtt_discovery(const String, StaticJsonDocument<1536>&);
void publish_data(String topic, String value);
void publish_data(String topic, int value);
void Subscribe(const char* topic);
void loop();
private:
PubSubClient _mqtt_client;
WiFiClient _net;
};
#endif

View File

@ -1,25 +0,0 @@
#ifndef MyClass_h
#define MyClass_h
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include "secrets.h"
class Communicator {
public:
Communicator(WiFiClient&, void (*mqttCallback)(char*, byte*, unsigned int));
void (*mqttCallback)(char*, byte*, unsigned int);
bool ConnectMQTT(const String&, const String&, const String&, const String&);
void mqtt_discovery(const String, StaticJsonDocument<1536>&);
void publish_data(String, String);
void loop();
private:
PubSubClient _mqtt_client;
WiFiClient _net;
};
#endif

View File

@ -1,358 +0,0 @@
#include "Device.h"
Device::Device(const uint8_t _pin_cool, const uint8_t _pin_heat, const uint8_t _pin_wire) {
this->_pin_cool = _pin_cool;
this->_pin_heat = _pin_heat;
this->_pin_wire = _pin_wire;
}
void Device::CoolSet(bool set_state) {
// Set pin state
if (set_state) {
Serial.println("Starting cooling.");
strcpy(_curr_action, ACTION_COOLING);
SendState(ACTION_TPC, _curr_action);
digitalWrite(_pin_heat, LOW); // Just to be sure.
digitalWrite(_pin_cool, HIGH);
} else {
Serial.println("Stopping cooling.");
strcpy(_curr_action, ACTION_IDLE);
SendState(ACTION_TPC, _curr_action);
digitalWrite(_pin_cool, LOW);
digitalWrite(_pin_heat, LOW);
}
}
void Device::HeatSet(bool set_state) {
if (set_state) {
Serial.println("Starting heat.");
strcpy(_curr_action, ACTION_HEATING);
SendState(ACTION_TPC, _curr_action);
digitalWrite(_pin_cool, LOW); // Just to be sure.
digitalWrite(_pin_heat, HIGH);
} else {
Serial.println("Stopping heat.");
strcpy(_curr_action, ACTION_IDLE);
SendState(ACTION_TPC, _curr_action);
digitalWrite(_pin_cool, LOW);
digitalWrite(_pin_heat, LOW);
}
}
void Device::Hyst(float new_value){
_hyst = new_value;
}
float Device::Hyst(){
return _hyst;
}
void Device::Update(){
float currentMillis = millis();
//Read temp sensor.
if (currentMillis - _lastSensor >= _sensor_period) {
//Read temperature from DS18b20
float tempC = _sensors.getTempC(_ds_serial);
if (tempC == DEVICE_DISCONNECTED_C) {
Serial.println("Error: Could not read temperature data");
} else {
_curr_temp = DallasTemperature::toFahrenheit(tempC);
}
}
// Some helpful variables.
bool heating = _curr_action==ACTION_HEATING;
bool cooling = _curr_action==ACTION_COOLING;
bool running = (heating || cooling);
// Adjust cool/heat on or off
if (_mode == "cool"){
if (_curr_temp > _temp + _hyst && !cooling) {
CoolSet(true);
} else if (_curr_temp <= _temp && cooling) {
CoolSet(false);
}
} else if (_mode == "heat"){
if (_curr_temp < _temp - _hyst && !heating) {
HeatSet(true);
} else if (_curr_temp >= _temp && heating) {
HeatSet(false);
}
} else if (_mode == "auto"){
if ((_curr_temp < _temp_lo - _hyst) && !heating) {
HeatSet(true);
} else if ((_curr_temp > _temp_hi + _hyst) && !cooling) {
CoolSet(true);
} else if (running && (_curr_temp >= _temp_lo) && (_curr_temp <= _temp_hi)) {
if (heating) HeatSet(false);
if (cooling) CoolSet(false);
}
} else {
// IS OFF
if (heating) HeatSet(false);
if (cooling) CoolSet(false);
}
//Send Data to broker
if (currentMillis - _lastSend >= _send_period) {
// Time's up, send data
char temp[7];
dtostrf(_curr_temp, 6, 2, temp);
SendState(TEMP_CURRENT, temp);
_lastSend = currentMillis;
}
_mqtt_client.loop();
}
void Device::AttachNet(WiFiClient &network) {
this->_net = network;
this->_mqtt_client.setClient(_net);
this->_mqtt_client.setBufferSize(DOC_SIZE);
}
void Device::AttachSensor(DallasTemperature &sensors, uint8_t serial[8]){
_sensors = sensors;
_ds_serial = serial;
}
//void Device::topicRoot(const String &root) { _device_prefix = root; }
//byte* Device::topicRoot() { return _device_prefix; }
void Device::SendConfig(char* broker, char* name, String &chipid, bool multimode) {
String name_slug = slugify(name);
String CMD_TPL = "{{ value }}";
String STAT_TPL = "{{ value_json }}";
_topic_root = ROOT + name_slug;
String _config_topic = CONFIG_ROOT + name_slug + "_" + chipid + "/config";
StaticJsonDocument<DOC_SIZE> payload_doc;
payload_doc["uniq_id"] = chipid + "_" + name_slug;
payload_doc["name"] = name;
payload_doc["temp_unit"] = "F";
payload_doc["max_temp"] = _max_temp;
payload_doc["min_temp"] = _min_temp;
payload_doc["initial"] = _init_temp;
// Action Topic
payload_doc["action_topic"] = _topic_root + ACTION_TPC;
payload_doc["action_template"] = STAT_TPL;
// Mode setup
payload_doc["mode_cmd_t"] = _topic_root + MODE_SET;
payload_doc["mode_cmd_tpl"] = CMD_TPL;
payload_doc["mode_stat_t"] = _topic_root + MODE_STATE;
payload_doc["mode_stat_tpl"] = STAT_TPL;
JsonArray modes = payload_doc.createNestedArray("modes");
modes.add("off");
modes.add("cool");
payload_doc["temp_cmd_t"] = _topic_root + TEMP_SET;
payload_doc["temp_cmd_tpl"] = CMD_TPL;
payload_doc["temp_stat_t"] = _topic_root + TEMP_STATE;
payload_doc["temp_stat_tpl"] = STAT_TPL;
payload_doc["curr_temp_t"] = _topic_root + TEMP_CURRENT;
payload_doc["curr_temp_tpl"] = CMD_TPL;
if (multimode) {
payload_doc["temp_hi_cmd_t"] = _topic_root + TEMP_HI_SET;
payload_doc["temp_hi_cmd_tpl"] = CMD_TPL;
payload_doc["temp_hi_stat_t"] = _topic_root + TEMP_HI_STATE;
payload_doc["temp_hi_stat_tpl"] = STAT_TPL;
payload_doc["temp_lo_cmd_t"] = _topic_root + TEMP_LO_SET;
payload_doc["temp_lo_cmd_tpl"] = CMD_TPL;
payload_doc["temp_lo_stat_t"] = _topic_root + TEMP_LO_STATE;
payload_doc["temp_lo_stat_tpl"] = STAT_TPL;
modes.add("heat");
modes.add("auto");
}
// Attach Device
JsonObject dev = payload_doc.createNestedObject("dev");
dev["name"] = DEVICE_NAME;
dev["mdl"] = DEVICE_MDL;
dev["sw"] = String(version);
dev["mf"] = DEVICE_MF;
JsonArray ids = dev.createNestedArray("ids");
ids.add(chipid);
String payload;
serializeJson(payload_doc, payload);
bool response = ConnectMQTT(broker, MQTT_NAME, MQTT_USER, MQTT_PASSWORD);
if (response) {
_mqtt_client.publish(_config_topic.c_str(), payload.c_str(), MSG_RETAIN);
_mqtt_client.loop();
}
_mqtt_client.subscribe((_topic_root + MODE_SET).c_str());
_mqtt_client.subscribe((_topic_root + TEMP_SET).c_str());
if (multimode) {
_mqtt_client.subscribe((_topic_root + TEMP_HI_SET).c_str());
_mqtt_client.subscribe((_topic_root + TEMP_LO_SET).c_str());
}
_mqtt_client.loop();
}
void Device::_Temp(byte value){
Serial.print("Set Temp");
_temp = value;
SendState(TEMP_STATE, (char*)value);
}
void Device::_Mode(char* value){
Serial.print("Set Mode");
strcpy(_mode, value);
SendState(MODE_STATE, (char*)value);
}
void Device::_TempHi(byte value){
Serial.print("Set High Temp");
_temp_hi = value;
SendState(TEMP_HI_STATE, (char*)value);
}
void Device::_TempLo(byte value){
Serial.print("Set Low Temp");
_temp_lo = value;
SendState(TEMP_LO_STATE, (char*)value);
}
void Device::SendState(String suffix, String payload){
String topic = _topic_root + suffix;
_mqtt_client.publish(topic.c_str(), payload.c_str(), MSG_RETAIN);
}
/** Callback function for MQTT client.
Looks up a command function based on topic and executes it.
@param topic
@param payload
@param length
*/
void Device::_mqttCallback(char *topic, uint8_t *payload, unsigned int length) {
char data [16] = {'\0'};
// Remove root from the incoming topic.
char suffix[100] = topic[_topic_root.length()];
Serial.print("Incoming topic -> ");
Serial.print(suffix);
Serial.print(": ");
int i;
for (i; i < length; i++)
{
data[i] = ((char)payload[i]);
Serial.print(data[i]);
}
data[i] = '\0';
switch (suffix) {
case MODE_SET:
_Mode(data);
break;
case TEMP_SET:
_Temp(atoi(data));
break;
case TEMP_LO_SET:
_TempLo(atoi(data));
break;
case TEMP_HI_SET:
_TempHi(atoi(data));
break;
default:
Serial.println("Command function not found for " + String(topic));
}
}
/** Connect to MQTT broker.
@param server IP address string of the server.
@param name the name used for this connection
@param user MQTT broker username
@param password MQTT broker password
@return boolean indicating success or failure of connection.
*/
bool Device::ConnectMQTT(const String &server, const String &name, const String &user, const String &password) {
_mqtt_client.setServer((server.c_str(), 1883);
_mqtt_client.setCallback(&Device::_mqttCallback);
byte i = 0;
if (_mqtt_client.connected()) return true;
while (!_mqtt_client.connected() && (i < 3)) {
Serial.println("Attempt MQTT Connection.");
boolean ret;
ret = _mqtt_client.connect(name.c_str(), user.c_str(), password.c_str());
if (ret) {
Serial.println("Connected to MQTT");
return true;
} else {
int Status = _mqtt_client.state();
switch (Status)
{
case -4:
Serial.println(F("Connection timeout"));
break;
case -3:
Serial.println(F("Connection lost"));
break;
case -2:
Serial.println(F("Connect failed"));
break;
case -1:
Serial.println(F("Disconnected"));
break;
case 1:
Serial.println(F("Bad protocol"));
break;
case 2:
Serial.println(F("Bad client ID"));
break;
case 3:
Serial.println(F("Unavailable"));
break;
case 4:
Serial.println(F("Bad credentials"));
break;
case 5:
Serial.println(F("Unauthorized"));
break;
}
}
Serial.print(".");
i++;
delay(5000);
}
return false;
}

View File

@ -14,6 +14,8 @@ const char version[] = "0.0.1";
#define ACTION_TPC "action/state"
#define MODE_SET "mode/set"
#define MODE_STATE "mode/state"
#define SWING_STATE "swing/state"
#define SWING_SET "swing/set"
#define TEMP_SET "temp/set"
#define TEMP_STATE "temp/state"
#define TEMP_CURRENT "temp/current"
@ -53,29 +55,129 @@ const char version[] = "0.0.1";
#define TOPIC_LIMIT 4
//COMMAND_SIGNATURE void (*commandFunction)(uint8_t*) // https://forum.arduino.cc/t/assignment-of-function/528949/3
//CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
//~ #define OFF 0
//~ #define COOL 1
//~ #define HEAT 2
//~ #define AUTO 3
const size_t DOC_SIZE = JSON_OBJECT_SIZE(29) + JSON_ARRAY_SIZE(4);
#define TEMP 0
#define TEMP_HI 1
#define TEMP_LO 2
uint8_t CHILLER_SP = 70;
uint8_t FERMA_SP = 70;
uint8_t FERMB_SP = 70;
//~ typedef void (*ptr)(uint8_t*, uint8_t*);
//typedef ptr (*pm)();
struct CommandTopic {
void (*cmd)(byte*);
//~ ptr CmdFunc;
byte* Setting;
String CmdTopic;
};
struct Mode {
String CmdTopic;
String StateTopic;
String Setting;
};
struct SetPoint {
String CmdTopic;
String StateTopic;
int Setting;
};
struct ControlDevice {
String name; // "Glycol Chiller"
String topic_root; // "brewhouse/"
CommandTopic command_topics[TOPIC_LIMIT];
String current_temp_topic;
byte temp_sensor[8];
String action_topic;
String action_state;
String swing_topic;
String swing_state;
byte heat_pin;
byte cool_pin;
Mode mode;
SetPoint setpoints[3];
bool dual;
};
void (*cmd)(byte*) CmdLookup(String command_topic, ControlDevice devices[], int device_count) {
for (int i=0;i<device_count;i++) {
ControlDevice this_device = devices[i];
for (int j=0;j<TOPIC_LIMIT;j++) {
if (this_device.command_topics[j]->CmdTopic == command_topic) return this_device.command_topics[j]->cmd;
}
float UpdateTemperature(DallasTemperature *sensors, DeviceAddress glycol_tank) {
// method 2 - faster
float tempC = sensors->getTempC(glycol_tank);
if(tempC == DEVICE_DISCONNECTED_C)
{
Serial.println("Error: Could not read temperature data");
return 0;
}
return DallasTemperature::toFahrenheit(tempC);
}
bool DeviceLoop(ControlDevice &device, DallasTemperature *sensors) {
Serial.println(device.name);
float dsTemp = UpdateTemperature(sensors, device.temp_sensor);
String initial_action = device.action_state;
if (device.mode.Setting == "auto") {
int setHi = device.setpoints[TEMP_HI].Setting;
int setLo = device.setpoints[TEMP_LO].Setting;
if ((setLo <= dsTemp) && (dsTemp <= setHi)) { // Within Temp Range
device.action_state = ACTION_IDLE;
digitalWrite(device.cool_pin, LOW);
digitalWrite(device.heat_pin, LOW);
} else if (dsTemp < setLo) { // Needs Heat
device.action_state = ACTION_HEATING;
digitalWrite(device.cool_pin, LOW);
digitalWrite(device.heat_pin, HIGH);
} else if (dsTemp > setHi) { // Needs Cooling
device.action_state = ACTION_COOLING;
digitalWrite(device.cool_pin, HIGH);
digitalWrite(device.heat_pin, LOW);
}
}
int setTemp = device.setpoints[TEMP].Setting;
if (device.mode.Setting == "cool") {
if (dsTemp > setTemp) { // Needs Cooling
device.action_state = ACTION_COOLING;
digitalWrite(device.cool_pin, HIGH);
digitalWrite(device.heat_pin, LOW);
} else {
device.action_state = ACTION_IDLE;
digitalWrite(device.cool_pin, LOW);
digitalWrite(device.heat_pin, LOW);
}
}
if (device.mode.Setting == "heat") {
if (dsTemp < setTemp) { // Needs Cooling
device.action_state = ACTION_HEATING;
digitalWrite(device.cool_pin, LOW);
digitalWrite(device.heat_pin, HIGH);
} else {
device.action_state = ACTION_IDLE;
digitalWrite(device.cool_pin, LOW);
digitalWrite(device.heat_pin, LOW);
}
}
if (device.mode.Setting == "off") {
device.action_state = ACTION_IDLE;
}
if (initial_action != device.action_state) {
return true;
}
return false;
}
#endif

View File

@ -1,11 +0,0 @@
TOPIC_ROOT + suffix;
TOPIC_ROOT + suffix;
TOPIC_ROOT + "mode/set";
TOPIC_ROOT + "mode/state";
TOPIC_ROOT + "temp/set";
TOPIC_ROOT + "temp/state";
TOPIC_ROOT + "temp/current";
TOPIC_ROOT + "temp_hi/set";
TOPIC_ROOT + "temp_hi/state";
TOPIC_ROOT + "temp_lo/set";
TOPIC_ROOT + "temp_lo/state";

View File

@ -7,11 +7,17 @@ source repository.
#define GLOBAL_H
#define DEVICE_SW "0.0.1"
auto chipid = String(ESP.getChipId(), HEX);
#define DEVICE_NAME "Glycol Chiller"
#define DEVICE_MDL "Chillenator v0.1"
#define DEVICE_MF "Damn Yankee Brewing"
#define FERMENTER_COUNT 2
#define ONE_WIRE_BUS D2
#define GLYCOL_RELAY D3
#define FERMA_RELAY D4
#define FERMB_RELAY D5
const size_t DOC_SIZE = JSON_OBJECT_SIZE(29) + JSON_ARRAY_SIZE(4);
#endif

View File

@ -1,109 +0,0 @@
#include "communicator.h"
Communicator::Communicator(WiFiClient& network, void (*cmd)(char *topic, byte *payload, unsigned int length)) {
this->_net = network;
this->mqttCallback = cmd;
this->_mqtt_client.setBufferSize(1536);
}
void Communicator::loop() {
_mqtt_client.loop();
}
bool Communicator::ConnectMQTT(const String &server, const String &name, const String &user, const String &password) {
_mqtt_client.setClient(_net);
_mqtt_client.setServer(server.c_str(), 1883);
_mqtt_client.setCallback(mqttCallback);
byte i = 0;
if (_mqtt_client.connected()) return true;
while (!_mqtt_client.connected() && (i < 3)) {
Serial.println("Attempt MQTT Connection.");
boolean ret;
ret = _mqtt_client.connect(name.c_str(), user.c_str(), password.c_str());
if (ret) {
Serial.println("Connected to MQTT");
return true;
} else {
int Status = _mqtt_client.state();
switch (Status)
{
case -4:
Serial.println(F("Connection timeout"));
break;
case -3:
Serial.println(F("Connection lost"));
break;
case -2:
Serial.println(F("Connect failed"));
break;
case -1:
Serial.println(F("Disconnected"));
break;
case 1:
Serial.println(F("Bad protocol"));
break;
case 2:
Serial.println(F("Bad client ID"));
break;
case 3:
Serial.println(F("Unavailable"));
break;
case 4:
Serial.println(F("Bad credentials"));
break;
case 5:
Serial.println(F("Unauthorized"));
break;
}
}
Serial.print(".");
i++;
delay(5000);
}
return false;
}
void Communicator::mqtt_discovery(const String topic, StaticJsonDocument<1536> &entity) {
String payload;
serializeJson(entity, payload);
bool response = ConnectMQTT(MQTT_BROKER.toString(), MQTT_NAME, MQTT_USER, MQTT_PASSWORD);
if (response)
{
Serial.print("Sending discovery payload to ");
Serial.println(topic);
Serial.println("");
Serial.println(payload);
Serial.println("");
_mqtt_client.publish(topic.c_str(), payload.c_str(), false);
if (entity.containsKey("mode_cmd_t")) _mqtt_client.subscribe(entity["mode_cmd_t"]);
if (entity.containsKey("temp_cmd_t")) _mqtt_client.subscribe(entity["temp_cmd_t"]);
if (entity.containsKey("temp_hi_cmd_t")) _mqtt_client.subscribe(entity["temp_hi_cmd_t"]);
if (entity.containsKey("temp_lo_cmd_t")) _mqtt_client.subscribe(entity["temp_lo_cmd_t"]);
_mqtt_client.loop();
}
}
void Communicator::publish_data(String topic, String value) {
_mqtt_client.publish(topic.c_str(), value.c_str(), false);
}

View File

@ -1,25 +0,0 @@
#ifndef MyClass_h
#define MyClass_h
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include "secrets.h"
class Communicator {
public:
Communicator(WiFiClient&, void (*mqttCallback)(char*, byte*, unsigned int));
void (*mqttCallback)(char*, byte*, unsigned int);
bool ConnectMQTT(const String&, const String&, const String&, const String&);
void mqtt_discovery(const String, StaticJsonDocument<1536>&);
void publish_data(String, String);
void loop();
private:
PubSubClient _mqtt_client;
WiFiClient _net;
};
#endif

View File

@ -1,17 +0,0 @@
/* This file is all the stuff I want to be
able to change without having to update the
source repository.
*/
#ifndef GLOBAL_H
#define GLOBAL_H
#define DEVICE_SW "0.0.1"
auto chipid = String(ESP.getChipId(), HEX);
#define DEVICE_NAME "Glycol Chiller"
#define DEVICE_MDL "Chillenator v0.1"
#define DEVICE_MF "Damn Yankee Brewing"
#define FERMENTER_COUNT 2
#endif

View File

@ -1,42 +0,0 @@
#ifndef SLOWPWM_h
#define SLOWPWM_h
#include <Arduino.h>
class slowPWM {
private:
byte outputPin;
unsigned long period;
unsigned long lastSwitchTime;
byte outputState;
public:
void begin(byte pin, unsigned long per) {
outputPin = pin;
period = per;
lastSwitchTime = 0;
outputState = LOW;
pinMode(pin, OUTPUT);
Serial.println("Setup PWM");
}
byte compute(byte duty) {
unsigned long onTime = (duty * period) / 100;
unsigned long offTime = period - onTime;
unsigned long currentTime = millis();
if (duty == 0) {
outputState = LOW;
} else if (outputState == HIGH && (currentTime - lastSwitchTime >= onTime)) {
lastSwitchTime = currentTime;
outputState = LOW;
} else if (outputState == LOW && (currentTime - lastSwitchTime >= offTime)) {
lastSwitchTime = currentTime;
outputState = HIGH;
}
return outputState;
}
};
#endif

View File

@ -1,33 +1,68 @@
//#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <IPAddress.h>
#include <DallasTemperature.h>
// My Libraries
#include <secrets.h>
#include <global.h>
#include <communicator.h>
String chiller_state = "idle";
int tank_setpoint = 28;
#include <Global.h>
#include <Communicator.h>
#include <Device.h>
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
WiFiClient net;
byte glycol_ds18b20[8] = {0x28,0xFF,0x64,0x0E,0x7F,0x57,0x09,0x66};
byte fermA_ds18b20[8] = {0x28,0xFF,0x64,0x0E,0x7F,0x55,0x1C,0x55};
byte fermB_ds18b20[8] = {0x28,0xFF,0x64,0x0E,0x71,0x86,0xF1,0xB8};
ControlDevice DEVICE_LIST[3];
int TOTAL_DEVICES = 0;
auto chipid = String(ESP.getChipId(), HEX);
Communicator hass_comm = Communicator(net);
unsigned long lastMillis;
unsigned long currentMillis;
void mqttCallback(char *topic, byte *payload, unsigned int length) {
Serial.print("incoming: ");
Serial.println(topic);
for (unsigned int i = 0; i < length; i++)
{
Serial.print((char)payload[i]);
payload[length] = '\0';
for (int i=0;i<TOTAL_DEVICES;i++) {
if (strcmp((char *)topic, (DEVICE_LIST[i].mode.CmdTopic).c_str() ) == 0){
DEVICE_LIST[i].mode.Setting = (char*)payload;
if (DEVICE_LIST[i].mode.Setting == "auto") {
DEVICE_LIST[i].swing_state = "off";
hass_comm.publish_data(DEVICE_LIST[i].swing_topic, "on");
} else {
DEVICE_LIST[i].swing_state = "on";
hass_comm.publish_data(DEVICE_LIST[i].swing_topic, "off");
}
Serial.println("");
}
Communicator hass_comm = Communicator(net, &mqttCallback);
hass_comm.publish_data(DEVICE_LIST[i].mode.StateTopic, DEVICE_LIST[i].mode.Setting);
break;
}
String slugify(String input) {
input.toLowerCase();
input.replace(" ", "_");
return input;
int setpoint_count;
bool dual_mode = DEVICE_LIST[i].dual;
if (dual_mode) {
setpoint_count = 3;
} else {
setpoint_count = 1;
}
for (int j=0;j<setpoint_count;j++) {
String thistopic = DEVICE_LIST[i].setpoints[j].CmdTopic;
if (strcmp((char *)topic, thistopic.c_str()) == 0){
DEVICE_LIST[i].setpoints[j].Setting = atoi((char *)payload);
hass_comm.publish_data(DEVICE_LIST[i].setpoints[j].StateTopic, String(DEVICE_LIST[i].setpoints[j].Setting));
break;
}
}
}
}
void merge(JsonObject dest, JsonObjectConst src) {
@ -40,45 +75,72 @@ void merge(JsonObject dest, JsonObjectConst src) {
*
* Using climate device.
*/
void climateDevice(String name, boolean multimode=false) {
auto chipid = String(ESP.getChipId(), HEX);
void climateDevice(String name, byte ds18b20[], bool multimode=false) {
int setpoint_count = 0;
String name_slug = slugify(name);
String config_topic = "homeassistant/climate/" + name_slug + "_" + chipid + "/config";
String topic_root = "brewhouse/" + name_slug + "/";
ControlDevice device;
device.dual = false;
device.name = name;
device.topic_root = "brewhouse/" + slugify(name) + "/";
for (int i=0;i<8;i++) {
device.temp_sensor[i] = ds18b20[i];
}
StaticJsonDocument<1536> entity;
entity["uniq_id"] = chipid + "_" + name_slug;
entity["name"] = name;
entity["value_template"] = "{{ value }}";
entity["temp_unit"] = "F";
entity["max_temp"] = 100;
entity["min_temp"] = 0;
entity["swing_mode_stat_t"] = topic_root + SWING_STATE;
device.swing_topic = topic_root + SWING_STATE;
entity["act_t"] = topic_root + ACTION_TPC;
entity["act_tpl"] = "{{ value }}";
device.action_topic = topic_root + ACTION_TPC;
// Mode setup
entity["mode_cmd_t"] = topic_root + "mode/set";
entity["mode_cmd_t"] = topic_root + MODE_SET;
Mode mode = {topic_root + MODE_SET, topic_root + MODE_STATE, "off"};
device.mode = mode;
entity["mode_cmd_tpl"] = "{{ value }}";
entity["mode_stat_t"] = topic_root + "mode/state";
entity["mode_stat_tpl"] = "{{ value_json }}";
entity["mode_stat_t"] = topic_root + MODE_STATE;
JsonArray modes = entity.createNestedArray("modes");
modes.add("off");
modes.add("cool");
entity["temp_cmd_t"] = topic_root + "temp/set";
entity["temp_cmd_t"] = topic_root + TEMP_SET;
SetPoint temp = {topic_root + TEMP_SET,topic_root + TEMP_STATE,70};
device.setpoints[setpoint_count] = temp;
setpoint_count++;
entity["temp_cmd_tpl"] = "{{ value }}";
entity["temp_stat_t"] = topic_root + "temp/state";
entity["temp_stat_tpl"] = "{{ value_json }}";
entity["curr_temp_t"] = topic_root + "temp/current";
entity["temp_stat_t"] = topic_root + TEMP_STATE;
entity["curr_temp_t"] = topic_root + TEMP_CURRENT;
entity["curr_temp_tpl"] = "{{ value }}";
device.current_temp_topic= topic_root + TEMP_CURRENT;
if (multimode == true) {
entity["temp_hi_cmd_t"] = topic_root + "temp_hi/set";
device.dual = true;
entity["temp_hi_cmd_t"] = topic_root + TEMP_HI_SET;
SetPoint temp_hi = {topic_root + TEMP_HI_SET, topic_root + TEMP_HI_STATE, 70};
device.setpoints[setpoint_count] = temp_hi;
setpoint_count++;
entity["temp_hi_cmd_tpl"] = "{{ value }}";
entity["temp_hi_stat_t"] = topic_root + "temp_hi/state";
entity["temp_hi_stat_tpl"] = "{{ value_json }}";
entity["temp_hi_stat_t"] = topic_root + TEMP_HI_STATE;
entity["temp_lo_cmd_t"] = topic_root + "temp_lo/set";
entity["temp_lo_cmd_t"] = topic_root + TEMP_LO_SET;
SetPoint temp_lo = {topic_root + TEMP_LO_SET, topic_root + TEMP_LO_STATE, 70};
device.setpoints[setpoint_count] = temp_lo;
setpoint_count++;
entity["temp_lo_cmd_tpl"] = "{{ value }}";
entity["temp_lo_stat_t"] = topic_root + "temp_lo/state";
entity["temp_lo_stat_tpl"] = "{{ value_json }}";
entity["temp_lo_stat_t"] = topic_root + TEMP_LO_STATE;
modes.add("heat");
modes.add("auto");
}
@ -91,12 +153,39 @@ void climateDevice(String name, boolean multimode=false) {
ids.add(chipid);
//dev["ids"] = "[\"" + chipid +"\"]";
DEVICE_LIST[TOTAL_DEVICES] = device;
TOTAL_DEVICES++;
hass_comm.mqtt_discovery(config_topic, entity);
}
void SendUpdate(ControlDevice device) {
int setpoint_count = 0;
float device_temp;
device_temp = UpdateTemperature(&sensors, device.temp_sensor);
hass_comm.publish_data(device.current_temp_topic, String(device_temp));
hass_comm.publish_data(device.mode.StateTopic, device.mode.Setting);
hass_comm.publish_data(device.action_topic, device.action_state);
bool dual_mode = device.dual;
if (dual_mode) {
setpoint_count = 3;
} else {
setpoint_count = 1;
}
for (int i=0;i<setpoint_count;i++) {
hass_comm.publish_data(device.setpoints[i].StateTopic, String(device.setpoints[i].Setting));
}
}
void setup() {
hass_comm.connectCallback(&mqttCallback);
const char* ssid = WIFI_SSID;
const char* password = WIFI_PASSWORD;
sensors.begin();
sensors.setResolution(glycol_ds18b20, 9);
Serial.begin(115200);
WiFi.begin(ssid, password);
@ -108,20 +197,41 @@ void setup() {
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
climateDevice("Coolant Tank");
climateDevice("Coolant Tank", glycol_ds18b20);
climateDevice("Fermenter 1", fermA_ds18b20,true);
climateDevice("Fermenter 2", fermB_ds18b20,true);
String f_name = "Fermenter ";
for (int i=0;i<FERMENTER_COUNT;i++) {
int f_num = i+1;
Serial.println(f_name + f_num);
climateDevice(f_name + f_num, true);
}
SendUpdate(DEVICE_LIST[0]);
SendUpdate(DEVICE_LIST[1]);
SendUpdate(DEVICE_LIST[2]);
lastMillis = millis();
}
void loop() {
delay(5000);
hass_comm.publish_data("brewhouse/coolant_tank/temp/current", String(tank_setpoint));
hass_comm.publish_data("brewhouse/coolant_tank/mode/state", chiller_state);
currentMillis = millis();
if (currentMillis - lastMillis >= 10000) {
if (DeviceLoop(DEVICE_LIST[0], &sensors)) {
Serial.println("Ouput state changed.");
SendUpdate(DEVICE_LIST[0]);
}
if (DeviceLoop(DEVICE_LIST[1], &sensors)) {
Serial.println("Ouput state changed.");
SendUpdate(DEVICE_LIST[1]);
}
if (DeviceLoop(DEVICE_LIST[2], &sensors)) {
Serial.println("Ouput state changed.");
SendUpdate(DEVICE_LIST[2]);
}
sensors.requestTemperatures();
SendUpdate(DEVICE_LIST[0]);
SendUpdate(DEVICE_LIST[1]);
SendUpdate(DEVICE_LIST[2]);
lastMillis = currentMillis;
}
delay(100);
hass_comm.loop();
}

View File

@ -1,79 +0,0 @@
#include <ArduinoJson.h>
class Device {
public:
String device_topic;
Communicator hass_comm;
Device(Communicator& comm, String topic) {
this->hass_comm = comm;
this->device_topic = topic;
}
void addParameter(String id, String value){
_params[_paramcount] = Parameter(id, value);
_paramcount++
}
void attachEntity(Entity entity){
_entities[_entitycount] = entity;
_entitycount++;
}
void registerDevice(){
for (int i=0;i<_entitycount;i++){
StaticJsonDocument<512> doc;
for (int j=0;j<_entities[i].paramcount;j++) {
doc[_entities[i].params[j].getID()] = _entities[i].params[j].getValue();
}
}
JsonObject dev = doc.createNestedObject("dev");
for (int i=0; i<_paramcount;i++) {
dev[_params[j].getID()] = _params[j].getValue();
}
//Register
hass_comm.mqtt_discovery(device_topic, doc);
}
private:
Parameter _params[];
int _paramcount = 0;
Entity _entities[];
int _entitycount = 0;
}
class Entity {
public:
Parameter params[];
int paramcount = 0;
void addParameter(String id, String value){
params[paramcount] = Parameter(id, value);
paramcount++
}
}
class Parameter {
public:
Parameter(String id, String value){
this->_id = id;
this->_value = value;
}
String getID(){
return this->_id;
}
String getValue(){
return this->_value;
}
private:
String _id;
String _value;
}