├── service └── blegateway.service ├── ble2http.py ├── config.py ├── Node-RED Flows ├── README.md └── ble2mqtt2influx.json ├── Grafana ├── README.md ├── unknown.json ├── heartbeat.json └── tlm.json ├── config ├── sample.json └── blegateway.json ├── LICENSE ├── blegateway.py ├── ble2mqtt.py ├── .gitignore ├── ble2influx.py ├── main.py └── README.md /service/blegateway.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=ble gateway Service 3 | After=network.target 4 | [Service] 5 | Type=simple 6 | User=pi 7 | WorkingDirectory=/home/pi/ble-gateway 8 | ExecStart=/usr/bin/python3 /home/pi/ble-gateway/main.py 9 | Restart=always 10 | [Install] 11 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /ble2http.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import config 4 | import blegateway 5 | 6 | httpCONFIG = config.get_config('http') 7 | header = {'Content-type': 'application/json'} 8 | 9 | def heartbeat(): 10 | requests.post(httpCONFIG['host'], 11 | data=json.dumps(blegateway.fill_heartbeat()).encode('utf8'), 12 | headers=header) 13 | 14 | def send_bt(bt_addr, message): 15 | requests.post(httpCONFIG['host'], data=json.dumps(message).encode('utf8'), headers=header) 16 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | try: 4 | # Allows for test config file to be used during development 5 | config = json.load(open('config/testblegateway.json',)) 6 | except: 7 | config = json.load(open('config/blegateway.json',)) 8 | 9 | def get_config(section): 10 | if section == 'bleDevice' or section == 'filters' or \ 11 | section == 'identifiers' or section == 'endpoints' or \ 12 | section == 'names': 13 | return config[section] 14 | elif section == 'mqtt' or section == 'http' or \ 15 | section == 'influx': 16 | if section in config: 17 | return config[section] 18 | else: 19 | print('Invalid Section given') 20 | return None -------------------------------------------------------------------------------- /Node-RED Flows/README.md: -------------------------------------------------------------------------------- 1 | ## Node-RED Flows 2 | 3 | The example is provided to provide easy parsing of the mqtt messages into Influx DB. 4 | 5 | It is assumed that the following datbases have been created: 6 | * ruuvi 7 | 8 | If influx has bee installed the following can be run to create the databases: 9 | 10 | ```bash 11 | #Enter influx console 12 | influx 13 | #create each database 14 | CREATE DATABASE ruuvi 15 | ``` 16 | 17 | If you are missing any nodes a warning will pop up to display they are missing and the node will be blanked out. Go to Manage pallete to add these Nodes. 18 | 19 | Once the flow has been imported and all Nodes have been installed the databases will need configured for each 'influx batch node'. 20 | 21 | Note: the database name ruuvi is used so that this can be used with: https://github.com/Scrin/RuuviCollector 22 | -------------------------------------------------------------------------------- /Grafana/README.md: -------------------------------------------------------------------------------- 1 | ## Grafana 2 | 3 | Before importing the dashboard the databases need to be added as a datasource. 4 | Instructions can be found at: 5 | https://grafana.com/docs/features/datasources/influxdb/ 6 | 7 | ## Grafana - Ruuvi Tags 8 | For displaying ruuvitag data the examples provided by the Ruuvi team can be used. They can be found at: https://github.com/ruuvi/ruuvi.grafana-dashboards.json 9 | 10 | ## Grafana - TLM Tags 11 | 12 | The example is provided to give an example on showing the TLM beacon data. 13 | 14 | 15 | ## Grafana - Unknown Beacons 16 | 17 | The example is provided to give an example on showing the TLM ble data. 18 | 19 | This would be mainly used for tracking information resulting from the received RSSI values. 20 | 21 | ## Grafana - Heartbeat Information 22 | 23 | The example provided display's Uptime, CPU Usage (%), Memory Used (MB's) and Memory Used (%). -------------------------------------------------------------------------------- /config/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "bleDevice" : { 3 | "bleDevice" : 0 4 | }, 5 | "filters" : { 6 | "eddystone" : true, 7 | "ibeacon" : false, 8 | "ruuvi" : true, 9 | "ruuviPlus" : false, 10 | "unknown" : false, 11 | "rssiThreshold" : true, 12 | "rssi" : -60, 13 | "macFilterEnabled" : true, 14 | "macFilter" : [ 15 | "c0bb722a568e", 16 | "dc3fd0bbcec2", 17 | "c467f2f9cf5a", 18 | "f7ac6ea886b1" 19 | ] 20 | }, 21 | "identifiers" : { 22 | "interface" : "wlp1s0", 23 | "location" : "home", 24 | "zone" : "masterBedroom", 25 | "gatewayType" : "PiZero" 26 | }, 27 | "endpoints" : { 28 | "mqttEnabled" : true, 29 | "httpEnabled" : false, 30 | "influxEnabled" : false 31 | }, 32 | "mqtt" : { 33 | "host" : "192.168.8.1", 34 | "port" : 1883, 35 | "user" : "bobs", 36 | "password" : "burgers", 37 | "ssl" : false, 38 | "ca" : null, 39 | "cert" : null, 40 | "key" : null 41 | } 42 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 B@$T!0N 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/blegateway.json: -------------------------------------------------------------------------------- 1 | { 2 | "bleDevice" : { 3 | "bleDevice" : 0, 4 | "serialPort" : "/dev/ttyS0", 5 | "baudrate" : 115200, 6 | "timeout" : 1 7 | }, 8 | "filters" : { 9 | "eddystone" : true, 10 | "ibeacon" : false, 11 | "ruuvi" : true, 12 | "ruuviPlus" : true, 13 | "unknown" : false, 14 | "rssiThreshold" : false, 15 | "rssi" : -60, 16 | "macFilterEnabled" : false, 17 | "macFilter" : [ 18 | "FFFF96C5A1EE", 19 | "D8FFFFFFFD7A", 20 | "FFFFFF284268" 21 | ] 22 | }, 23 | "names" : { 24 | "FFFF96C5A1EE" : "Outside Patio", 25 | "FFFFFF284268" : "Master Bedroom", 26 | "D8FFFFFFFD7A" : "Outside Garden" 27 | }, 28 | "identifiers" : { 29 | "interface" : "wlp1s0", 30 | "location" : "home", 31 | "zone" : "masterBedroom", 32 | "gatewayType" : "PiZero" 33 | }, 34 | "endpoints" : { 35 | "mqttEnabled" : true, 36 | "httpEnabled" : false, 37 | "influxEnabled" : false 38 | }, 39 | "mqtt" : { 40 | "host" : "127.0.0.1", 41 | "port" : 1883, 42 | "user" : null, 43 | "password" : null, 44 | "ssl" : false, 45 | "ca" : null, 46 | "cert" : null, 47 | "key" : null 48 | }, 49 | "http" : { 50 | "host" : "127.0.0.1", 51 | "port" : 443, 52 | "user" : null, 53 | "password" : null, 54 | "ca" : null, 55 | "cert" : null, 56 | "key" : null 57 | }, 58 | "influx" : { 59 | "host" : "127.0.0.1", 60 | "port" : 8086, 61 | "database" : "ruuvi", 62 | "user" : null, 63 | "password" : null 64 | } 65 | } -------------------------------------------------------------------------------- /blegateway.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from uptime import uptime 3 | import psutil 4 | import config 5 | 6 | start = round(uptime()) 7 | 8 | def getMAC(interface='eth0'): 9 | # Return the MAC address of the specified interface 10 | try: 11 | str = open('/sys/class/net/%s/address' %interface).read() 12 | except: 13 | str = "00:00:00:00:00:00" 14 | return str[0:17] 15 | 16 | DEVmac = str.upper(getMAC(config.get_config('identifiers')['interface']).translate({ord(':'): None})) 17 | 18 | def gateway_mac(): 19 | return DEVmac 20 | 21 | def timestamp(): 22 | t = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now()) 23 | t = str(t) 24 | t = t.translate({ord(' '): 'T'}) 25 | return t + 'Z' 26 | 27 | def fill_heartbeat(): 28 | up = round(uptime()) - start 29 | cpu = psutil.cpu_percent() 30 | vMem = psutil.virtual_memory() 31 | return {'ts': timestamp(), 'edgeMAC': DEVmac, 'type': config.get_config('identifiers')['gatewayType'], \ 32 | 'uptime': up, 'cpu': cpu, 'totalMemory': vMem[0], 'availableMemory': vMem[1], \ 33 | 'percentUsedMemory': vMem[2], 'usedMemory': vMem[3], 'freeMemory': vMem[4], \ 34 | 'location': config.get_config('identifiers')['location'], 'zone': config.get_config('identifiers')['zone']} 35 | 36 | def ble_message(bt_addr, rssi, packet, decoded, smoothedRSSI, name): 37 | msg = decoded 38 | msg['mac'] = bt_addr 39 | msg['edgeMAC'] = gateway_mac() 40 | msg['data'] = packet 41 | msg['rssi'] = rssi 42 | msg['rssiSmooth'] = smoothedRSSI 43 | msg['ts'] = timestamp() 44 | msg['location'] = config.get_config('identifiers')['location'] 45 | msg['zone'] = config.get_config('identifiers')['zone'] 46 | if name != None: 47 | msg['name'] = name 48 | return msg 49 | -------------------------------------------------------------------------------- /ble2mqtt.py: -------------------------------------------------------------------------------- 1 | import paho.mqtt.client as mqtt 2 | import ssl, time, json 3 | import config 4 | import blegateway 5 | 6 | mqttCONFIG = config.get_config('mqtt') 7 | ids = config.get_config('identifiers') 8 | 9 | TOPIC = ids['location'] + "/" + ids['zone'] + "/" 10 | print("Main Topic: " + TOPIC) 11 | heartbeatTOPIC = 'heartbeat/' + ids['location'] + "/" + ids['zone'] 12 | print("Heartbeat Topic: " + heartbeatTOPIC) 13 | 14 | DISCONNECTED = 0 15 | CONNECTING = 1 16 | CONNECTED = 2 17 | 18 | if mqttCONFIG['ssl']: 19 | ROOT_CA = mqttCONFIG['ca'] 20 | CLIENT_CERT = mqttCONFIG['cert'] 21 | PRIVATE_KEY = mqttCONFIG['key'] 22 | 23 | def MQTT(): 24 | isSSL = mqttCONFIG['ssl'] 25 | if isSSL: 26 | ROOT_CA = mqttCONFIG['ca'] 27 | CLIENT_CERT = mqttCONFIG['cert'] 28 | PRIVATE_KEY = mqttCONFIG['key'] 29 | state = DISCONNECTED 30 | global client 31 | client = mqtt.Client() 32 | if mqttCONFIG['user'] != None and \ 33 | mqttCONFIG['password'] != None : 34 | client.username_pw_set(mqttCONFIG['user'], password=mqttCONFIG['password']) 35 | if isSSL == True: 36 | client.tls_set(ca_certs=ROOT_CA, certfile=CLIENT_CERT, keyfile=PRIVATE_KEY, cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLS, ciphers=None) 37 | 38 | while state != CONNECTED: 39 | try: 40 | state = CONNECTING 41 | client.connect(mqttCONFIG['host'], mqttCONFIG['port'], 60) 42 | state = CONNECTED 43 | except: 44 | print('Could not establish MQTT connection') 45 | time.sleep(0.5) 46 | if state == CONNECTED: 47 | print('MQTT Client Connected') 48 | client.loop_start() 49 | 50 | def heartbeat(): 51 | client.publish( heartbeatTOPIC, json.dumps(blegateway.fill_heartbeat()), qos=0, retain=False ) 52 | 53 | def send_bt(bt_addr, message): 54 | client.publish( TOPIC + bt_addr, json.dumps(message), qos=0, retain=False ) 55 | 56 | def end(): 57 | client.loop_stop() 58 | client.disconnect() -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | privateconfig.py 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | 132 | testconfig.py 133 | 134 | config/testble2mqtt.json 135 | 136 | config/testblegateway.json 137 | -------------------------------------------------------------------------------- /ble2influx.py: -------------------------------------------------------------------------------- 1 | from influxdb import InfluxDBClient 2 | import json 3 | import config 4 | import blegateway 5 | 6 | influxCONFIG = config.get_config('influx') 7 | ids = config.get_config('identifiers') 8 | 9 | def INFLUX(): 10 | if influxCONFIG['user'] == None: 11 | influxCONFIG['user'] = 'root' 12 | if influxCONFIG['password'] == None: 13 | influxCONFIG['password'] = 'root' 14 | global client 15 | client = InfluxDBClient(influxCONFIG['host'], influxCONFIG['port'], \ 16 | influxCONFIG['user'], influxCONFIG['password'], influxCONFIG['database']) 17 | 18 | dbs = client.get_list_database() 19 | if influxCONFIG['database'] in dbs: 20 | pass 21 | else: 22 | client.create_database(influxCONFIG['database']) 23 | client.switch_database(influxCONFIG['database']) 24 | 25 | def heartbeat(): 26 | temp = blegateway.fill_heartbeat() 27 | msg = [] 28 | _msg = { 29 | "measurement": 'heartbeat_measurements', 30 | "tags": { 31 | "edgeMac": temp['edgeMAC'], 32 | "edgeType": temp['type'] 33 | }, 34 | "fields": { 35 | "edgeMAC": temp['edgeMAC'], 36 | "edgeType": temp['type'], 37 | "uptime": temp['uptime'], 38 | "cpu": temp['cpu'], 39 | "totalMemory": temp['totalMemory'], 40 | "availableMemory": temp['availableMemory'], 41 | "percentUsedMemory": temp['percentUsedMemory'], 42 | "usedMemory": temp['usedMemory'], 43 | "freeMemory": temp['freeMemory'], 44 | "location": temp['location'], 45 | "zone": temp['zone'], 46 | } 47 | } 48 | 49 | msg.append(_msg) 50 | client.write_points(msg) 51 | 52 | def send_bt(bt_addr, message): 53 | if message['dataFormat'] == 10: 54 | mesurement = 'uid_measurements' 55 | elif message['dataFormat'] == 11: 56 | mesurement = 'url_measurements' 57 | elif message['dataFormat'] == 12: 58 | mesurement = 'tlm_measurements' 59 | elif message['dataFormat'] == 13: 60 | mesurement = 'etlm_measurements' 61 | elif message['dataFormat'] == 14: 62 | mesurement = 'eid_measurements' 63 | elif message['dataFormat'] == 20: 64 | mesurement = 'ibeacon_measurements' 65 | elif message['dataFormat'] == 3 or message['dataFormat'] == 5: 66 | mesurement = 'ruuvi_measurements' 67 | else: 68 | mesurement = 'unknown_measurements' 69 | 70 | tags = {"mac": bt_addr, "edgeMAC": message['edgeMAC']} 71 | items = {} 72 | 73 | if 'name' in message: 74 | tags['name'] = message['name'] 75 | 76 | for i in message: 77 | if 'data' in i or 'ts' in i: 78 | pass 79 | else: 80 | items[i] = message[i] 81 | 82 | msg = [] 83 | _msg = { 84 | "measurement": mesurement, 85 | "tags": tags, 86 | "fields": items 87 | } 88 | 89 | msg.append(_msg) 90 | client.write_points(msg) -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | import json 4 | import blegateway 5 | import config 6 | 7 | if config.get_config('bleDevice')['bleDevice'] == 1: 8 | # Import Receiver for nRF module 9 | from beaconscanner import BeaconReceiver 10 | else: 11 | # Import bluez scanner 12 | from beaconscanner import BeaconScanner 13 | 14 | mqttEnabled = config.get_config('endpoints')['mqttEnabled'] 15 | httpEnabled = config.get_config('endpoints')['httpEnabled'] 16 | influxEnabled = config.get_config('endpoints')['influxEnabled'] 17 | 18 | if mqttEnabled: 19 | import ble2mqtt 20 | if httpEnabled: 21 | import ble2http 22 | if influxEnabled: 23 | import ble2influx 24 | 25 | try: 26 | names = config.get_config('names') 27 | except: 28 | namesEnabled = False 29 | else: 30 | namesEnabled = True 31 | 32 | mFen = config.get_config('filters')['macFilterEnabled'] 33 | if mFen: 34 | mF = config.get_config('filters')['macFilter'] 35 | 36 | def callback(bt_addr, rssi, packet, dec, smoothedRSSI): 37 | if namesEnabled: 38 | if bt_addr in names: 39 | name = names[bt_addr] 40 | else: 41 | name = None 42 | else: 43 | name = None 44 | if mFen == True: 45 | for i in mF: 46 | if str.upper(i) == bt_addr: 47 | if mqttEnabled: 48 | ble2mqtt.send_bt(bt_addr, blegateway.ble_message\ 49 | (bt_addr, rssi, packet, dec, smoothedRSSI, name)) 50 | if influxEnabled: 51 | ble2influx.send_bt(bt_addr, blegateway.ble_message\ 52 | (bt_addr, rssi, packet, dec, smoothedRSSI, name)) 53 | if httpEnabled: 54 | ble2http.send_bt(bt_addr, blegateway.ble_message\ 55 | (bt_addr, rssi, packet, dec, smoothedRSSI, name)) 56 | else: 57 | if mqttEnabled: 58 | ble2mqtt.send_bt(bt_addr, blegateway.ble_message\ 59 | (bt_addr, rssi, packet, dec, smoothedRSSI, name)) 60 | if influxEnabled: 61 | ble2influx.send_bt(bt_addr, blegateway.ble_message\ 62 | (bt_addr, rssi, packet, dec, smoothedRSSI, name)) 63 | if httpEnabled: 64 | ble2http.send_bt(bt_addr, blegateway.ble_message\ 65 | (bt_addr, rssi, packet, dec, smoothedRSSI, name)) 66 | 67 | 68 | def main_loop(): 69 | RSSIen = config.get_config('filters')['rssiThreshold'] 70 | if (RSSIen): 71 | RSSI = config.get_config('filters')['rssi'] 72 | else: 73 | RSSI = -999 74 | if mqttEnabled: 75 | ble2mqtt.MQTT() 76 | if influxEnabled: 77 | ble2influx.INFLUX() 78 | global scanner 79 | f = config.get_config('filters') 80 | if config.get_config('bleDevice')['bleDevice'] == 1: 81 | scanner = BeaconReceiver(callback, config.get_config('bleDevice')['serialPort'], \ 82 | config.get_config('bleDevice')['baudrate'], \ 83 | config.get_config('bleDevice')['timeout'],\ 84 | rssiThreshold=RSSI,\ 85 | ruuvi=f['ruuvi'], ruuviPlus=f['ruuviPlus'], \ 86 | eddystone=f['eddystone'], ibeacon=f['ibeacon'], unknown=f['unknown']) 87 | else: 88 | scanner = BeaconScanner(callback, rssiThreshold=RSSI,\ 89 | ruuvi=f['ruuvi'], ruuviPlus=f['ruuviPlus'], \ 90 | eddystone=f['eddystone'], ibeacon=f['ibeacon'], unknown=f['unknown']) 91 | scanner.start() 92 | while True: 93 | if config.get_config('bleDevice')['bleDevice'] == 1: 94 | time.sleep(30) 95 | if mqttEnabled: 96 | ble2mqtt.heartbeat() 97 | if influxEnabled: 98 | ble2influx.heartbeat() 99 | if httpEnabled: 100 | ble2http.heartbeat() 101 | else: 102 | time.sleep(30) 103 | scanner._mon.toggle_scan(False) 104 | if mqttEnabled: 105 | ble2mqtt.heartbeat() 106 | if influxEnabled: 107 | ble2influx.heartbeat() 108 | if httpEnabled: 109 | ble2http.heartbeat() 110 | scanner._mon.toggle_scan(True) 111 | 112 | if __name__ == "__main__": 113 | try: 114 | main_loop() 115 | except KeyboardInterrupt: 116 | scanner.stop() 117 | if mqttEnabled: 118 | ble2mqtt.end() 119 | print("\nExiting application\n") 120 | sys.exit(0) 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BLE Gateway 2 | BLE Gateway is an application designed for use with linux PC's. 3 | 4 | This application is designed to turn your linux based PC into a functioning BLE gateway. 5 | 6 | It supports MQTT, HTTP and influxDB endpoints and allows multiple to be used at one time. 7 | 8 | It relies on 3 other ptyhon module to work correctly: 9 | * pybluez - 0.22 10 | * beacondecoder - 0.6.2 11 | * beaconscanner - 1.2.4 12 | 13 | Also supports external nRF52832 module. 14 | 15 | # Tested on 16 | The application has been tested on: 17 | * RPI 3B+ : running Raspbian Buster 18 | * RPI Zero W : running Raspbian Buster 19 | * Dell Inspiron 7000 : running Fedora 31 and Ubuntu 20 20 | 21 | # External BLE module 22 | 23 | To use an external ble module the nrf module in beaconscanner is wanting to receive the following information: 24 | mac address, ble packet, rssi 25 | 26 | https://github.com/ruuvi/ruuvi.gateway_nrf.c 27 | 28 | The above repository can be used to flash a nRF52832 module to provide this data. 29 | 30 | This has been tested on the following ble module: 31 | Ruuvitag 32 | Raytac MDBT42Q-U512KV2 33 | 34 | ## RPI 35 | 36 | The RPI's built in serial connections cannot be used out of the box as it is used to connect to the RPI's console over serial. To enable the use of the serial pin the following needs to be done: 37 | 38 | ```bash 39 | sudo raspi-config 40 | ``` 41 | 42 | Select 'Interfacing Options' 43 | Select 'Serial' 44 | Select 'No' 45 | Select ' Yes' 46 | 47 | Then reboot 48 | 49 | You will now have /dev/ttyS0 listed in the devices. Connect your module to the follwing pins on your RPI: 50 | * Pin 8 - TX 51 | * Pin 10 - RX 52 | 53 | NOTE: If you do not want to disable to serial console an external USB to serial adapter can be used. 54 | 55 | ## Onion Omega 56 | 57 | On Onion Omega devices TX1 and RX1 pins can be used to connect UART devices. 58 | 59 | The serial device that corresponds to these pins is /dev/ttyS1 60 | 61 | # Installation 62 | 63 | The below example is for a system based on Debian 10 which includes devices such as Raspberry Pi's or Ubuntu based OS's. 64 | 65 | ```bash 66 | # install libbluetooth, python3-pip, python3-bluez and git 67 | sudo apt-get install python3-pip python3-bluez libbluetooth-dev git 68 | # grant the python executable permission to access raw socket data 69 | sudo setcap 'cap_net_raw,cap_net_admin+eip' $(readlink -f $(which python3)) 70 | #install python modules 71 | pip3 install beaconscanner uptime paho-mqtt psutil influxdb 72 | # 73 | cd ~/ 74 | #clone repository 75 | git clone https://github.com/theBASTI0N/ble-gateway.git && cd ble-gateway 76 | ``` 77 | 78 | ## Configuration 79 | Edit the configuration file with your favourite editor. 80 | 81 | The configuration file is located in the config directory being named blegateway.json. 82 | It contains all of the different configuration options. 83 | 84 | ``` 85 | * bleDevice = Contains the information on the ble device that will be used. 86 | ** bleDevice = 0 for builtin device, 1 for serial device 87 | * filters = Allows multiple filters to be applied. 88 | ** eddystone = Allows eddystone TLM device to be seen. 89 | ** ibeacon = Allows ibeacon tags to be seen. 90 | ** ruuvi = Allows RuuviTag beacons to be seen. 91 | ** ruuviPlus = Allows enhanced ruuvi decoding to occur. 92 | ** rssiThreshold = Enables the RSSI threshold 93 | ** rssi = If above enabled only device with q stronger RSSI will be seen. 94 | * identifiers = Allows ids to be given to multiple gateways. 95 | ** interface = Select which network interface to use to get the mac. 96 | ** location = Overarching location eg. Head Office 97 | ** zone = More specific location eg. Board Room 98 | ** gatewayType = Used for grouping track of heartbeat data of similar devices. 99 | * endpoints = Allows the toggling of different endpoint types. Currently MQTT, HTTP and InfluxDB are supported. 100 | * mqtt = Allows the configuration of a MQTT endpoint 101 | * http = Allows the configuration of a http endpoint 102 | * influx = Allows the configuration of a influxDB endpoint 103 | 104 | ``` 105 | 106 | ## Service 107 | 108 | To enable the application as a service so it will run at boot. The following can be followed: 109 | 110 | ```bash 111 | #move to service director 112 | cd ~/ble-gateway/service 113 | #copy the service file to the correct location 114 | sudo cp blegateway.service /etc/systemd/system/ 115 | #enable the service to start on boot 116 | sudo systemctl enable blegateway 117 | #start the service 118 | sudo systemctl start blegateway 119 | #check it is running. If active all is working 120 | sudo systemctl status blegateway 121 | ``` 122 | 123 | NOTE: This has been designed ot run on a Raspberry Pi. 124 | 125 | # RuuviCollector 126 | 127 | I have designed this application to work with: 128 | https://github.com/Scrin/RuuviCollector 129 | 130 | This is why the readings go into the influxDB database ruuvi when using either MQTT/node-red and influxDB endpoints by default. 131 | 132 | # Visualisation 133 | 134 | To visualise the data the following has been tested and is required. Versions may differ depending on system you are using. 135 | * MQTT broker 136 | * Node-RED 137 | * Grafana 138 | * InfluxDB 139 | 140 | ## Node-RED Flows 141 | 142 | The Example can be found in the Node-RED Flows folder. It will pass MQTT messages to influxDB. 143 | 144 | ## Grafana 145 | 146 | Grafana Examples can be found in the grafana folder. 147 | -------------------------------------------------------------------------------- /Grafana/unknown.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "editable": true, 16 | "gnetId": null, 17 | "graphTooltip": 0, 18 | "id": 17, 19 | "links": [], 20 | "panels": [ 21 | { 22 | "aliasColors": {}, 23 | "bars": false, 24 | "dashLength": 10, 25 | "dashes": false, 26 | "datasource": "ruuvi", 27 | "fieldConfig": { 28 | "defaults": { 29 | "custom": {} 30 | }, 31 | "overrides": [] 32 | }, 33 | "fill": 0, 34 | "fillGradient": 0, 35 | "gridPos": { 36 | "h": 11, 37 | "w": 24, 38 | "x": 0, 39 | "y": 0 40 | }, 41 | "hiddenSeries": false, 42 | "id": 2, 43 | "legend": { 44 | "avg": false, 45 | "current": false, 46 | "max": false, 47 | "min": false, 48 | "show": true, 49 | "total": false, 50 | "values": false 51 | }, 52 | "lines": true, 53 | "linewidth": 1, 54 | "nullPointMode": "null", 55 | "percentage": false, 56 | "pluginVersion": "7.1.1", 57 | "pointradius": 2, 58 | "points": false, 59 | "renderer": "flot", 60 | "seriesOverrides": [], 61 | "spaceLength": 10, 62 | "stack": false, 63 | "steppedLine": false, 64 | "targets": [ 65 | { 66 | "alias": "$tag_mac", 67 | "groupBy": [ 68 | { 69 | "params": [ 70 | "$__interval" 71 | ], 72 | "type": "time" 73 | }, 74 | { 75 | "params": [ 76 | "mac" 77 | ], 78 | "type": "tag" 79 | }, 80 | { 81 | "params": [ 82 | "null" 83 | ], 84 | "type": "fill" 85 | } 86 | ], 87 | "measurement": "unknown_measurements", 88 | "orderByTime": "ASC", 89 | "policy": "autogen", 90 | "refId": "A", 91 | "resultFormat": "time_series", 92 | "select": [ 93 | [ 94 | { 95 | "params": [ 96 | "rssi" 97 | ], 98 | "type": "field" 99 | }, 100 | { 101 | "params": [], 102 | "type": "mean" 103 | } 104 | ] 105 | ], 106 | "tags": [] 107 | } 108 | ], 109 | "thresholds": [], 110 | "timeFrom": null, 111 | "timeRegions": [], 112 | "timeShift": null, 113 | "title": "RSSI", 114 | "tooltip": { 115 | "shared": true, 116 | "sort": 0, 117 | "value_type": "individual" 118 | }, 119 | "type": "graph", 120 | "xaxis": { 121 | "buckets": null, 122 | "mode": "time", 123 | "name": null, 124 | "show": true, 125 | "values": [] 126 | }, 127 | "yaxes": [ 128 | { 129 | "format": "dB", 130 | "label": null, 131 | "logBase": 1, 132 | "max": null, 133 | "min": null, 134 | "show": true 135 | }, 136 | { 137 | "format": "short", 138 | "label": null, 139 | "logBase": 1, 140 | "max": null, 141 | "min": null, 142 | "show": true 143 | } 144 | ], 145 | "yaxis": { 146 | "align": false, 147 | "alignLevel": null 148 | } 149 | }, 150 | { 151 | "aliasColors": {}, 152 | "bars": false, 153 | "dashLength": 10, 154 | "dashes": false, 155 | "datasource": "ruuvi", 156 | "fieldConfig": { 157 | "defaults": { 158 | "custom": {} 159 | }, 160 | "overrides": [] 161 | }, 162 | "fill": 0, 163 | "fillGradient": 0, 164 | "gridPos": { 165 | "h": 9, 166 | "w": 24, 167 | "x": 0, 168 | "y": 11 169 | }, 170 | "hiddenSeries": false, 171 | "id": 4, 172 | "legend": { 173 | "avg": false, 174 | "current": false, 175 | "max": false, 176 | "min": false, 177 | "show": true, 178 | "total": false, 179 | "values": false 180 | }, 181 | "lines": true, 182 | "linewidth": 1, 183 | "nullPointMode": "null", 184 | "percentage": false, 185 | "pluginVersion": "7.1.1", 186 | "pointradius": 2, 187 | "points": false, 188 | "renderer": "flot", 189 | "seriesOverrides": [], 190 | "spaceLength": 10, 191 | "stack": false, 192 | "steppedLine": false, 193 | "targets": [ 194 | { 195 | "alias": "$tag_mac", 196 | "groupBy": [ 197 | { 198 | "params": [ 199 | "$__interval" 200 | ], 201 | "type": "time" 202 | }, 203 | { 204 | "params": [ 205 | "mac" 206 | ], 207 | "type": "tag" 208 | }, 209 | { 210 | "params": [ 211 | "linear" 212 | ], 213 | "type": "fill" 214 | } 215 | ], 216 | "measurement": "unknown_measurements", 217 | "orderByTime": "ASC", 218 | "policy": "default", 219 | "refId": "A", 220 | "resultFormat": "time_series", 221 | "select": [ 222 | [ 223 | { 224 | "params": [ 225 | "rssiSmooth" 226 | ], 227 | "type": "field" 228 | }, 229 | { 230 | "params": [], 231 | "type": "mean" 232 | } 233 | ] 234 | ], 235 | "tags": [] 236 | } 237 | ], 238 | "thresholds": [], 239 | "timeFrom": null, 240 | "timeRegions": [], 241 | "timeShift": null, 242 | "title": "RSSI Smooth", 243 | "tooltip": { 244 | "shared": true, 245 | "sort": 0, 246 | "value_type": "individual" 247 | }, 248 | "type": "graph", 249 | "xaxis": { 250 | "buckets": null, 251 | "mode": "time", 252 | "name": null, 253 | "show": true, 254 | "values": [] 255 | }, 256 | "yaxes": [ 257 | { 258 | "format": "dB", 259 | "label": null, 260 | "logBase": 1, 261 | "max": null, 262 | "min": null, 263 | "show": true 264 | }, 265 | { 266 | "format": "short", 267 | "label": null, 268 | "logBase": 1, 269 | "max": null, 270 | "min": null, 271 | "show": true 272 | } 273 | ], 274 | "yaxis": { 275 | "align": false, 276 | "alignLevel": null 277 | } 278 | } 279 | ], 280 | "schemaVersion": 26, 281 | "style": "dark", 282 | "tags": [], 283 | "templating": { 284 | "list": [] 285 | }, 286 | "time": { 287 | "from": "now-6h", 288 | "to": "now" 289 | }, 290 | "timepicker": { 291 | "refresh_intervals": [ 292 | "10s", 293 | "30s", 294 | "1m", 295 | "5m", 296 | "15m", 297 | "30m", 298 | "1h", 299 | "2h", 300 | "1d" 301 | ] 302 | }, 303 | "timezone": "", 304 | "title": "Unknown Measurement", 305 | "uid": "sX6kiRHGz", 306 | "version": 1 307 | } -------------------------------------------------------------------------------- /Node-RED Flows/ble2mqtt2influx.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "a57f3dfc.89dab", 4 | "type": "tab", 5 | "label": "BLE2MQTT2INFLUX", 6 | "disabled": false, 7 | "info": "" 8 | }, 9 | { 10 | "id": "7e465370.ac86ac", 11 | "type": "function", 12 | "z": "a57f3dfc.89dab", 13 | "name": "TLM", 14 | "func": "var tokens = msg.topic.split(\"/\");\nmsg.topic =tokens[3]; //get device name from topic level 3 /v1.6/devices/tokens[3]\nvar dest = tokens[tokens.length-1];\ninputjson =JSON.parse(msg.payload);\nvar _fields = {};\nfor(var item in inputjson){\n if (item.includes('Data') || item.includes(\"ts\")){}\n else\n {\n _fields[item] = inputjson[item];}\n}\n\nvar tags = {mac: inputjson['mac'],\n edgeMAC: inputjson['edgeMAC']} ;\n\nfor(var item in inputjson){\n if (item.includes('name')){\n tags[item] = inputjson[item];\n }\n else{}\n}\n\nmsg.payload = [ \n { //device name as measurement\n measurement: \"tlm_measurements\",\n tags,\n fields: _fields\n }\n ];\n \nreturn msg;", 15 | "outputs": 1, 16 | "noerr": 0, 17 | "x": 890, 18 | "y": 160, 19 | "wires": [ 20 | [ 21 | "1ff35b8c.120f34" 22 | ] 23 | ] 24 | }, 25 | { 26 | "id": "e2254fd.a31b5b", 27 | "type": "switch", 28 | "z": "a57f3dfc.89dab", 29 | "name": "", 30 | "property": "payload.dataFormat", 31 | "propertyType": "msg", 32 | "rules": [ 33 | { 34 | "t": "eq", 35 | "v": "0", 36 | "vt": "str" 37 | }, 38 | { 39 | "t": "eq", 40 | "v": "10", 41 | "vt": "str" 42 | }, 43 | { 44 | "t": "eq", 45 | "v": "11", 46 | "vt": "str" 47 | }, 48 | { 49 | "t": "eq", 50 | "v": "12", 51 | "vt": "str" 52 | }, 53 | { 54 | "t": "eq", 55 | "v": "13", 56 | "vt": "str" 57 | }, 58 | { 59 | "t": "eq", 60 | "v": "3", 61 | "vt": "str" 62 | }, 63 | { 64 | "t": "eq", 65 | "v": "5", 66 | "vt": "str" 67 | } 68 | ], 69 | "checkall": "true", 70 | "repair": false, 71 | "outputs": 7, 72 | "x": 525, 73 | "y": 156, 74 | "wires": [ 75 | [ 76 | "480aa838.c33728" 77 | ], 78 | [ 79 | "36043169.43a1be" 80 | ], 81 | [ 82 | "36043169.43a1be" 83 | ], 84 | [ 85 | "c2d87d46.d51e2" 86 | ], 87 | [ 88 | "36043169.43a1be" 89 | ], 90 | [ 91 | "36043169.43a1be" 92 | ], 93 | [ 94 | "36043169.43a1be" 95 | ] 96 | ] 97 | }, 98 | { 99 | "id": "6fb1e240.3c091c", 100 | "type": "json", 101 | "z": "a57f3dfc.89dab", 102 | "name": "", 103 | "property": "payload", 104 | "action": "obj", 105 | "pretty": false, 106 | "x": 355, 107 | "y": 156, 108 | "wires": [ 109 | [ 110 | "e2254fd.a31b5b" 111 | ] 112 | ] 113 | }, 114 | { 115 | "id": "c2d87d46.d51e2", 116 | "type": "json", 117 | "z": "a57f3dfc.89dab", 118 | "name": "", 119 | "property": "payload", 120 | "action": "", 121 | "pretty": false, 122 | "x": 730, 123 | "y": 160, 124 | "wires": [ 125 | [ 126 | "7e465370.ac86ac" 127 | ] 128 | ] 129 | }, 130 | { 131 | "id": "40e0dfdb.d14b6", 132 | "type": "function", 133 | "z": "a57f3dfc.89dab", 134 | "name": "Ruuvi", 135 | "func": "var tokens = msg.topic.split(\"/\");\nmsg.topic =tokens[3]; //get device name from topic level 3 /v1.6/devices/tokens[3]\ninputjson =JSON.parse(msg.payload);\nvar _fields = {};\nfor(var item in inputjson){\n if (item.includes('Data') || item.includes(\"ts\")){}\n else\n {\n _fields[item] = inputjson[item];}\n}\n\nvar tags = {mac: inputjson['mac'],\n edgeMAC: inputjson['edgeMAC']} ;\n\nfor(var item in inputjson){\n if (item.includes('name')){\n tags[item] = inputjson[item];\n }\n else{}\n}\n\nmsg.payload = [ \n {\n measurement: \"ruuvi_measurements\" ,\n tags, //device name as measurement\n fields: _fields\n },\n ];\n \nreturn msg;", 136 | "outputs": 1, 137 | "noerr": 0, 138 | "x": 890, 139 | "y": 200, 140 | "wires": [ 141 | [ 142 | "1ff35b8c.120f34" 143 | ] 144 | ] 145 | }, 146 | { 147 | "id": "36043169.43a1be", 148 | "type": "json", 149 | "z": "a57f3dfc.89dab", 150 | "name": "", 151 | "property": "payload", 152 | "action": "", 153 | "pretty": false, 154 | "x": 730, 155 | "y": 200, 156 | "wires": [ 157 | [ 158 | "40e0dfdb.d14b6" 159 | ] 160 | ] 161 | }, 162 | { 163 | "id": "79cba41e.e517fc", 164 | "type": "mqtt in", 165 | "z": "a57f3dfc.89dab", 166 | "name": "", 167 | "topic": "home/+/#", 168 | "qos": "0", 169 | "datatype": "auto", 170 | "broker": "7ea27770.f00488", 171 | "x": 80, 172 | "y": 160, 173 | "wires": [ 174 | [ 175 | "6fb1e240.3c091c" 176 | ] 177 | ] 178 | }, 179 | { 180 | "id": "2dc486c4.e8a40a", 181 | "type": "comment", 182 | "z": "a57f3dfc.89dab", 183 | "name": "Beacons", 184 | "info": "Change home to location being used", 185 | "x": 80, 186 | "y": 120, 187 | "wires": [] 188 | }, 189 | { 190 | "id": "480aa838.c33728", 191 | "type": "json", 192 | "z": "a57f3dfc.89dab", 193 | "name": "", 194 | "property": "payload", 195 | "action": "str", 196 | "pretty": false, 197 | "x": 725, 198 | "y": 116, 199 | "wires": [ 200 | [ 201 | "92609fef.caa72" 202 | ] 203 | ] 204 | }, 205 | { 206 | "id": "92609fef.caa72", 207 | "type": "function", 208 | "z": "a57f3dfc.89dab", 209 | "name": "Unknown", 210 | "func": "var tokens = msg.topic.split(\"/\");\nmsg.topic =tokens[3]; //get device name from topic level 3 /v1.6/devices/tokens[3]\nvar dest = tokens[tokens.length-1];\ninputjson =JSON.parse(msg.payload);\nvar _fields = {};\nfor(var item in inputjson){\n if (item.includes('Data') || item.includes(\"ts\")){}\n else\n {\n _fields[item] = inputjson[item];}\n}\n\nvar tags = {mac: inputjson['mac'],\n edgeMAC: inputjson['edgeMAC']} ;\n\nfor(var item in inputjson){\n if (item.includes('name')){\n tags[item] = inputjson[item];\n }\n else{}\n}\n\nmsg.payload = [ \n { //device name as measurement\n measurement: \"unknown_measurements\",\n tags,\n fields: _fields\n }\n ];\n \nreturn msg;", 211 | "outputs": 1, 212 | "noerr": 0, 213 | "x": 895, 214 | "y": 116, 215 | "wires": [ 216 | [ 217 | "1ff35b8c.120f34" 218 | ] 219 | ] 220 | }, 221 | { 222 | "id": "1ff35b8c.120f34", 223 | "type": "influxdb batch", 224 | "z": "a57f3dfc.89dab", 225 | "influxdb": "c86ff7aa.877878", 226 | "precision": "", 227 | "retentionPolicy": "", 228 | "name": "", 229 | "x": 1150, 230 | "y": 180, 231 | "wires": [] 232 | }, 233 | { 234 | "id": "7f97c1e4.88eed", 235 | "type": "mqtt in", 236 | "z": "a57f3dfc.89dab", 237 | "name": "", 238 | "topic": "heartbeat/#", 239 | "qos": "0", 240 | "datatype": "auto", 241 | "broker": "7ea27770.f00488", 242 | "x": 90, 243 | "y": 300, 244 | "wires": [ 245 | [ 246 | "953d51ca.f4144" 247 | ] 248 | ] 249 | }, 250 | { 251 | "id": "fee00e0.05672f", 252 | "type": "comment", 253 | "z": "a57f3dfc.89dab", 254 | "name": "Heartbeat", 255 | "info": "", 256 | "x": 80, 257 | "y": 240, 258 | "wires": [] 259 | }, 260 | { 261 | "id": "953d51ca.f4144", 262 | "type": "function", 263 | "z": "a57f3dfc.89dab", 264 | "name": "Heartbeat", 265 | "func": "var tokens = msg.topic.split(\"/\");\nmsg.topic =tokens[3]; //get device name from topic level 3 /v1.6/devices/tokens[3]\nvar dest = tokens[tokens.length-1];\ninputjson =JSON.parse(msg.payload);\nvar _fields = {};\nfor(var item in inputjson){\n if (item.includes('Data') || item.includes(\"ts\")){}\n else\n {\n _fields[item] = inputjson[item];}\n}\n\nvar tags = {\n edgeMac: inputjson['edgeMAC'],\n edgeType: inputjson['type']\n } ;\n\nmsg.payload = [ \n {\n measurement: \"heartbeat_measurements\",\n tags, //device name as measurement\n fields: _fields\n },\n ];\n \nreturn msg;", 266 | "outputs": 1, 267 | "noerr": 0, 268 | "x": 880, 269 | "y": 300, 270 | "wires": [ 271 | [ 272 | "1ff35b8c.120f34" 273 | ] 274 | ] 275 | }, 276 | { 277 | "id": "7ea27770.f00488", 278 | "type": "mqtt-broker", 279 | "z": "", 280 | "name": "mqtt", 281 | "broker": "192.168.8.1", 282 | "port": "1883", 283 | "clientid": "", 284 | "usetls": false, 285 | "compatmode": true, 286 | "keepalive": "60", 287 | "cleansession": true, 288 | "birthTopic": "", 289 | "birthQos": "0", 290 | "birthPayload": "", 291 | "closeTopic": "", 292 | "closeQos": "0", 293 | "closePayload": "", 294 | "willTopic": "", 295 | "willQos": "0", 296 | "willPayload": "" 297 | }, 298 | { 299 | "id": "c86ff7aa.877878", 300 | "type": "influxdb", 301 | "z": "", 302 | "hostname": "192.168.8.1", 303 | "port": "8086", 304 | "protocol": "http", 305 | "database": "ruuvi", 306 | "name": "ruuvi", 307 | "usetls": false, 308 | "tls": "e3ca172.d4b94e8" 309 | }, 310 | { 311 | "id": "e3ca172.d4b94e8", 312 | "type": "tls-config", 313 | "z": "", 314 | "name": "local-tls", 315 | "cert": "", 316 | "key": "", 317 | "ca": "", 318 | "certname": "", 319 | "keyname": "", 320 | "caname": "", 321 | "verifyservercert": false 322 | } 323 | ] -------------------------------------------------------------------------------- /Grafana/heartbeat.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "editable": true, 16 | "gnetId": null, 17 | "graphTooltip": 0, 18 | "id": 3, 19 | "links": [], 20 | "panels": [ 21 | { 22 | "aliasColors": {}, 23 | "bars": false, 24 | "cacheTimeout": null, 25 | "dashLength": 10, 26 | "dashes": false, 27 | "datasource": "Linux", 28 | "fill": 1, 29 | "fillGradient": 0, 30 | "gridPos": { 31 | "h": 9, 32 | "w": 12, 33 | "x": 0, 34 | "y": 0 35 | }, 36 | "id": 6, 37 | "legend": { 38 | "avg": false, 39 | "current": false, 40 | "max": false, 41 | "min": false, 42 | "show": true, 43 | "total": false, 44 | "values": false 45 | }, 46 | "lines": true, 47 | "linewidth": 1, 48 | "links": [], 49 | "nullPointMode": "null", 50 | "options": { 51 | "dataLinks": [] 52 | }, 53 | "percentage": false, 54 | "pluginVersion": "6.3.6", 55 | "pointradius": 2, 56 | "points": false, 57 | "renderer": "flot", 58 | "seriesOverrides": [], 59 | "spaceLength": 10, 60 | "stack": false, 61 | "steppedLine": false, 62 | "targets": [ 63 | { 64 | "alias": "Mac: $tag_edgeMac", 65 | "groupBy": [ 66 | { 67 | "params": [ 68 | "$__interval" 69 | ], 70 | "type": "time" 71 | }, 72 | { 73 | "params": [ 74 | "edgeMac" 75 | ], 76 | "type": "tag" 77 | }, 78 | { 79 | "params": [ 80 | "linear" 81 | ], 82 | "type": "fill" 83 | } 84 | ], 85 | "measurement": "linuxHealth", 86 | "orderByTime": "ASC", 87 | "policy": "default", 88 | "refId": "A", 89 | "resultFormat": "time_series", 90 | "select": [ 91 | [ 92 | { 93 | "params": [ 94 | "uptime" 95 | ], 96 | "type": "field" 97 | }, 98 | { 99 | "params": [], 100 | "type": "mean" 101 | } 102 | ] 103 | ], 104 | "tags": [] 105 | } 106 | ], 107 | "thresholds": [], 108 | "timeFrom": null, 109 | "timeRegions": [], 110 | "timeShift": null, 111 | "title": "Uptime", 112 | "tooltip": { 113 | "shared": true, 114 | "sort": 0, 115 | "value_type": "individual" 116 | }, 117 | "type": "graph", 118 | "xaxis": { 119 | "buckets": null, 120 | "mode": "time", 121 | "name": null, 122 | "show": true, 123 | "values": [] 124 | }, 125 | "yaxes": [ 126 | { 127 | "format": "s", 128 | "label": null, 129 | "logBase": 1, 130 | "max": null, 131 | "min": null, 132 | "show": true 133 | }, 134 | { 135 | "format": "short", 136 | "label": null, 137 | "logBase": 1, 138 | "max": null, 139 | "min": null, 140 | "show": true 141 | } 142 | ], 143 | "yaxis": { 144 | "align": false, 145 | "alignLevel": null 146 | } 147 | }, 148 | { 149 | "aliasColors": {}, 150 | "bars": false, 151 | "cacheTimeout": null, 152 | "dashLength": 10, 153 | "dashes": false, 154 | "datasource": "Linux", 155 | "fill": 1, 156 | "fillGradient": 0, 157 | "gridPos": { 158 | "h": 9, 159 | "w": 12, 160 | "x": 12, 161 | "y": 0 162 | }, 163 | "id": 7, 164 | "legend": { 165 | "avg": false, 166 | "current": false, 167 | "max": false, 168 | "min": false, 169 | "show": true, 170 | "total": false, 171 | "values": false 172 | }, 173 | "lines": true, 174 | "linewidth": 1, 175 | "links": [], 176 | "nullPointMode": "null", 177 | "options": { 178 | "dataLinks": [] 179 | }, 180 | "percentage": false, 181 | "pluginVersion": "6.3.6", 182 | "pointradius": 2, 183 | "points": false, 184 | "renderer": "flot", 185 | "seriesOverrides": [], 186 | "spaceLength": 10, 187 | "stack": false, 188 | "steppedLine": false, 189 | "targets": [ 190 | { 191 | "alias": "Mac: $tag_edgeMac", 192 | "groupBy": [ 193 | { 194 | "params": [ 195 | "$__interval" 196 | ], 197 | "type": "time" 198 | }, 199 | { 200 | "params": [ 201 | "edgeMac" 202 | ], 203 | "type": "tag" 204 | }, 205 | { 206 | "params": [ 207 | "linear" 208 | ], 209 | "type": "fill" 210 | } 211 | ], 212 | "measurement": "linuxHealth", 213 | "orderByTime": "ASC", 214 | "policy": "default", 215 | "refId": "A", 216 | "resultFormat": "time_series", 217 | "select": [ 218 | [ 219 | { 220 | "params": [ 221 | "cpu" 222 | ], 223 | "type": "field" 224 | }, 225 | { 226 | "params": [], 227 | "type": "mean" 228 | } 229 | ] 230 | ], 231 | "tags": [] 232 | } 233 | ], 234 | "thresholds": [], 235 | "timeFrom": null, 236 | "timeRegions": [], 237 | "timeShift": null, 238 | "title": "CPU Usage", 239 | "tooltip": { 240 | "shared": true, 241 | "sort": 0, 242 | "value_type": "individual" 243 | }, 244 | "type": "graph", 245 | "xaxis": { 246 | "buckets": null, 247 | "mode": "time", 248 | "name": null, 249 | "show": true, 250 | "values": [] 251 | }, 252 | "yaxes": [ 253 | { 254 | "format": "percent", 255 | "label": null, 256 | "logBase": 1, 257 | "max": null, 258 | "min": null, 259 | "show": true 260 | }, 261 | { 262 | "format": "short", 263 | "label": null, 264 | "logBase": 1, 265 | "max": null, 266 | "min": null, 267 | "show": true 268 | } 269 | ], 270 | "yaxis": { 271 | "align": false, 272 | "alignLevel": null 273 | } 274 | }, 275 | { 276 | "aliasColors": {}, 277 | "bars": false, 278 | "cacheTimeout": null, 279 | "dashLength": 10, 280 | "dashes": false, 281 | "datasource": "Linux", 282 | "fill": 1, 283 | "fillGradient": 0, 284 | "gridPos": { 285 | "h": 9, 286 | "w": 12, 287 | "x": 0, 288 | "y": 9 289 | }, 290 | "id": 8, 291 | "legend": { 292 | "alignAsTable": false, 293 | "avg": false, 294 | "current": false, 295 | "max": false, 296 | "min": false, 297 | "show": true, 298 | "total": false, 299 | "values": false 300 | }, 301 | "lines": true, 302 | "linewidth": 1, 303 | "links": [], 304 | "nullPointMode": "null", 305 | "options": { 306 | "dataLinks": [] 307 | }, 308 | "percentage": false, 309 | "pluginVersion": "6.3.6", 310 | "pointradius": 2, 311 | "points": false, 312 | "renderer": "flot", 313 | "seriesOverrides": [], 314 | "spaceLength": 10, 315 | "stack": false, 316 | "steppedLine": false, 317 | "targets": [ 318 | { 319 | "alias": "Mac: $tag_edgeMac", 320 | "groupBy": [ 321 | { 322 | "params": [ 323 | "$__interval" 324 | ], 325 | "type": "time" 326 | }, 327 | { 328 | "params": [ 329 | "edgeMac" 330 | ], 331 | "type": "tag" 332 | }, 333 | { 334 | "params": [ 335 | "linear" 336 | ], 337 | "type": "fill" 338 | } 339 | ], 340 | "measurement": "linuxHealth", 341 | "orderByTime": "ASC", 342 | "policy": "default", 343 | "refId": "A", 344 | "resultFormat": "time_series", 345 | "select": [ 346 | [ 347 | { 348 | "params": [ 349 | "usedMemory" 350 | ], 351 | "type": "field" 352 | }, 353 | { 354 | "params": [], 355 | "type": "mean" 356 | } 357 | ] 358 | ], 359 | "tags": [] 360 | } 361 | ], 362 | "thresholds": [], 363 | "timeFrom": null, 364 | "timeRegions": [], 365 | "timeShift": null, 366 | "title": "Memory Used", 367 | "tooltip": { 368 | "shared": true, 369 | "sort": 0, 370 | "value_type": "individual" 371 | }, 372 | "transparent": true, 373 | "type": "graph", 374 | "xaxis": { 375 | "buckets": null, 376 | "mode": "time", 377 | "name": null, 378 | "show": true, 379 | "values": [] 380 | }, 381 | "yaxes": [ 382 | { 383 | "format": "decbytes", 384 | "label": null, 385 | "logBase": 1, 386 | "max": null, 387 | "min": null, 388 | "show": true 389 | }, 390 | { 391 | "format": "short", 392 | "label": null, 393 | "logBase": 1, 394 | "max": null, 395 | "min": null, 396 | "show": true 397 | } 398 | ], 399 | "yaxis": { 400 | "align": false, 401 | "alignLevel": null 402 | } 403 | }, 404 | { 405 | "cacheTimeout": null, 406 | "datasource": "Linux", 407 | "gridPos": { 408 | "h": 9, 409 | "w": 12, 410 | "x": 12, 411 | "y": 9 412 | }, 413 | "id": 9, 414 | "links": [], 415 | "options": { 416 | "fieldOptions": { 417 | "calcs": [ 418 | "mean" 419 | ], 420 | "defaults": { 421 | "decimals": 2, 422 | "mappings": [], 423 | "max": 100, 424 | "min": 0, 425 | "thresholds": [ 426 | { 427 | "color": "green", 428 | "value": null 429 | }, 430 | { 431 | "color": "#EAB839", 432 | "value": 60 433 | }, 434 | { 435 | "color": "red", 436 | "value": 80 437 | } 438 | ], 439 | "unit": "percent" 440 | }, 441 | "override": {}, 442 | "values": false 443 | }, 444 | "orientation": "horizontal", 445 | "showThresholdLabels": false, 446 | "showThresholdMarkers": true 447 | }, 448 | "pluginVersion": "6.3.6", 449 | "targets": [ 450 | { 451 | "alias": "Mac: $tag_edgeMac", 452 | "groupBy": [ 453 | { 454 | "params": [ 455 | "$__interval" 456 | ], 457 | "type": "time" 458 | }, 459 | { 460 | "params": [ 461 | "edgeMac" 462 | ], 463 | "type": "tag" 464 | }, 465 | { 466 | "params": [ 467 | "linear" 468 | ], 469 | "type": "fill" 470 | } 471 | ], 472 | "measurement": "linuxHealth", 473 | "orderByTime": "ASC", 474 | "policy": "default", 475 | "refId": "A", 476 | "resultFormat": "time_series", 477 | "select": [ 478 | [ 479 | { 480 | "params": [ 481 | "percentUsedMemory" 482 | ], 483 | "type": "field" 484 | }, 485 | { 486 | "params": [], 487 | "type": "mean" 488 | } 489 | ] 490 | ], 491 | "tags": [] 492 | } 493 | ], 494 | "timeFrom": null, 495 | "timeShift": null, 496 | "title": "Memory Used %", 497 | "type": "gauge" 498 | } 499 | ], 500 | "refresh": "10s", 501 | "schemaVersion": 19, 502 | "style": "dark", 503 | "tags": [], 504 | "templating": { 505 | "list": [] 506 | }, 507 | "time": { 508 | "from": "now-6h", 509 | "to": "now" 510 | }, 511 | "timepicker": { 512 | "refresh_intervals": [ 513 | "5s", 514 | "10s", 515 | "30s", 516 | "1m", 517 | "5m", 518 | "15m", 519 | "30m", 520 | "1h", 521 | "2h", 522 | "1d" 523 | ] 524 | }, 525 | "timezone": "", 526 | "title": "Board Health", 527 | "uid": "4IvnakhWk", 528 | "version": 25 529 | } -------------------------------------------------------------------------------- /Grafana/tlm.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "editable": true, 16 | "gnetId": null, 17 | "graphTooltip": 0, 18 | "id": 16, 19 | "links": [], 20 | "panels": [ 21 | { 22 | "aliasColors": {}, 23 | "bars": false, 24 | "dashLength": 10, 25 | "dashes": false, 26 | "datasource": "ruuvi", 27 | "fieldConfig": { 28 | "defaults": { 29 | "custom": {} 30 | }, 31 | "overrides": [] 32 | }, 33 | "fill": 0, 34 | "fillGradient": 0, 35 | "gridPos": { 36 | "h": 8, 37 | "w": 12, 38 | "x": 0, 39 | "y": 0 40 | }, 41 | "hiddenSeries": false, 42 | "id": 8, 43 | "legend": { 44 | "avg": false, 45 | "current": false, 46 | "max": false, 47 | "min": false, 48 | "show": true, 49 | "total": false, 50 | "values": false 51 | }, 52 | "lines": true, 53 | "linewidth": 1, 54 | "nullPointMode": "null", 55 | "percentage": false, 56 | "pluginVersion": "7.1.1", 57 | "pointradius": 2, 58 | "points": false, 59 | "renderer": "flot", 60 | "seriesOverrides": [], 61 | "spaceLength": 10, 62 | "stack": false, 63 | "steppedLine": false, 64 | "targets": [ 65 | { 66 | "alias": "$tag_mac", 67 | "groupBy": [ 68 | { 69 | "params": [ 70 | "$__interval" 71 | ], 72 | "type": "time" 73 | }, 74 | { 75 | "params": [ 76 | "mac" 77 | ], 78 | "type": "tag" 79 | }, 80 | { 81 | "params": [ 82 | "linear" 83 | ], 84 | "type": "fill" 85 | } 86 | ], 87 | "measurement": "tlm_measurements", 88 | "orderByTime": "ASC", 89 | "policy": "autogen", 90 | "refId": "A", 91 | "resultFormat": "time_series", 92 | "select": [ 93 | [ 94 | { 95 | "params": [ 96 | "rssiSmooth" 97 | ], 98 | "type": "field" 99 | }, 100 | { 101 | "params": [], 102 | "type": "mean" 103 | } 104 | ] 105 | ], 106 | "tags": [] 107 | } 108 | ], 109 | "thresholds": [], 110 | "timeFrom": null, 111 | "timeRegions": [], 112 | "timeShift": null, 113 | "title": "RSSI Smooth", 114 | "tooltip": { 115 | "shared": true, 116 | "sort": 0, 117 | "value_type": "individual" 118 | }, 119 | "type": "graph", 120 | "xaxis": { 121 | "buckets": null, 122 | "mode": "time", 123 | "name": null, 124 | "show": true, 125 | "values": [] 126 | }, 127 | "yaxes": [ 128 | { 129 | "format": "dB", 130 | "label": null, 131 | "logBase": 1, 132 | "max": null, 133 | "min": null, 134 | "show": true 135 | }, 136 | { 137 | "format": "short", 138 | "label": null, 139 | "logBase": 1, 140 | "max": null, 141 | "min": null, 142 | "show": true 143 | } 144 | ], 145 | "yaxis": { 146 | "align": false, 147 | "alignLevel": null 148 | } 149 | }, 150 | { 151 | "aliasColors": {}, 152 | "bars": false, 153 | "dashLength": 10, 154 | "dashes": false, 155 | "datasource": "ruuvi", 156 | "fieldConfig": { 157 | "defaults": { 158 | "custom": {} 159 | }, 160 | "overrides": [] 161 | }, 162 | "fill": 0, 163 | "fillGradient": 0, 164 | "gridPos": { 165 | "h": 8, 166 | "w": 12, 167 | "x": 12, 168 | "y": 0 169 | }, 170 | "hiddenSeries": false, 171 | "id": 4, 172 | "legend": { 173 | "avg": false, 174 | "current": false, 175 | "max": false, 176 | "min": false, 177 | "show": true, 178 | "total": false, 179 | "values": false 180 | }, 181 | "lines": true, 182 | "linewidth": 1, 183 | "nullPointMode": "null", 184 | "percentage": false, 185 | "pluginVersion": "7.1.1", 186 | "pointradius": 2, 187 | "points": false, 188 | "renderer": "flot", 189 | "seriesOverrides": [], 190 | "spaceLength": 10, 191 | "stack": false, 192 | "steppedLine": false, 193 | "targets": [ 194 | { 195 | "alias": "$tag_mac", 196 | "groupBy": [ 197 | { 198 | "params": [ 199 | "$__interval" 200 | ], 201 | "type": "time" 202 | }, 203 | { 204 | "params": [ 205 | "mac" 206 | ], 207 | "type": "tag" 208 | }, 209 | { 210 | "params": [ 211 | "linear" 212 | ], 213 | "type": "fill" 214 | } 215 | ], 216 | "measurement": "tlm_measurements", 217 | "orderByTime": "ASC", 218 | "policy": "autogen", 219 | "refId": "A", 220 | "resultFormat": "time_series", 221 | "select": [ 222 | [ 223 | { 224 | "params": [ 225 | "batteryVoltage" 226 | ], 227 | "type": "field" 228 | }, 229 | { 230 | "params": [], 231 | "type": "mean" 232 | } 233 | ] 234 | ], 235 | "tags": [] 236 | } 237 | ], 238 | "thresholds": [], 239 | "timeFrom": null, 240 | "timeRegions": [], 241 | "timeShift": null, 242 | "title": "Battery", 243 | "tooltip": { 244 | "shared": true, 245 | "sort": 0, 246 | "value_type": "individual" 247 | }, 248 | "type": "graph", 249 | "xaxis": { 250 | "buckets": null, 251 | "mode": "time", 252 | "name": null, 253 | "show": true, 254 | "values": [] 255 | }, 256 | "yaxes": [ 257 | { 258 | "format": "volt", 259 | "label": null, 260 | "logBase": 1, 261 | "max": null, 262 | "min": null, 263 | "show": true 264 | }, 265 | { 266 | "format": "short", 267 | "label": null, 268 | "logBase": 1, 269 | "max": null, 270 | "min": null, 271 | "show": true 272 | } 273 | ], 274 | "yaxis": { 275 | "align": false, 276 | "alignLevel": null 277 | } 278 | }, 279 | { 280 | "aliasColors": {}, 281 | "bars": false, 282 | "dashLength": 10, 283 | "dashes": false, 284 | "datasource": "ruuvi", 285 | "fieldConfig": { 286 | "defaults": { 287 | "custom": {} 288 | }, 289 | "overrides": [] 290 | }, 291 | "fill": 1, 292 | "fillGradient": 0, 293 | "gridPos": { 294 | "h": 9, 295 | "w": 12, 296 | "x": 0, 297 | "y": 8 298 | }, 299 | "hiddenSeries": false, 300 | "id": 2, 301 | "legend": { 302 | "avg": false, 303 | "current": false, 304 | "max": false, 305 | "min": false, 306 | "show": true, 307 | "total": false, 308 | "values": false 309 | }, 310 | "lines": true, 311 | "linewidth": 1, 312 | "nullPointMode": "null", 313 | "percentage": false, 314 | "pluginVersion": "7.1.1", 315 | "pointradius": 2, 316 | "points": false, 317 | "renderer": "flot", 318 | "seriesOverrides": [], 319 | "spaceLength": 10, 320 | "stack": false, 321 | "steppedLine": false, 322 | "targets": [ 323 | { 324 | "alias": "$tag_mac", 325 | "groupBy": [ 326 | { 327 | "params": [ 328 | "$__interval" 329 | ], 330 | "type": "time" 331 | }, 332 | { 333 | "params": [ 334 | "mac" 335 | ], 336 | "type": "tag" 337 | }, 338 | { 339 | "params": [ 340 | "linear" 341 | ], 342 | "type": "fill" 343 | } 344 | ], 345 | "measurement": "tlm_measurements", 346 | "orderByTime": "ASC", 347 | "policy": "autogen", 348 | "refId": "A", 349 | "resultFormat": "time_series", 350 | "select": [ 351 | [ 352 | { 353 | "params": [ 354 | "temperature" 355 | ], 356 | "type": "field" 357 | }, 358 | { 359 | "params": [], 360 | "type": "mean" 361 | } 362 | ] 363 | ], 364 | "tags": [] 365 | } 366 | ], 367 | "thresholds": [], 368 | "timeFrom": null, 369 | "timeRegions": [], 370 | "timeShift": null, 371 | "title": "Temperature", 372 | "tooltip": { 373 | "shared": true, 374 | "sort": 0, 375 | "value_type": "individual" 376 | }, 377 | "type": "graph", 378 | "xaxis": { 379 | "buckets": null, 380 | "mode": "time", 381 | "name": null, 382 | "show": true, 383 | "values": [] 384 | }, 385 | "yaxes": [ 386 | { 387 | "format": "celsius", 388 | "label": null, 389 | "logBase": 1, 390 | "max": null, 391 | "min": null, 392 | "show": true 393 | }, 394 | { 395 | "format": "short", 396 | "label": null, 397 | "logBase": 1, 398 | "max": null, 399 | "min": null, 400 | "show": true 401 | } 402 | ], 403 | "yaxis": { 404 | "align": false, 405 | "alignLevel": null 406 | } 407 | }, 408 | { 409 | "aliasColors": {}, 410 | "bars": false, 411 | "dashLength": 10, 412 | "dashes": false, 413 | "datasource": "ruuvi", 414 | "fieldConfig": { 415 | "defaults": { 416 | "custom": {} 417 | }, 418 | "overrides": [] 419 | }, 420 | "fill": 0, 421 | "fillGradient": 0, 422 | "gridPos": { 423 | "h": 8, 424 | "w": 12, 425 | "x": 0, 426 | "y": 17 427 | }, 428 | "hiddenSeries": false, 429 | "id": 6, 430 | "legend": { 431 | "avg": false, 432 | "current": false, 433 | "max": false, 434 | "min": false, 435 | "show": true, 436 | "total": false, 437 | "values": false 438 | }, 439 | "lines": true, 440 | "linewidth": 1, 441 | "nullPointMode": "null", 442 | "percentage": false, 443 | "pluginVersion": "7.1.1", 444 | "pointradius": 2, 445 | "points": false, 446 | "renderer": "flot", 447 | "seriesOverrides": [], 448 | "spaceLength": 10, 449 | "stack": false, 450 | "steppedLine": false, 451 | "targets": [ 452 | { 453 | "alias": "$tag_mac", 454 | "groupBy": [ 455 | { 456 | "params": [ 457 | "$__interval" 458 | ], 459 | "type": "time" 460 | }, 461 | { 462 | "params": [ 463 | "mac" 464 | ], 465 | "type": "tag" 466 | }, 467 | { 468 | "params": [ 469 | "linear" 470 | ], 471 | "type": "fill" 472 | } 473 | ], 474 | "measurement": "tlm_measurements", 475 | "orderByTime": "ASC", 476 | "policy": "autogen", 477 | "refId": "A", 478 | "resultFormat": "time_series", 479 | "select": [ 480 | [ 481 | { 482 | "params": [ 483 | "rssi" 484 | ], 485 | "type": "field" 486 | }, 487 | { 488 | "params": [], 489 | "type": "mean" 490 | } 491 | ] 492 | ], 493 | "tags": [] 494 | } 495 | ], 496 | "thresholds": [], 497 | "timeFrom": null, 498 | "timeRegions": [], 499 | "timeShift": null, 500 | "title": "RSSI", 501 | "tooltip": { 502 | "shared": true, 503 | "sort": 0, 504 | "value_type": "individual" 505 | }, 506 | "type": "graph", 507 | "xaxis": { 508 | "buckets": null, 509 | "mode": "time", 510 | "name": null, 511 | "show": true, 512 | "values": [] 513 | }, 514 | "yaxes": [ 515 | { 516 | "format": "dB", 517 | "label": null, 518 | "logBase": 1, 519 | "max": null, 520 | "min": null, 521 | "show": true 522 | }, 523 | { 524 | "format": "short", 525 | "label": null, 526 | "logBase": 1, 527 | "max": null, 528 | "min": null, 529 | "show": true 530 | } 531 | ], 532 | "yaxis": { 533 | "align": false, 534 | "alignLevel": null 535 | } 536 | } 537 | ], 538 | "schemaVersion": 26, 539 | "style": "dark", 540 | "tags": [], 541 | "templating": { 542 | "list": [] 543 | }, 544 | "time": { 545 | "from": "now-6h", 546 | "to": "now" 547 | }, 548 | "timepicker": { 549 | "refresh_intervals": [ 550 | "10s", 551 | "30s", 552 | "1m", 553 | "5m", 554 | "15m", 555 | "30m", 556 | "1h", 557 | "2h", 558 | "1d" 559 | ] 560 | }, 561 | "timezone": "", 562 | "title": "TLM Mesurements", 563 | "uid": "1y5PzRHGk", 564 | "version": 4 565 | } --------------------------------------------------------------------------------