Skip to content

Commit

Permalink
Initial support for GVH5074 (#21)
Browse files Browse the repository at this point in the history
* Initial 5074 testing

* Fixed decimal rounding issue

* Adding refrence material

* Now using geti for  two's complement

* Updated notes for new temperature method

* Fixed internal anchor link

Co-authored-by: cornking03 <cornking03+git@gmail.com>
  • Loading branch information
cornking03 and cornking03 committed Jan 18, 2022
1 parent 692567f commit 3066bc8
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 1 deletion.
3 changes: 2 additions & 1 deletion blerry/blerry_main.be
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ for mac:user_config.keys()
end

# Load model handle functions only if used
var model_drivers = {'GVH5075' : 'blerry_model_GVH5075.be',
var model_drivers = {'GVH5074' : 'blerry_model_GVH5074.be',
'GVH5075' : 'blerry_model_GVH5075.be',
'GVH5072' : 'blerry_model_GVH5075.be',
'GVH5101' : 'blerry_model_GVH5075.be',
'GVH5102' : 'blerry_model_GVH5075.be',
Expand Down
67 changes: 67 additions & 0 deletions blerry/blerry_model_GVH5074.be
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
def handle_GVH5074(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 == 0x0A)
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

output_map['Battery'] = adv_data.geti(7, 1)
# .geti() will take the two's complement when it extracts data
output_map['Temperature'] = adv_data.geti(3,2) / 100.0
output_map['Humidity'] = adv_data.get(5, 2) / 100.0

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['GVH5074'] = handle_GVH5074
require_active['GVH5074'] = true
80 changes: 80 additions & 0 deletions docs/GVH7074.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Notes for GVH7074

https://github.com/w1gx/govee-ble-scanner/wiki/Sniffing-BLE-advertising-packets was helpful in decoding the GVH5074

Full 'p' strings captured from ESP32 serial monitor
```
02010607030A18F5FE88EC1109476F7665655F48353037345F414141410AFF88EC00650775126402
02010607030A18F5FE88EC1109476F7665655F48353037345F414141410AFF88EC00650769126402
02010607030A18F5FE88EC1109476F7665655F48353037345F414141410AFF88EC0069075C126402
```

## Flags
`02 01 06`

`02` - two bytes

`01` - Type = Flags

`06` - Value (bitwise - 00000110) Bit 1 : "LE General Discoverable Mode", Bit 2: "BR/EDR Not Supported."

## Service Class
`07 03 0A 18 F5 FE 88 EC`

`07` - 7 bytes

`03` - Type = Service Class

`0A 18` -> 18 0A (little endian) - Device Information

`F5 FE` -> FE F5 (little endian) - Dialog Semiconductor GmbH

`88 EC` -> EC 88 (little endian) - unknown

## Complete Local Name
`11 09 47 6F 76 65 65 5F 48 35 30 37 34 5F 41 41 41 41`

`11` - 17 bytes

`09` - Type = Complete Local Name

`47 6F 76 65 65 5F 48 35 30 37 34 5F 41 41 41 41` = Govee_H5074_AAAA (AAAA = last 4 of mac)

## Manufacturer Specific Data
`0A FF 88 EC 00 65 07 75 12 64 02`

`0A` - 10 bytes

`FF` - Type = Manufacturer Specific Data

`88 EC 00` - unknown - same across all 'p' strings

`65 07` -> `07 65` (little endian) = See notes on [temperature](#temperature) below

`75 12` -> `12 75` (little endian) = 4725 in decimal. Divide by 100 to get relative humidity

`64` - 100 in decimal. Assuming this is battery percentage, but not sure.

`02` - unknown

## Temperature
The GVH5074 uses [Two's Complement](https://en.wikipedia.org/wiki/Two%27s_complement#Converting_to_two's_complement_representation) for negative numbers.
> In two's complement notation, a non-negative number is represented by its ordinary binary representation; in this case, the most significant bit is 0. Though, the range of numbers represented is not the same as with unsigned binary numbers. For example, an 8-bit unsigned number can represent the values 0 to 255 (11111111). However a two's complement 8-bit number can only represent positive integers from 0 to 127 (01111111), because the rest of the bit combinations with the most significant bit as '1' represent the negative integers −1 to −128.
>
> The two's complement operation is the additive inverse operation, so negative numbers are represented by the two's complement of the absolute value.
The berry language has a `geti` [method](https://github.com/berry-lang/berry/wiki/Chapter-7#get-geti-methods) that will return a signed value.

> Read a 1/2/4 bytes value from any offset in the bytes array. The standard mode is little endian, if you specify a negative size it enables big endian. `get` returns unsigned values, while `geti` returns signed values.
> `b.geti(<offset>, <size>) -> bytes object`
In order to get the temperature in °C, use the `geti` method and then divide by 100.0
```
# Example
adv_data = bytes('88EC0052FA6E166402')
temp = adv_data.geti(3,2) / 100.0
```
Output
```
-14.54
```

0 comments on commit 3066bc8

Please sign in to comment.