The easiest way to process information from smart meters, thermometers and other smart things you develop yourself is to have them broadcast information in BTLE (bluetooth low energy) advertisement, then to collect the BTLE advertisement on a central processing unit.
This project shows you how to write code to relay BTLE advertisement to Hologram cloud via cellular network in the simplest possible way, then you can base upon it to develop your own code. Hologram provides a good data plan for IoT, with 1MB free for developers. To debug this project, I only consumed 4.7 kB of data, there is room left.
holo_gw.py - Hologram BTLE-to-cellular gatewayThis single-file sample code shows how to implement a BTLE advertisement to cellular data gateway (1-way). This relay software runs on Rapberry Pi on Linux Raspbian. It scans for BTLE advertisement received by PI's built-in BTLE then uses Hologram SDK API to send it to Hologram Cloud, via the Holo Nova modem.From hardware perspective, Raspberry Pi zero W is used to capture BTLE advertisement from surrounding devices. Hologram Nova plugged into Pi relays the advertisement data to Hologram Cloud.
This sample code relays only the following selected fields of BTLE adv to Holo Cloud:- Name- Device address- Manufacturer Data break down to a 16-bit unique manufacturer ID followed by manufacturer data. Note that from BTLE standard's view, manu ID + data form a single field in the advertisement.To customize your filter, you may just modify the relay() function in holo_gw.py.
Usage1) install Hologram SDK
2) copy the file holo_gw.py to Raspberry Pi
3) type in Raspberry Pi console where it is copied to: ./holo_gw.py
Code mainloop explainedRaspberry Pi turn off its builtin BTLE by default. We turn it on:
os.system('echo "power on"|sudo bluetoothctl')
The Raspbery builtin BTLE (Bluetooth Low Energy) device can be configured, controlled, and communicated with through the standard HCI (Host Control Interface). HCI is partly implemented in Linux Kernel, partly in library. Access to HCI is handled by a central system daemon, and HCI programming interface or API is exported by that daemon to other Python applications in the form of D-bus (Desktop bus) services. Therefore Python applications do not directly interface HCI, but use D-bus methods and signal to program the BTLE device. With D-bus API, Python applications don't need to care about Bluetooth specification, the BTLE devices are just programming objects that are driven by programming methods and that respond to Python application with signals.
Create a default context in dbus-python to be used by "bus":
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
Below, we create a connection to System bus to listen to BTLE events (signal). System Bus is the Pi board wide, unique virtual bus, shared by all users, that allows you to access BT HCI via the system BT Daemon (bluetoothd process). In our case, BTLE Python API for the builtin BTLE device can only be accessed through System bus. The variable "bus" holds the connection to System Bus:
bus = dbus.SystemBus()
The "interfaces_added" subroutine will be called each time a remote BTLE device is discovered for the 1st time. "add_signal_receiver" method subscribes you to a system events. The following instruction subscribes the "interfaces_added" subroutine to the event "InterfacesAdded" when a new NTLE peripheral is discovered:
bus.add_signal_receiver(interfaces_added,
dbus_interface = "org.freedesktop.DBus.ObjectManager",
signal_name = "InterfacesAdded")
Similarly, the "properties_changed" subroutine will be called each time a known BTLE device change the content of its advertisement. The following instruction subscribes the "properties_changed" subroutine to the event "PropertiesChanged" concerning remote BTLE devices. "org.bluez.Device1" is the parent of D-bus objects representing remote BTLE devices.
bus.add_signal_receiver(properties_changed,
dbus_interface = "org.freedesktop.DBus.Properties",
signal_name = "PropertiesChanged",
arg0 = "org.bluez.Device1",
path_keyword = "path")
"om" holds the group of methods called ObjectManager. bus.get_object("org.bluez", "/") returns the root object "/" on the virtual bus "org.bluez". This root object implements the API who's full name is "org.freedesktop.DBus.ObjectManager":
om = dbus.Interface(bus.get_object("org.bluez", "/"),
"org.freedesktop.DBus.ObjectManager")
GetManagedObjects() method returns the object-tree with two objects in our case: 1) Host Control Interface to the builtin device "/org/bluez/hci0". 2) the root object "/org/bluez". The object of interest is the 1st one, it offers the API to control the local builtin BTLE device on board of Raspberry Pi:
objects = om.GetManagedObjects()
Next we are going to configure the builtin bluetooth device also called adapter. "find_adapter" subroutine explores the object-tree to find the the D-bus object representing the local BTLE device (builtin BTLE device). The distinction between the local builtin and a discovered remote BTLE device is: local builtin BTLE device has a API named "org.bluez.Adapter1", whereas remote BTLE device has a API named "org.bluez.Device1". Note that the number "1" denotes Bluez software version, nothing to do with the device numbering.
adapter = find_adapter(objects)
The "adapter" variable holds the API to the local (builtin) BTLE device.
Calling "StartDiscovery" method of the API starts scanning neighboring BTLE devices. An empty filter ({}) meaning all scanned BTLE devices are reported:
adapter.SetDiscoveryFilter({})
adapter.StartDiscovery()
When a remote BTLE device is discovered, interfaces_added() will be called to parse the advertisement from that device. Sending the collected BTLE advertisement to Hologram Cloud is extremely simple and is done in the subroutine relay(), by invoking Hologram SDK API as Linux shell command:
os.system('sudo hologram send <text info contained in BTLE adv>')
Several seconds later, <text info contained in BTLE adv> will land in Hologram cloud.
Trace captured on Rasperry Pi console("/home/pi/holo" is where I put holo_gw.py)pi@raspberrypi:~/holo $ ./holo_gw.py[NEW] Controller B8:27:EB:DF:DD:12 raspberrypi [default]Agent registered[bluetooth]# power onAgent unregistered[DEL] Controller B8:27:EB:DF:DD:12 raspberrypi [default]interfaces_added[ 60:6B:BD:0F:C1:15 ] AddressType = public Name = DTVBluetooth Paired = 0 ServicesResolved = 0 Adapter = /org/bluez/hci0 LegacyPairing = 0 TxPower = 0 Alias = DTVBluetooth ManufacturerData = dbus.Dictionary({dbus.UInt16(15): dbus.Array([dbus.Byte(0), dbus.Byte(9), dbus.Byte(72)], signature=dbus.Signature('y'), variant_level=1)}, signature=dbus.Signature('qv'), variant_level=1) Connected = 0 UUIDs = dbus.Array([dbus.String(u'0000110a-0000-1000-8000-00805f9b34fb'), dbus.String(u'00001200-0000-1000-8000-00805f9b34fb')], signature=dbus.Signature('s'), variant_level=1) Address = 60:6B:BD:0F:C1:15 RSSI = -57 Blocked = 0 Class = 0x08043c Trusted = 0 Icon = audio-card************** send to Holo Cloud: sudo hologram send "DTVBluetooth[60:6B:BD:0F:C1:15]:manu_id=15 data=0x000948"*************RESPONSE MESSAGE: Message sent successfully
the received data seen in Hologram Dashboard
Message sent from Device #158411: DTVBluetooth[60:6B:BD:0F:C1:15]:manu_id=15 data=0x000948
View Raw:
success true
data
id 4611179
logged "2018-01-06 01:25:53.911534"
orgid 10965
deviceid 158411
record_id "895ec6f4-f280-11e7-bb6d-bc764e206eb6"
device_metadata "{\"m_version\": 1, \"dev_fw_string\": \"nova_u201-0.7.2\"}"
data "{\"received\": \"2018-01-06T01:25:53.251328\", \"authtype\": \"otp\", \"tags\": [\"_SOCKETAPI_\", \"_DEVICE_158411_\", \"_SIMPLESTRING_\"], \"timestamp\": \"1515201949\", \"device_name\": \"Unnamed Device (86191)\", \"errorcode\": 0, \"source\": \"8944502407175486191\", \"record_id\": \"895ec6f4-f280-11e7-bb6d-bc764e206eb6\", \"data\": \"RFRWQmx1ZXRvb3RoWzYwOjZCOkJEOjBGOkMxOjE1XTptYW51X2lkPTE1IGRhdGE9MHgwMDA5NDg=\", \"device_id\": 158411}"
matched_rules []
tags
0 "_SIMPLESTRING_"
1 "_DEVICE_158411_"
2 "_SOCKETAPI_"
Annex : iBeacon BTLE adversement format
0x02, // length of this data 0x01, // GAP_ADTYPE_FLAGS 0x06, // GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED | GAP_ADTYPE_FLAGS_GENERAL 0x1A, // length of manufacture specific data excluding this length 0xFF, // GAP_ADTYPE_MANUFACTURER_SPECIFIC 0x4C, // company ID[0] 0x00, // company ID[1] 0x02, // beacon type[0] 0x15, // beacon type[1] 0xA3, // UUID LSB 0x22, 0x37, 0xE7, 0x3E, 0xC0, 0xC5, 0x84, 0x86, 0x4B, 0xB9, 0x99, 0xF9, 0x82, 0x03, 0xF7, // UUID MSB 0x00, //major[0] (user data[0]) 0x00, //major[1] (user data[1]) 0x00, //minor[0] (user data[2]) 0x00, //minor[1] (user data[3]) 0x00 //measured power (can be set later)
Comments