Created
December 11, 2023 22:39
-
-
Save thadk/6169beac3c15620d90a3074d37d30629 to your computer and use it in GitHub Desktop.
WIP OpenERV mods 2023 using HASSIO and 2mqtt for serial<->MQTT fan control and CO2 integration
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# the --network portion allows it to connect to the MQTT server in a separate docker container, may not be directly applicable | |
# also check the USB-serial device matches your system's, it seems to vary. | |
docker run --rm --name 2mqtt --network=container:addon_core_mosquitto --volume /mnt/data/supervisor/homeassistant/2mqtt.yaml:/app/config.yaml --device /dev/ttyACM0 --env TZ=America/New_York docker.io/mycontroller/2mqtt:1.4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# WIP | |
logger: | |
mode: development # logger mode: development, production | |
encoding: console # encoding options: console, json | |
level: info # log levels: debug, info, warn, error, fatal | |
adapters: # you can have more than one adapter | |
- name: adapter1 # name of the adapter | |
enabled: true # enable or disable the adapter, default disabled | |
reconnect_delay: 20s # reconnect automatically, if there is a failure on the connection | |
provider: raw # provider type, options: mysensors_v2, raw | |
source: # source is the device, to be converted to MQTT, based on the type, configurations will be different | |
type: serial # source device type: serial | |
port: /dev/ttyACM0 # serial port | |
baud_rate: 115200 # serial baud rate | |
transmit_pre_delay: 10ms # waits and sends a message, to avoid collision on the source network | |
mqtt: # mqtt broker details | |
broker: tcp://core-mosquitto:1883 # broker url: supports tcp, mqtt, tls, mqtts | |
insecure: false # enable/disable insecure on tls connection | |
username: # username of the broker | |
password: # password of the broker | |
subscribe: openerv_serial/# # subscribe a topic, should include `#` at the end, your controller to serial port(source) | |
publish: homeassistant/sensor/openerv_serial # publish on this topic, can add many topics with comma, serial to your controller | |
qos: 0 # qos number: 0, 1, 2 | |
transmit_pre_delay: 0s | |
reconnect_delay: 5s | |
formatter_script: | |
to_mqtt: | | |
// want to add mqtt topic dynamically | |
// assume the raw_data is "hello;mqtt/secret/topic" | |
const dataArray = raw_data.split(";") | |
if (dataArray[1].trim() === "mqtt/secret/newSpeed") { | |
result = { | |
data: dataArray[0].trim(), | |
mqtt_topic: "homeassistant/sensor/openerv_serial_fan" | |
} | |
} else if (dataArray[1].trim() === "mqtt/secret/openerv") { | |
result = { | |
data: JSON.stringify(dataArray[0] | |
.trim() | |
.replace("temp2", "temp2: ") | |
.replace("potoverride", "|potoverride") | |
.split("|") | |
.map(n => | |
({ [n.split(":")[0].trim()]:Number(n.split(":")[1].trim()) }) ) | |
.reduce((result, currentObject) => ({ | |
...result, | |
...currentObject | |
}))) | |
//mqtt_topic: dataArray[1].trim(), | |
} | |
} else { | |
result = { | |
data: "unknown2", | |
//mqtt_topic: dataArray[1].trim() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# work in progress. thermistor->temperature conversions are incomplete and in confusing and conflicting units. | |
# fan percentage_value_template viewing doesn't work for some reason but setting the percent using mqtt+2mqtt+serial does. | |
mqtt: | |
sensor: | |
- name: "openerv fan override" | |
state_topic: "homeassistant/sensor/openerv_serial" | |
value_template: "{{ value_json.potoverride }}" | |
state_class: "measurement" | |
unit_of_measurement: "percent" | |
unique_id: "sensor.openerv.fanspeed_override" | |
icon: "mdi:fan" | |
- name: "openerv fan" | |
state_topic: "homeassistant/sensor/openerv_serial" | |
value_template: "{{ value_json.potval }}" | |
state_class: "measurement" | |
unit_of_measurement: "percent" | |
unique_id: "sensor.openerv.fanspeed" | |
icon: "mdi:fan" | |
- name: "openerv fan outdoor temp" | |
state_topic: "homeassistant/sensor/openerv_serial" | |
# value_template: "{{ value_json.temp2 }}" | |
# state_class: "measurement" | |
# unit_of_measurement: "percent" | |
unique_id: "sensor.openerv.temperature_out" | |
icon: "mdi:thermometer" | |
value_template: > | |
{% set R25 = 100 %} | |
{% set B25_50 = 3950 %} | |
{% set A = 0.001125 %} | |
{% set B = 0.000234 %} | |
{% set C = 0.000000086 %} | |
{% set R0 = R25 * (1023 / (value_json.temp1|int) - 1) ** (-1 / B25_50) %} | |
{% set resistance_temp1 = R0 * (1023 / (value_json.temp1|int) - 1) %} | |
{% set temperature_temp1 = 1 / (A + B * log(resistance_temp1 / R0) + C * (log(resistance_temp1 / R0) ** 3)) - 273.15 %} | |
{{ temperature_temp1 | round(2) }} | |
- name: "openerv fan indoor temp" | |
state_topic: "homeassistant/sensor/openerv_serial" | |
# value_template: "{{ value }}" | |
# state_class: "measurement" | |
# unit_of_measurement: "°C" | |
value_template: > | |
{% set B_value = 3950 %} | |
{% set R0 = 10000 %} | |
{% set A = 0.001125 %} | |
{% set B = 0.000234 %} | |
{% set C = 0.000000086 %} | |
{% set temp1_resistance = R0 * (2 ** (B_value / ((value_json.temp1|int)/ 1023) - 1)) %} | |
{% set temp1_temperature_C = 1 / (A + B * log(temp1_resistance / R0 ) + C * (log(temp1_resistance / R0) ** 3)) %} | |
{{ ((temp1_temperature_C | float * 9/5) + 32) | round(0) }} | |
unique_id: "sensor.openerv.temperature_in" | |
icon: "mdi:thermometer" | |
fan: #mqtt controlled fans | |
- name: "Interior OpenERV Fan" | |
percentage_state_topic: "homeassistant/sensor/openerv_serial" | |
#{ 'is_on': True, 'percentage': , 'speed_count': 99} | |
percentage_value_template: "{{ value_json.potoverride }}" | |
command_topic: "openerv_serial" | |
percentage_command_topic: "openerv_serial" | |
percentage_command_template: "setfan{{ value }}" | |
- name: "Exterior OpenERV Fan" | |
percentage_state_topic: "homeassistant/sensor/openerv_serial" | |
command_topic: "openerv_serial" | |
percentage_command_topic: "openerv_serial" | |
percentage_command_template: "setexteriorfan{{ value }}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# https://web.archive.org/web/20221129082251/https://www.openerv.org/community | |
from machine import Pin, ADC, PWM | |
from time import sleep | |
import select | |
import sys | |
import re | |
#setup | |
enable_pin_active_low=Pin(1, Pin.OUT) | |
enable_pin_active_low.off() | |
dir_pin=Pin(3, Pin.OUT) | |
dir_pin.off() | |
mot1=PWM(Pin(4)) | |
mot2=PWM(Pin(5)) | |
stepper = PWM(Pin(0)) | |
potpinadc = ADC(Pin(26)) | |
temp1=ADC(Pin(28)) | |
temp2=ADC(Pin(27)) | |
led=Pin(25, Pin.OUT) | |
mot1.freq(20_000) | |
mot2.freq(20_000) | |
mot1.duty_u16(0) | |
mot2.duty_u16(0) | |
# Create an instance of a polling object | |
poll_obj = select.poll() | |
# Register sys.stdin (standard input) for monitoring read events with priority 1 | |
poll_obj.register(sys.stdin,1) | |
oldpotval = 0 | |
potval = 0 | |
stepper.duty_u16(30000) | |
stepperspeed=5000 #pulses per second to the driver, whatever that comes to in rpm | |
potoverride=None | |
minimumForExternal = 66 | |
print_tock = 0 | |
#end setup, begin input section | |
for x in range(20, stepperspeed): # accelerate the stepper up to speed | |
stepper.freq(x) | |
stepper.duty_u16(30_000) | |
sleep(0.004) | |
print(stepper.freq()) | |
led.toggle() | |
while True: | |
potval=int(potpinadc.read_u16()/630) #input, convert potval to a value between about 0 and 100 | |
#begin output | |
tempval1=temp1.read_u16() | |
tempval2=temp2.read_u16() | |
sleep(0.1) | |
if (print_tock >= 1000): | |
print_tock = 0 | |
print_tock += 1 | |
def return_override_variable(hardware, override): | |
return override if override is not None else hardware | |
#number = read_input() | |
# Calculate and print the result | |
#result = calculate_value(number) | |
#print("The calculated value is:", result) | |
#if poll_obj.poll(0): | |
# ch= sys.stdin.readline().strip() | |
#ch = int(sys.stdin.read(1)) | |
# print("ch",ch) | |
try: | |
# Create a polling object | |
poll_obj = select.poll() | |
# Register sys.stdin (standard input) for monitoring read events with priority 0 | |
poll_obj.register(sys.stdin, select.POLLIN) | |
# Use poll() to check for events | |
events = poll_obj.poll(0) # Non-blocking check | |
for fd, event in events: | |
if event & select.POLLIN: | |
user_input = sys.stdin.readline().strip() | |
print(f"Received fan speed data: {user_input}; mqtt/secret/newSpeed") | |
# Use regular expression to extract the number following "setfan" | |
match = re.match(r"setfan(\d+)", user_input) | |
if match: | |
fan_speed = int(match.group(1)) | |
potoverride = fan_speed | |
match2 = re.match(r"setexteriorfan(\d+)", user_input) | |
if match2: | |
minimumForExternal = int(match2.group(1)) | |
except Exception as e: | |
print(f"An error occurred: {e}") | |
pot = return_override_variable(potval, potoverride) | |
#apx every 30 sec for 500, 1.6min for 999 | |
if (print_tock % 999 == 0): | |
print("potval:",potval,"potoverride:",potoverride,"|temp1:",tempval1, "|temp2",tempval2,"|mot1duty:", mot1.duty_u16(),"|mot2duty:", mot2.duty_u16(), "; mqtt/secret/openerv") | |
# both fans off, just rotor: 41dB | |
#mot1 is exterior fan, | |
# 53.5dB at max by itself; | |
# still 54 at max with front at half. | |
# 48dB at 66% (43686) | |
#mot2 is interior fan, | |
# 57dB at max by itself; | |
# 47dB at half by itself. | |
# 52dB at 75% (49151) | |
# 51dB at 66% (43686) | |
#mot2.duty_u16(43686) | |
#mot1.duty_u16(43686) | |
#led.toggle() | |
if abs(pot-oldpotval)>3: | |
mot1.duty_u16(max(pot*630, minimumForExternal*630)) | |
oldpotval=pot | |
mot1.freq(20_000) | |
# originally: mot2.duty_u16(mot1.duty_u16()) | |
mot2.duty_u16(pot*630) | |
mot2.freq(20_000) | |
if (potval>95): | |
mot1.duty_u16(65535) | |
mot1.freq(20_000) | |
mot2.duty_u16(mot1.duty_u16()) | |
mot2.freq(20_000) | |
#no cleanup | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment