Skip to content

Instantly share code, notes, and snippets.

@thadk
Created December 11, 2023 22:39
Show Gist options
  • Save thadk/6169beac3c15620d90a3074d37d30629 to your computer and use it in GitHub Desktop.
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
# 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
# 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()
}
}
# 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 }}"
# 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