-
-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
split blerry to driver files, config file, and main. refactor repo (#1)
* split into "driver" functions * re-org * split config and execution * update readme
- Loading branch information
Showing
8 changed files
with
409 additions
and
368 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
Example creation of BLE advertisement interpretation using Tasmota. | ||
# Blerry - BLE Driver for Tasmota written in Berry | ||
|
||
Requires a build with BLE and Berry (ESP32 devices only). | ||
|
||
Some of the commands used in `blerry.be` are not initialized by the time `autoexec.be` is run. So, you must load `blerry.be` as part of a rule. | ||
|
||
To use: | ||
- Upload `blerry.be`, `blerry_main.be`, and each `blerry_model_xxxx.be` model driver file you may need to the file system of the ESP32. | ||
- Edit `blerry.be` as needed for your device configuration, HA discovery choices, etc... | ||
- Create and enable (`Rule1 1`) the following Tasmota Rule and Restart Tasmota (Some of the commands used in `blerry.be` are not initialized by the time `autoexec.be` is run. So, you must load `blerry.be` as part of a rule.) | ||
``` | ||
Rule1 ON System#Boot DO br load('blerry.be') ENDON | ||
``` |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# --------- USER INPUT --------- | ||
var user_config = {'A4C138AAAAAA': {'alias': 'trial_govee5075', 'model': 'GVH5075', 'discovery': true}, | ||
'A4C138BBBBBB': {'alias': 'other_govee5075', 'model': 'GVH5075', 'via_pubs': false}, | ||
'A4C138CCCCCC': {'alias': 'trial_ATCpvvx', 'model': 'ATCpvvx', 'discovery': true, 'use_lwt': true}, | ||
'494208DDDDDD': {'alias': 'trial_inkbird', 'model': 'IBSTH2', 'discovery': true}} | ||
var base_topic = 'tele/tasmota_blerry' | ||
|
||
# ------ ADVANCED CONFIG ------ | ||
var old_details = false # Set to true if Tasmota build is before https://github.com/arendst/Tasmota/pull/13671 was merged | ||
var override_config = {} # default_config is applied first, user_config is applied next to specific macs, then override_config overwrites anything entered. | ||
# useful for if you wanted user_config the same on many devices except only send discovery on 1 device. have discovery off in user_config but add here. | ||
# var override_config = {'temp_precision': 3, | ||
# 'humi_precision': 2} | ||
|
||
# -------- LOAD BLERRY -------- | ||
load('blerry_main.be') # Do not change this line |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
|
||
# ----------- IMPORTS ---------- | ||
import math | ||
import string | ||
import json | ||
|
||
# ----------- DEFAULT ---------- | ||
var default_config = {'model': 'ATCpvvx', # Must match 'ATCpvvx', 'GVH5075', or 'IBSTH2' | ||
'discovery': false, # HA MQTT Discovery | ||
'use_lwt': false, # use receiving device's LWT as LWT for BLE device | ||
'via_pubs': false, # publish attributes like "Time_via_%topic%" and "RSSI_via_%topic%" (default false to reduce workload on ESP) | ||
'sensor_retain': false, # retain publication of data | ||
'publish_attributes': false, # publish attributes to individual topics in addition to JSON payload (default false to reduce workload on ESP) | ||
'temp_precision': 2, # digits of precision for temperature | ||
'humi_precision': 1, # digits of precision for humidity | ||
'last_p': bytes(''), # DO NOT CHANGE | ||
'done_disc': false, # DO NOT CHANGE | ||
'done_extra_disc': false} # DO NOT CHANGE | ||
|
||
# ----------- HELPERS ---------- | ||
def round(x, p) | ||
return math.ceil(math.pow(10.0, p)*x)/math.pow(10.0, p) | ||
end | ||
def get_dewpoint(t, h) # temp, humidity, precision | ||
var gamma = math.log(h / 100.0) + 17.62 * t / (243.5 + t) | ||
return (243.5 * gamma / (17.62 - gamma)) | ||
end | ||
def string_replace(x, y, r) | ||
var z = x[0..] | ||
var n = size(y) | ||
var m = size(r) | ||
var k = 0 | ||
while k < size(z) | ||
var j = string.find(z, y, k) | ||
if j < 0 | ||
break | ||
end | ||
if j > 0 | ||
z = z[0..j-1] + r + z[j+n..] | ||
else | ||
z = r + z[j+n..] | ||
end | ||
k = j+m | ||
end | ||
return z | ||
end | ||
|
||
# ----------- BLERRY ----------- | ||
var device_config = {} | ||
var details_trigger = 'DetailsBLE' | ||
if old_details | ||
details_trigger = 'details' | ||
end | ||
var discovery_retain = true # only false when testing | ||
|
||
# Get this device's topic info | ||
var device_topic = tasmota.cmd('Status')['Status']['Topic'] | ||
var cmnd_prefix = tasmota.cmd('Prefix1')['Prefix1'] | ||
var stat_prefix = tasmota.cmd('Prefix2')['Prefix2'] | ||
var tele_prefix = tasmota.cmd('Prefix3')['Prefix3'] | ||
var full_topic_f = tasmota.cmd('FullTopic')['FullTopic'] | ||
var hostname = tasmota.cmd('Status 5')['StatusNET']['Hostname'] | ||
var device_tele_topic = string_replace(string_replace(full_topic_f, '%prefix%', tele_prefix), '%topic%', device_topic) | ||
|
||
def publish_sensor_discovery(mac, prop, dclass, unitm) | ||
var item = device_config[mac] | ||
var prefix = '{' | ||
if item['use_lwt'] | ||
prefix = prefix + string.format('"avty_t\": \"%s/LWT\",\"pl_avail\": \"Online\",\"pl_not_avail\": \"Offline\",', device_tele_topic) | ||
else | ||
prefix = prefix + '\"avty\": [],' | ||
end | ||
prefix = prefix + string.format('\"dev\":{\"ids\":[\"blerry_%s\"],\"name\":\"%s\",\"mf\":\"blerry\",\"mdl\":\"%s\",\"via_device\":\"%s\"},', item['alias'], item['alias'], item['model'], hostname) | ||
prefix = prefix + string.format('\"exp_aft\": 600,\"json_attr_t\": \"%s/%s\",\"stat_t\": \"%s/%s\",', base_topic, item['alias'], base_topic, item['alias']) | ||
tasmota.publish(string.format('homeassistant/sensor/blerry_%s/%s/config', item['alias'], prop), prefix + string.format('\"dev_cla\": \"%s\",\"unit_of_meas\": \"%s\",\"name\": \"%s %s\",\"uniq_id\": \"blerry_%s_%s\",\"val_tpl\": \"{{ value_json.%s }}\"}', dclass, unitm, item['alias'], prop, item['alias'], prop, prop), discovery_retain) | ||
end | ||
|
||
def publish_binary_sensor_discovery(mac, prop, dclass) | ||
var item = device_config[mac] | ||
var prefix = '{' | ||
if item['use_lwt'] | ||
prefix = prefix + string.format('"avty_t\": \"%s/LWT\",\"pl_avail\": \"Online\",\"pl_not_avail\": \"Offline\",', device_tele_topic) | ||
else | ||
prefix = prefix + '\"avty\": [],' | ||
end | ||
prefix = prefix + string.format('\"dev\":{\"ids\":[\"blerry_%s\"],\"name\":\"%s\",\"mf\":\"blerry\",\"mdl\":\"%s\",\"via_device\":\"%s\"},', item['alias'], item['alias'], item['model'], hostname) | ||
prefix = prefix + string.format('\"exp_aft\": 600,\"json_attr_t\": \"%s/%s\",\"stat_t\": \"%s/%s\",', base_topic, item['alias'], base_topic, item['alias']) | ||
if dclass != 'none' | ||
prefix = prefix + string.format('\"dev_cla\": \"%s\",', dclass) | ||
end | ||
tasmota.publish(string.format('homeassistant/binary_sensor/blerry_%s/%s/config', item['alias'], prop), prefix + string.format('\"name\": \"%s %s\",\"uniq_id\": \"blerry_%s_%s\",\"val_tpl\": \"{{ value_json.%s }}\"}', item['alias'], prop, item['alias'], prop, prop), discovery_retain) | ||
end | ||
|
||
# Build complete device config maps | ||
for mac:user_config.keys() | ||
device_config[mac] = {} | ||
for item:default_config.keys() | ||
device_config[mac][item] = default_config[item] | ||
end | ||
for item:user_config[mac].keys() | ||
device_config[mac][item] = user_config[mac][item] | ||
end | ||
for item:override_config.keys() | ||
device_config[mac][item] = override_config[item] | ||
end | ||
end | ||
|
||
# Load model handle functions only if used | ||
var model_drivers = {'GVH5075': 'blerry_model_GVH5075.be', | ||
'ATCpvvx': 'blerry_model_ATCpvvx.be', | ||
'ATC' : 'blerry_model_ATCpvvx.be', | ||
'pvvx' : 'blerry_model_ATCpvvx.be', | ||
'IBSTH1' : 'blerry_model_IBSTH2.be', | ||
'IBSTH2' : 'blerry_model_IBSTH2.be'} | ||
var models = {} | ||
for mac:user_config.keys() | ||
models[model_drivers[device_config[mac]['model']]] = true | ||
end | ||
|
||
var device_handles = {} | ||
var require_active = {} | ||
for m:models.keys() | ||
load(m) | ||
end | ||
|
||
# Register Aliases with Tasmota and Register Handle Functions | ||
var setup_active = false | ||
for mac:user_config.keys() | ||
device_config[mac]['handle'] = device_handles[device_config[mac]['model']] | ||
tasmota.cmd(string.format('BLEAlias %s=%s', mac, device_config[mac]['alias'])) | ||
setup_active = setup_active || require_active[device_config[mac]['model']] | ||
end | ||
if setup_active | ||
tasmota.cmd('BLEScan0 1') | ||
end | ||
|
||
# Enable BLEDetails for All Aliased Devices and Make Rule | ||
tasmota.cmd('BLEDetails4') | ||
def DetailsBLE_callback(value, trigger, msg) | ||
device_config[value['mac']]['handle'](value, trigger, msg) | ||
tasmota.gc() | ||
end | ||
tasmota.add_rule(details_trigger, DetailsBLE_callback) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
|
||
# ATC or pvvx | ||
def handle_ATCpvvx(value, trigger, msg) | ||
if trigger == details_trigger | ||
var this_device = device_config[value['mac']] | ||
var p = bytes(value['p']) | ||
var i = 0 | ||
var adv_len = 0 | ||
var adv_data = bytes('') | ||
var adv_type = 0 | ||
while i < size(p) | ||
adv_len = p.get(i,1) | ||
adv_type = p.get(i+1,1) | ||
adv_data = p[i+2..i+adv_len] | ||
if adv_type == 0x16 # Service Data 16-bit UUID, used by pvvx and ATC advert | ||
if adv_data[0..1] == bytes('1A18') # little endian of 0x181A | ||
var last_data = this_device['last_p'] | ||
if (size(last_data) == 18) && (adv_len == 18) # use this to ignore re-processing of "same data, new counter" | ||
last_data[15] = adv_data[15] | ||
end | ||
if adv_data == last_data | ||
return 0 | ||
else | ||
device_config[value['mac']]['last_p'] = adv_data | ||
end | ||
if this_device['discovery'] && !this_device['done_disc'] | ||
publish_sensor_discovery(value['mac'], 'Temperature', 'temperature', '°C') | ||
publish_sensor_discovery(value['mac'], 'Humidity', 'humidity', '%') | ||
publish_sensor_discovery(value['mac'], 'DewPoint', 'temperature', '°C') | ||
publish_sensor_discovery(value['mac'], 'Battery', 'battery', '%') | ||
publish_sensor_discovery(value['mac'], 'Battery_Voltage', 'voltage', 'V') | ||
publish_sensor_discovery(value['mac'], 'RSSI', 'signal_strength', 'dB') | ||
device_config[value['mac']]['done_disc'] = true | ||
end | ||
var output_map = {} | ||
output_map['Time'] = tasmota.time_str(tasmota.rtc()['local']) | ||
output_map['alias'] = this_device['alias'] | ||
output_map['mac'] = value['mac'] | ||
output_map['via_device'] = device_topic | ||
output_map['RSSI'] = value['RSSI'] | ||
if this_device['via_pubs'] | ||
output_map['Time_via_' + device_topic] = output_map['Time'] | ||
output_map['RSSI_via_' + device_topic] = output_map['RSSI'] | ||
end | ||
if adv_len == 16 | ||
output_map['Temperature'] = adv_data.geti(8,-2)/10.0 | ||
output_map['Humidity'] = adv_data.get(10,1) | ||
output_map['Battery'] = adv_data.get(11,1) | ||
output_map['Battery_Voltage'] = adv_data.get(12,-2)/1000.0 | ||
elif adv_len == 18 | ||
if this_device['discovery'] && !this_device['done_extra_disc'] | ||
publish_binary_sensor_discovery(value['mac'], 'GPIO_PA6', 'none') | ||
publish_binary_sensor_discovery(value['mac'], 'GPIO_PA5', 'none') | ||
publish_binary_sensor_discovery(value['mac'], 'Triggered_by_Temperature', 'none') | ||
publish_binary_sensor_discovery(value['mac'], 'Triggered_by_Humidity', 'none') | ||
device_config[value['mac']]['done_extra_disc'] = true | ||
end | ||
output_map['Temperature'] = adv_data.geti(8,2)/100.0 | ||
output_map['Humidity'] = adv_data.get(10,2)/100.0 | ||
output_map['Battery_Voltage'] = adv_data.get(12,2)/1000.0 | ||
output_map['Battery'] = adv_data.get(14,1) | ||
output_map['Count'] = adv_data.get(15,1) | ||
output_map['Flag'] = adv_data.get(16,1) | ||
if output_map['Flag'] & 1 | ||
output_map['GPIO_PA6'] = 'ON' | ||
else | ||
output_map['GPIO_PA6'] = 'OFF' | ||
end | ||
if output_map['Flag'] & 2 | ||
output_map['GPIO_PA5'] = 'ON' | ||
else | ||
output_map['GPIO_PA5'] = 'OFF' | ||
end | ||
if output_map['Flag'] & 4 | ||
output_map['Triggered_by_Temperature'] = 'ON' | ||
else | ||
output_map['Triggered_by_Temperature'] = 'OFF' | ||
end | ||
if output_map['Flag'] & 8 | ||
output_map['Triggered_by_Humidity'] = 'ON' | ||
else | ||
output_map['Triggered_by_Humidity'] = 'OFF' | ||
end | ||
end | ||
output_map['DewPoint'] = round(get_dewpoint(output_map['Temperature'], output_map['Humidity']), this_device['temp_precision']) | ||
output_map['Temperature'] = round(output_map['Temperature'], this_device['temp_precision']) | ||
output_map['Humidity'] = round(output_map['Humidity'], this_device['humi_precision']) | ||
var this_topic = base_topic + '/' + this_device['alias'] | ||
tasmota.publish(this_topic, json.dump(output_map), this_device['sensor_retain']) | ||
if this_device['publish_attributes'] | ||
for output_key:output_map.keys() | ||
tasmota.publish(this_topic + '/' + output_key, string.format('%s', output_map[output_key]), this_device['sensor_retain']) | ||
end | ||
end | ||
end | ||
end | ||
i = i + adv_len + 1 | ||
end | ||
end | ||
end | ||
|
||
# map function into handles array | ||
device_handles['ATCpvvx'] = handle_ATCpvvx | ||
require_active['ATCpvvx'] = false | ||
|
||
device_handles['ATC'] = handle_ATCpvvx | ||
require_active['ATC'] = false | ||
|
||
device_handles['pvvx'] = handle_ATCpvvx | ||
require_active['pvvx'] = false |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# GVH5075: Govee Temp and Humidity Sensor | ||
def handle_GVH5075(value, trigger, msg) | ||
if trigger == details_trigger | ||
var this_device = device_config[value['mac']] | ||
var p = bytes(value['p']) | ||
var i = 0 | ||
var adv_len = 0 | ||
var adv_data = bytes('') | ||
var adv_type = 0 | ||
while i < size(p) | ||
adv_len = p.get(i,1) | ||
adv_type = p.get(i+1,1) | ||
adv_data = p[i+2..i+adv_len] | ||
if (adv_type == 0xFF) && (adv_len == 9) | ||
var last_data = this_device['last_p'] | ||
if adv_data == last_data | ||
return 0 | ||
else | ||
device_config[value['mac']]['last_p'] = adv_data | ||
end | ||
if this_device['discovery'] && !this_device['done_disc'] | ||
publish_sensor_discovery(value['mac'], 'Temperature', 'temperature', '°C') | ||
publish_sensor_discovery(value['mac'], 'Humidity', 'humidity', '%') | ||
publish_sensor_discovery(value['mac'], 'DewPoint', 'temperature', '°C') | ||
publish_sensor_discovery(value['mac'], 'Battery', 'battery', '%') | ||
publish_sensor_discovery(value['mac'], 'RSSI', 'signal_strength', 'dB') | ||
device_config[value['mac']]['done_disc'] = true | ||
end | ||
var output_map = {} | ||
output_map['Time'] = tasmota.time_str(tasmota.rtc()['local']) | ||
output_map['alias'] = this_device['alias'] | ||
output_map['mac'] = value['mac'] | ||
output_map['via_device'] = device_topic | ||
output_map['RSSI'] = value['RSSI'] | ||
if this_device['via_pubs'] | ||
output_map['Time_via_' + device_topic] = output_map['Time'] | ||
output_map['RSSI_via_' + device_topic] = output_map['RSSI'] | ||
end | ||
var basenum = (bytes('00') + adv_data[3..5]).get(0,-4) | ||
if basenum >= 0x800000 | ||
output_map['Temperature'] = (0x800000 - basenum)/10000.0 | ||
output_map['Humidity'] = ((basenum - 0x800000) % 1000)/10.0 | ||
else | ||
output_map['Temperature'] = basenum/10000.0 | ||
output_map['Humidity'] = (basenum % 1000)/10.0 | ||
end | ||
output_map['Battery'] = adv_data.get(6,1) | ||
output_map['DewPoint'] = round(get_dewpoint(output_map['Temperature'], output_map['Humidity']), this_device['temp_precision']) | ||
output_map['Temperature'] = round(output_map['Temperature'], this_device['temp_precision']) | ||
output_map['Humidity'] = round(output_map['Humidity'], this_device['humi_precision']) | ||
var this_topic = base_topic + '/' + this_device['alias'] | ||
tasmota.publish(this_topic, json.dump(output_map), this_device['sensor_retain']) | ||
if this_device['publish_attributes'] | ||
for output_key:output_map.keys() | ||
tasmota.publish(this_topic + '/' + output_key, string.format('%s', output_map[output_key]), this_device['sensor_retain']) | ||
end | ||
end | ||
end | ||
i = i + adv_len + 1 | ||
end | ||
end | ||
end | ||
|
||
# map function into handles array | ||
device_handles['GVH5075'] = handle_GVH5075 | ||
require_active['GVH5075'] = false |
Oops, something went wrong.