Skip to content

Commit

Permalink
split blerry to driver files, config file, and main. refactor repo (#1)
Browse files Browse the repository at this point in the history
* split into "driver" functions

* re-org

* split config and execution

* update readme
  • Loading branch information
tony-fav authored Dec 9, 2021
1 parent 1d8ed4a commit 97f4622
Show file tree
Hide file tree
Showing 8 changed files with 409 additions and 368 deletions.
8 changes: 5 additions & 3 deletions README.md
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
```
365 changes: 0 additions & 365 deletions blerry.be

This file was deleted.

16 changes: 16 additions & 0 deletions blerry/blerry.be
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
143 changes: 143 additions & 0 deletions blerry/blerry_main.be
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)
110 changes: 110 additions & 0 deletions blerry/blerry_model_ATCpvvx.be
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
66 changes: 66 additions & 0 deletions blerry/blerry_model_GVH5075.be
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
Loading

0 comments on commit 97f4622

Please sign in to comment.