-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add TheengsDecoder python module and examples/tests (#49)
* Add TheengsDecoder python module and examples/tests
- Loading branch information
Showing
11 changed files
with
287 additions
and
0 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
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,41 @@ | ||
# Using with Python | ||
|
||
## Dependencies | ||
Building this module it requires scikit-build and cmake, if not already installed you will need to open a terminal and execute: | ||
``` | ||
pip install scikit-build | ||
apt-get install cmake | ||
``` | ||
|
||
## Installation | ||
|
||
From a terminal cd to `DECODER_FOLDER/python` folder and execute: | ||
``` | ||
pip install . | ||
``` | ||
|
||
## Using | ||
|
||
`import TheengsDecoder` | ||
|
||
The library includes a BLE decoder [example](./../../examples/python/ScanAndDecode.py). To run the example, open the folder [ScanAndDecode](./../../examples/python/ScanAndDecode.py) in a terminal and type 'python ScanAndDecode.py` | ||
|
||
If Theengs Decoder recognized a device, it will print a message like the example below, otherwise None. | ||
``` | ||
TheengsDecoder found device: {"brand":"Xiaomi","model":"LYWSD03MMC","model_id":"LYWSD03MMC_ATC","tempc":26.3,"tempf":79.34,"hum":49,"batt":29,"volt":2.487} | ||
``` | ||
|
||
Additionally the example will print the properties of the device as well as the brand and model using the `getProperties` and `getAttributes` methods. The output of these looks like: | ||
``` | ||
{"properties":{"volt":{"unit":"V","name":"voltage"},"x_axis":{"unit":"int","name":"x_axis"},"y_axis":{"unit":"int","name":"y_axis"},"z_axis":{"unit":"int","name":"z_axis"},"tempc":{"unit":"°C","name":"temperature"},"hum":{"unit":"%","name":"humidity"}}} | ||
brand: Mokosmart , model: BeaconX Pro | ||
``` | ||
|
||
These functions are useful for passing the data to HomeAssistant or other home automation/monitoring services. | ||
|
||
## Methods | ||
|
||
- `decodeBLE(string)` Returns a string with the decoded data in JSON format or None. | ||
- `getProperties('model_id string')` Returns the properties (string) of the given model ID or None | ||
- `getAttribute('model_id string', 'attribute string')` Return the value (string) of named attrubte of the model ID or None. |
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,5 @@ | ||
# Scan and decode demo | ||
|
||
## Requirements | ||
- bleak: `pip install bleak` | ||
- TheengsDecoder: (not available on PyPy yet) see the README in REPO_FOLDER/python for installation. |
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,50 @@ | ||
import asyncio | ||
import json | ||
import struct | ||
from bleak import BleakScanner | ||
from TheengsDecoder import decodeBLE as dble | ||
from TheengsDecoder import getProperties, getAttribute | ||
|
||
def detection_callback(device, advertisement_data): | ||
print(device.address, "RSSI:", device.rssi, advertisement_data) | ||
data_json = {} | ||
|
||
if advertisement_data.service_data: | ||
dstr = list(advertisement_data.service_data.keys())[0] | ||
# TheengsDecoder only accepts 16 bit uuid's, this converts the 128 bit uuid to 16 bit. | ||
data_json['servicedatauuid'] = dstr[4:8] | ||
dstr = str(list(advertisement_data.service_data.values())[0].hex()) | ||
data_json['servicedata'] = dstr | ||
|
||
if advertisement_data.manufacturer_data: | ||
dstr = str(struct.pack('<H', list(advertisement_data.manufacturer_data.keys())[0]).hex()) | ||
dstr += str(list(advertisement_data.manufacturer_data.values())[0].hex()) | ||
data_json['manufacturerdata'] = dstr | ||
|
||
if advertisement_data.local_name: | ||
data_json['name'] = advertisement_data.local_name | ||
|
||
if data_json: | ||
print("data sent to decoder: ", json.dumps(data_json)) | ||
data_json = dble(json.dumps(data_json)) | ||
print("TheengsDecoder found device:", data_json) | ||
|
||
if data_json: | ||
dev = json.loads(data_json) | ||
print(getProperties(dev['model_id'])) | ||
brand = getAttribute(dev['model_id'], 'brand') | ||
model = getAttribute(dev['model_id'], 'model') | ||
print("brand:", brand, ", model:", model) | ||
|
||
|
||
async def main(): | ||
scanner = BleakScanner() | ||
scanner.register_detection_callback(detection_callback) | ||
await scanner.start() | ||
await asyncio.sleep(5.0) | ||
await scanner.stop() | ||
|
||
for d in scanner.discovered_devices: | ||
print(d) | ||
|
||
asyncio.run(main()) |
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,18 @@ | ||
cmake_minimum_required(VERSION 3.3) | ||
|
||
project(decoder VERSION 0.1.0) | ||
find_package(PythonExtensions REQUIRED) | ||
|
||
add_library(_decoder MODULE TheengsDecoder/_decoder.cpp ../src/decoder.cpp) | ||
python_extension_module(_decoder) | ||
|
||
target_include_directories(_decoder | ||
PUBLIC | ||
$<INSTALL_INTERFACE:../arduino_json> | ||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../src/arduino_json/src> | ||
${CMAKE_CURRENT_SOURCE_DIR}/../src | ||
) | ||
|
||
target_compile_features(_decoder PRIVATE cxx_std_11) | ||
|
||
install(TARGETS _decoder LIBRARY DESTINATION TheengsDecoder) |
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,25 @@ | ||
# Theengs Decoder | ||
|
||
## dependencies | ||
Building this module it requires scikit-build and cmake, if not already installed you will need to open a terminal and execute: | ||
``` | ||
pip install scikit-build | ||
apt-get install cmake | ||
``` | ||
|
||
## installation | ||
|
||
From a terminal cd to this folder and execute: | ||
``` | ||
python setup.py install --user | ||
``` | ||
|
||
## using | ||
|
||
`import TheengsDecoder` | ||
|
||
## methods | ||
|
||
- `decodeBLE(string)` Returns a new string with the decoded data in json format or None. | ||
- `getProperties('model_id string')` Returns the properties (string) of the given model ID or None | ||
- `getAttribute('model_id string', 'attribute string')` Return the value (string) of named attrubte of the model ID or None. |
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,3 @@ | ||
from ._decoder import decodeBLE # noqa: F401 | ||
from ._decoder import getAttribute # noqa: F401 | ||
from ._decoder import getProperties # noqa: F401 |
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 @@ | ||
// Python includes | ||
#include <Python.h> | ||
|
||
#include "decoder.h" | ||
|
||
// STD includes | ||
#include <stdio.h> | ||
|
||
//----------------------------------------------------------------------------- | ||
static PyObject *decode_BLE(PyObject *self, PyObject *args) | ||
{ | ||
// Unpack a string from the arguments | ||
const char *strArg; | ||
if (!PyArg_ParseTuple(args, "s", &strArg)) | ||
return NULL; | ||
|
||
StaticJsonDocument<1024> doc; | ||
DeserializationError err = deserializeJson(doc, strArg); | ||
if (!err) { | ||
TheengsDecoder decoder; | ||
JsonObject bleObject; | ||
bleObject = doc.as<JsonObject>(); | ||
|
||
if (decoder.decodeBLEJson(bleObject)) { | ||
std::string buf; | ||
bleObject.remove("servicedata"); | ||
bleObject.remove("manufacturerdata"); | ||
bleObject.remove("servicedatauuid"); | ||
serializeJson(bleObject, buf); | ||
return Py_BuildValue("s", buf.c_str()); | ||
} | ||
} | ||
|
||
Py_RETURN_NONE; | ||
} | ||
|
||
static PyObject *decode_getTheengProperties(PyObject *self, PyObject *args) | ||
{ | ||
const char *strArg; | ||
if (!PyArg_ParseTuple(args, "s", &strArg)) | ||
return NULL; | ||
|
||
TheengsDecoder decoder; | ||
std::string prop = decoder.getTheengProperties(strArg); | ||
if (!prop.empty()) { | ||
return Py_BuildValue("s", prop.c_str()); | ||
} | ||
|
||
Py_RETURN_NONE; | ||
} | ||
|
||
static PyObject *decode_getTheengAttribute(PyObject *self, PyObject *args) | ||
{ | ||
const char *model; | ||
const char *att; | ||
if (!PyArg_ParseTuple(args, "ss", &model, &att)) | ||
return NULL; | ||
|
||
TheengsDecoder decoder; | ||
std::string prop = decoder.getTheengAttribute(model, att); | ||
if (!prop.empty()) { | ||
return Py_BuildValue("s", prop.c_str()); | ||
} | ||
|
||
Py_RETURN_NONE; | ||
} | ||
|
||
//----------------------------------------------------------------------------- | ||
static PyMethodDef decoder_methods[] = { | ||
{ | ||
"decodeBLE", | ||
decode_BLE, | ||
METH_VARARGS, | ||
"Decodes a BLE advertisement packet into JSON data." | ||
}, | ||
{ | ||
"getAttribute", | ||
decode_getTheengAttribute, | ||
METH_VARARGS, | ||
"Decodes a BLE advertisement packet into JSON data." | ||
}, | ||
{ | ||
"getProperties", | ||
decode_getTheengProperties, | ||
METH_VARARGS, | ||
"Decodes a BLE advertisement packet into JSON data." | ||
}, | ||
{NULL, NULL, 0, NULL} /* Sentinel */ | ||
}; | ||
|
||
//----------------------------------------------------------------------------- | ||
#if PY_MAJOR_VERSION < 3 | ||
PyMODINIT_FUNC init_decoder(void) | ||
{ | ||
(void) Py_InitModule("_decoder", decoder_methods); | ||
} | ||
#else /* PY_MAJOR_VERSION >= 3 */ | ||
static struct PyModuleDef decoder_module_def = { | ||
PyModuleDef_HEAD_INIT, | ||
"_decoder", | ||
"Internal \"_decoder\" module", | ||
-1, | ||
decoder_methods | ||
}; | ||
|
||
PyMODINIT_FUNC PyInit__decoder(void) | ||
{ | ||
return PyModule_Create(&decoder_module_def); | ||
} | ||
#endif /* PY_MAJOR_VERSION >= 3 */ |
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,2 @@ | ||
[build-system] | ||
requires = ["setuptools", "wheel", "scikit-build", "cmake", "ninja"] |
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,11 @@ | ||
from skbuild import setup | ||
|
||
setup( | ||
name="TheengsDecoder", | ||
version="0.1.0", | ||
description="A message decoder for the Internet of Things", | ||
author='Theengs', | ||
license=" GPL-3.0 License", | ||
packages=['TheengsDecoder'], | ||
) | ||
|
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,19 @@ | ||
from TheengsDecoder import decodeBLE as dble | ||
from TheengsDecoder import getProperties | ||
from TheengsDecoder import getAttribute | ||
import json | ||
|
||
x = {"servicedata":"712098004a63b6658d7cc40d071003f32600"} | ||
z = dble(json.dumps(x)) | ||
print(z, "\n") | ||
|
||
p = json.loads(z) | ||
print(getProperties(p['model_id']), "\n") | ||
brand = getAttribute(p['model_id'], 'brand') | ||
model = getAttribute(p['model_id'], 'model') | ||
print("brand:", brand, ", model:", model) | ||
|
||
y = {} | ||
z = dble(json.dumps(y)) | ||
print("decoder result (None expected): ", z) | ||
print("Done") |