diff --git a/blerry/blerry_main.be b/blerry/blerry_main.be index b9c769f..7b8df01 100644 --- a/blerry/blerry_main.be +++ b/blerry/blerry_main.be @@ -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', diff --git a/blerry/blerry_model_GVH5074.be b/blerry/blerry_model_GVH5074.be new file mode 100644 index 0000000..1bceaaf --- /dev/null +++ b/blerry/blerry_model_GVH5074.be @@ -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 \ No newline at end of file diff --git a/docs/GVH7074.md b/docs/GVH7074.md new file mode 100644 index 0000000..49c0339 --- /dev/null +++ b/docs/GVH7074.md @@ -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(, ) -> 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 +```