├── .idea
├── .gitignore
├── MontyHome-Hackers-Guide.iml
├── inspectionProfiles
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── LICENSE
├── README.md
├── ble-request.py
├── requirements.txt
├── sample-projects
├── i2c-display.py
├── ifttt-request.py
└── turn-on-led.py
├── web-dashboard
├── ble_responses.json
├── templates
│ └── dashboard.html
└── your_flask_file.py
└── web-tool.html
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/MontyHome-Hackers-Guide.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 gtls
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Monty Home Device Hacking Guide
4 |
5 | Welcome to the **Monty Home Device Hacking Guide** repository! This guide provides step-by-step instructions for extending the functionality of the Monty Home BLE device using a Raspberry Pi. Originally designed for compost monitoring, the Monty Home device collects valuable data on temperature, humidity, and other environmental metrics. Through this guide, you’ll learn how to retrieve, display, and automate actions based on this data.
6 |
7 | ## Where to Buy Monty Home
8 |
9 | https://montycompost.co/
10 |
11 |
12 | ## Table of Contents
13 |
14 | - [Overview](#overview)
15 | - [Projects](#projects)
16 | - [Project 1: Temperature-Based LED Control](#project-1-temperature-based-led-control)
17 | - [Project 2: Display Temperature and Humidity on I2C Display](#project-2-display-temperature-and-humidity-on-i2c-display)
18 | - [Project 3: Temperature Alert via IFTTT](#project-3-temperature-alert-via-ifttt)
19 | - [Setup](#setup)
20 | - [Hardware Requirements](#hardware-requirements)
21 | - [Software Requirements](#software-requirements)
22 | - [BLE Commands](#ble-commands)
23 | - [Running the Code](#running-the-code)
24 | - [Customization](#customization)
25 | - [Additional Resources](#additional-resources)
26 |
27 | ---
28 |
29 | ## Overview
30 |
31 | This guide is designed for anyone interested in working with Bluetooth Low Energy (BLE) devices, IoT applications, or environmental monitoring. The Monty Home device communicates over BLE, providing real-time data on temperature, humidity, battery level, and more. In this repository, you’ll find three projects that use Python, BLE, and Raspberry Pi to interact with the Monty Home device.
32 |
33 | Each project covers different functionalities:
34 | 1. **Basic LED Control Based on Temperature Thresholds**
35 | 2. **Displaying Data on an OLED Screen Using I2C**
36 | 3. **Sending Notifications via IFTTT When Conditions Are Met**
37 |
38 | ---
39 |
40 | ## Projects
41 |
42 | ### Project 1: Temperature-Based LED Control
43 |
44 | **Objective**: Use the temperature data from the Monty Home device to control an LED on the Raspberry Pi. If the temperature exceeds a specified threshold, the LED lights up to indicate a warning.
45 |
46 | **Skills Gained**:
47 | - Setting up GPIO control for an LED.
48 | - Querying BLE data.
49 | - Basic Python programming and condition handling.
50 |
51 | **Hardware Needed**:
52 | - Raspberry Pi with BLE support
53 | - LED and 330-ohm resistor
54 |
55 | ---
56 |
57 | ### Project 2: Display Temperature and Humidity on I2C Display
58 |
59 | **Objective**: Display real-time temperature and humidity data from the Monty Home device on an OLED screen connected to the Raspberry Pi.
60 |
61 | **Skills Gained**:
62 | - Working with I2C devices.
63 | - Displaying dynamic data using the SSD1306 OLED display.
64 | - Implementing BLE data retrieval and display updates.
65 |
66 | **Hardware Needed**:
67 | - Raspberry Pi with BLE support
68 | - SSD1306 OLED Display (128x32 or 128x64)
69 |
70 | ---
71 |
72 | ### Project 3: Temperature Alert via IFTTT
73 |
74 | **Objective**: Configure the Raspberry Pi to send a notification via IFTTT if the temperature from the Monty Home device exceeds a specific threshold.
75 |
76 | **Skills Gained**:
77 | - Integrating with IFTTT for IoT automation.
78 | - Sending HTTP requests with the `requests` library.
79 | - Combining BLE data with cloud-based notifications.
80 |
81 | **Hardware Needed**:
82 | - Raspberry Pi with Wi-Fi
83 | - IFTTT account
84 |
85 | ---
86 |
87 | ## Setup
88 |
89 | ### Hardware Requirements
90 |
91 | 1. **Raspberry Pi** (Zero 2 or another model with BLE support).
92 | 2. **Monty Home BLE Device**.
93 | 3. Additional hardware specific to each project, such as an LED, OLED display, and IFTTT account.
94 |
95 | ### Software Requirements
96 |
97 | 1. **Raspberry Pi OS**: Install Raspberry Pi OS Lite (for headless) or Raspberry Pi OS with Desktop (for graphical interface).
98 | 2. **Python 3**: Make sure Python 3 and `pip` are installed.
99 | 3. **Libraries**:
100 | - **Bleak** for BLE communication: `pip install bleak`
101 | - **Requests** for IFTTT integration: `pip install requests`
102 | - **Adafruit CircuitPython SSD1306** for OLED control: `pip install adafruit-circuitpython-ssd1306`
103 | - **Pillow** for image manipulation on OLED: `pip install pillow`
104 |
105 | ---
106 |
107 | ## BLE Commands
108 |
109 | Use these commands to interact with the Monty Home device. Each command requests specific data or performs an action. You can replace or modify commands in the Python scripts as needed.
110 |
111 | | Command | Description |
112 | |-----------|---------------------------------------------------------|
113 | | `;QA\r\n` | Returns the index of all data in flash memory. |
114 | | `;QP\r\n` | Returns index of pending data in flash memory. |
115 | | `;QR\r\n` | Returns a record by index, NACK if index not found. |
116 | | `;QS\r\n` | Returns the status of the device. |
117 | | `;QL\r\n` | Returns the battery level as a percentage. |
118 | | `;QT\r\n` | Returns the temperature reading from the NTC sensor. |
119 | | `;QH\r\n` | Returns the relative humidity reading. |
120 | | `;QO\r\n` | Returns the most recent TVOC reading. |
121 | | `;QC\r\n` | Returns the most recent CO2 reading. |
122 | | `;QU\r\n` | Returns the unique ID of the device. |
123 | | `;QV\r\n` | Returns the firmware version of the device. |
124 | | `;CR\r\n` | Reboots the device. |
125 | | `;CF\r\n` | Performs a factory reset. |
126 |
127 | ---
128 |
129 | ## Running the Code
130 |
131 | Each project contains a Python script that establishes a BLE connection, sends queries, and processes data. To run a script:
132 |
133 | 1. Open a terminal on the Raspberry Pi.
134 | 2. Navigate to the project folder:
135 | ```bash
136 | cd /path/to/project
137 | ```
138 | 3. Run the script:
139 | ```bash
140 | python3 project_script.py
141 | ```
142 | Replace `project_script.py` with the actual script file name, such as `project1_temperature_led.py`.
143 |
144 | ---
145 |
146 | ## Customization
147 |
148 | ### Adjusting BLE Commands
149 | You can modify the BLE commands in the code to retrieve different types of data from the Monty Home device. For example, to query humidity instead of temperature, replace:
150 | ```python
151 | command = ";QT\r\n"
152 | ```
153 | with:
154 | ```python
155 | command = ";QH\r\n"
156 | ```
157 |
158 | ### Expanding Notification Handlers
159 | To process multiple types of data (e.g., temperature, humidity), add conditions within the `notification_handler` function to decode and display different readings.
160 |
161 | ### Integrating with Other Platforms
162 | Consider integrating data into IoT platforms or dashboards for real-time data visualization, logging, or further automation.
163 |
164 | ---
165 |
166 | ## Additional Resources
167 |
168 | - [Python on Raspberry Pi](https://realpython.com/python-raspberry-pi/)
169 | - [BLE on Raspberry Pi Guide](https://www.instructables.com/Control-Bluetooth-LE-Devices-From-A-Raspberry-Pi/)
170 | - [IFTTT Webhooks Documentation](https://ifttt.com/maker_webhooks)
171 | - [Adafruit CircuitPython SSD1306 Guide](https://learn.adafruit.com/monochrome-oled-breakouts)
172 |
173 | ---
174 |
175 |
176 | ## Contributing
177 |
178 | Feel free to submit pull requests, report issues, or suggest features. Any contributions to improve this guide and add new projects are welcome!
179 |
180 | ---
181 |
182 | This README provides everything needed for users to get started with BLE communication, project setup, and code customization. Let me know if you need further details or additional sections!
183 |
--------------------------------------------------------------------------------
/ble-request.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from bleak import BleakScanner, BleakClient
3 |
4 |
5 | async def scan_and_select_device():
6 | print("Scanning for BLE devices...")
7 | devices = await BleakScanner.discover()
8 |
9 | if not devices:
10 | print("No devices found.")
11 | return None
12 |
13 | for i, device in enumerate(devices):
14 | print(f"[{i}] {device.name} - {device.address}")
15 |
16 | device_index = int(input("Select the device index to connect: "))
17 | selected_device = devices[device_index]
18 |
19 | return selected_device
20 |
21 |
22 | async def connect_and_prompt_query(device):
23 | print(f"Connecting to {device.name}...")
24 |
25 | async with BleakClient(device.address) as client:
26 | print(f"Connected to {device.name} - {device.address}")
27 |
28 | # Use the services property instead of get_services() to remove the FutureWarning
29 | services = client.services
30 | if not services:
31 | await client.get_services() # Use this to trigger service discovery
32 |
33 | write_characteristic = None
34 | notify_characteristic = None
35 |
36 | # Find the first writable and notify characteristics
37 | for service in services:
38 | for characteristic in service.characteristics:
39 | if "write" in characteristic.properties:
40 | write_characteristic = characteristic
41 | if "notify" in characteristic.properties:
42 | notify_characteristic = characteristic
43 | if write_characteristic and notify_characteristic:
44 | break
45 | if write_characteristic and notify_characteristic:
46 | break
47 |
48 | if not write_characteristic:
49 | print("No writable characteristic found.")
50 | return
51 |
52 | if not notify_characteristic:
53 | print("No notify characteristic found.")
54 | return
55 |
56 | print(f"Using characteristic {write_characteristic.uuid} for sending queries.")
57 | print(f"Using characteristic {notify_characteristic.uuid} for reading responses.")
58 |
59 | # Use client.is_connected as a property to avoid the FutureWarning
60 | if client.is_connected:
61 | def notification_handler(sender, data):
62 | print(f"Received data from {sender}: {data.decode()}")
63 |
64 | # Subscribe to notifications
65 | await client.start_notify(notify_characteristic.uuid, notification_handler)
66 |
67 | while True:
68 | query = input("Enter the BLE query (or 'exit' to quit): ")
69 |
70 | if query.lower() == 'exit':
71 | print("Exiting...")
72 | break
73 |
74 | try:
75 | # Send the query to the BLE device
76 | await client.write_gatt_char(write_characteristic.uuid, query.encode())
77 | print(f"Query '{query}' sent.")
78 |
79 | # Give some time to receive the response
80 | await asyncio.sleep(2)
81 |
82 | except Exception as e:
83 | print(f"Failed to send query: {e}")
84 |
85 | await client.stop_notify(notify_characteristic.uuid)
86 |
87 |
88 | if __name__ == "__main__":
89 | loop = asyncio.get_event_loop()
90 | selected_device = loop.run_until_complete(scan_and_select_device())
91 |
92 | if selected_device:
93 | loop.run_until_complete(connect_and_prompt_query(selected_device))
94 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gtls64/MontyHome-Hackers-Guide/23a58be3aac3bf3e0ba5e6a663896de4da55440e/requirements.txt
--------------------------------------------------------------------------------
/sample-projects/i2c-display.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from datetime import datetime
3 | from bleak import BleakScanner, BleakClient
4 | import time
5 | import board
6 | import busio
7 | from adafruit_ssd1306 import SSD1306_I2C
8 | from PIL import Image, ImageDraw, ImageFont
9 |
10 | # Configuration variables
11 | DEVICE_NAME = "YourDeviceName" # Replace with the exact name of your BLE device
12 | QUERY_DELAY = 30 # Delay in seconds between each query
13 |
14 | # I2C display setup
15 | I2C_WIDTH = 128
16 | I2C_HEIGHT = 32
17 | i2c = busio.I2C(board.SCL, board.SDA)
18 | display = SSD1306_I2C(I2C_WIDTH, I2C_HEIGHT, i2c)
19 |
20 | # Create a blank image to draw on
21 | image = Image.new("1", (I2C_WIDTH, I2C_HEIGHT))
22 | draw = ImageDraw.Draw(image)
23 |
24 | # Font settings
25 | font = ImageFont.load_default()
26 |
27 | # List of queries to send and their corresponding descriptions
28 | queries = {
29 | ";QT\r\n": "NTC Temperature",
30 | ";QH\r\n": "Humidity"
31 | }
32 |
33 | # Store the responses for querying
34 | response_data = {}
35 |
36 | async def find_device_by_name():
37 | print(f"Searching for BLE device named '{DEVICE_NAME}'...")
38 | devices = await BleakScanner.discover()
39 |
40 | for device in devices:
41 | if device.name == DEVICE_NAME:
42 | print(f"Device found: {device.name} - {device.address}")
43 | return device
44 | print("Device not found.")
45 | return None
46 |
47 | async def connect_and_send_queries(device):
48 | async with BleakClient(device.address) as client:
49 | print(f"Connected to {device.name} - {device.address}")
50 |
51 | services = client.services
52 | if not services:
53 | await client.get_services()
54 |
55 | write_characteristic = None
56 | notify_characteristic = None
57 |
58 | # Find writable and notify characteristics
59 | for service in services:
60 | for characteristic in service.characteristics:
61 | if "write" in characteristic.properties:
62 | write_characteristic = characteristic
63 | if "notify" in characteristic.properties:
64 | notify_characteristic = characteristic
65 | if write_characteristic and notify_characteristic:
66 | break
67 | if write_characteristic and notify_characteristic:
68 | break
69 |
70 | if not write_characteristic or not notify_characteristic:
71 | print("Necessary characteristics not found.")
72 | return None, None
73 |
74 | query_iterator = iter(queries.items())
75 | received_response_event = asyncio.Event()
76 |
77 | # Notification handler to process data
78 | def notification_handler(sender, data):
79 | response_str = data.decode().strip()
80 | current_query_key, current_query_desc = current_query
81 | response_data[current_query_desc] = response_str
82 | received_response_event.set()
83 |
84 | await client.start_notify(notify_characteristic.uuid, notification_handler)
85 |
86 | # Send each query and wait for responses
87 | for current_query in query_iterator:
88 | query_command, query_desc = current_query
89 | try:
90 | print(f"Sending query: {query_command.strip()}")
91 | await client.write_gatt_char(write_characteristic.uuid, query_command.encode())
92 | await received_response_event.wait()
93 | received_response_event.clear()
94 |
95 | # Parse temperature and humidity
96 | temperature_str = response_data.get("NTC Temperature", "")
97 | humidity_str = response_data.get("Humidity", "")
98 |
99 | if temperature_str.startswith(";RT "):
100 | temperature_value = int(temperature_str.replace(";RT ", "")) / 100
101 | else:
102 | temperature_value = None
103 |
104 | if humidity_str.startswith(";RH "):
105 | humidity_value = int(humidity_str.replace(";RH ", "")) / 100
106 | else:
107 | humidity_value = None
108 |
109 | # Display temperature and humidity on I2C display
110 | draw.rectangle((0, 0, I2C_WIDTH, I2C_HEIGHT), outline=0, fill=0) # Clear the display
111 | draw.text((0, 0), f"Temp: {temperature_value:.2f}C" if temperature_value else "Temp: N/A", font=font, fill=255)
112 | draw.text((0, 16), f"Hum: {humidity_value:.2f}%" if humidity_value else "Hum: N/A", font=font, fill=255)
113 | display.image(image)
114 | display.show()
115 |
116 | except Exception as e:
117 | print(f"Failed to send query {query_command.strip()}: {e}")
118 |
119 | await client.stop_notify(notify_characteristic.uuid)
120 | return response_data, write_characteristic
121 |
122 | async def run_queries(device):
123 | while True:
124 | try:
125 | await connect_and_send_queries(device)
126 | await asyncio.sleep(QUERY_DELAY) # Delay before next query cycle
127 | except Exception as e:
128 | print(f"An error occurred: {e}. Retrying in a few seconds...")
129 | await asyncio.sleep(10)
130 |
131 | if __name__ == "__main__":
132 | loop = asyncio.get_event_loop()
133 | try:
134 | selected_device = loop.run_until_complete(find_device_by_name())
135 | if selected_device:
136 | loop.run_until_complete(run_queries(selected_device))
137 | else:
138 | print("Device not found. Exiting.")
139 | except Exception as e:
140 | print(f"Critical error: {e}")
141 |
--------------------------------------------------------------------------------
/sample-projects/ifttt-request.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from datetime import datetime
3 | from bleak import BleakScanner, BleakClient
4 | import requests
5 |
6 | # Configuration variables
7 | DEVICE_NAME = "MONTY5E094A81" # Replace with the exact name of your BLE device
8 | TEMPERATURE_THRESHOLD = 22 # Temperature threshold in Celsius
9 | QUERY_DELAY = 30 # Delay in seconds between each query
10 |
11 | # IFTTT configuration
12 | IFTTT_EVENT_NAME = "temperature_alert" # Replace with your IFTTT event name
13 | IFTTT_KEY = "ycxB_CRNV_Lwgv8vtvlI7mbunY0TAKsU-Ku3-MZvPuhp" # Replace with your IFTTT Webhooks key
14 | IFTTT_URL = f"https://maker.ifttt.com/trigger/{IFTTT_EVENT_NAME}/with/key/{IFTTT_KEY}"
15 |
16 | # List of queries to send and their corresponding descriptions
17 | queries = {
18 | ";QT\r\n": "NTC Temperature"
19 | }
20 |
21 | # Store the responses for querying
22 | response_data = {}
23 |
24 | async def find_device_by_name():
25 | print(f"Searching for BLE device named '{DEVICE_NAME}'...")
26 | devices = await BleakScanner.discover()
27 |
28 | for device in devices:
29 | if device.name == DEVICE_NAME:
30 | print(f"Device found: {device.name} - {device.address}")
31 | return device
32 | print("Device not found.")
33 | return None
34 |
35 | async def connect_and_send_queries(device):
36 | async with BleakClient(device.address) as client:
37 | print(f"Connected to {device.name} - {device.address}")
38 |
39 | services = client.services
40 | if not services:
41 | await client.get_services()
42 |
43 | write_characteristic = None
44 | notify_characteristic = None
45 |
46 | # Find writable and notify characteristics
47 | for service in services:
48 | for characteristic in service.characteristics:
49 | if "write" in characteristic.properties:
50 | write_characteristic = characteristic
51 | if "notify" in characteristic.properties:
52 | notify_characteristic = characteristic
53 | if write_characteristic and notify_characteristic:
54 | break
55 | if write_characteristic and notify_characteristic:
56 | break
57 |
58 | if not write_characteristic or not notify_characteristic:
59 | print("Necessary characteristics not found.")
60 | return None, None
61 |
62 | query_iterator = iter(queries.items())
63 | received_response_event = asyncio.Event()
64 |
65 | # Notification handler to process data
66 | def notification_handler(sender, data):
67 | response_str = data.decode().strip()
68 | current_query_key, current_query_desc = current_query
69 | response_data[current_query_desc] = response_str
70 | received_response_event.set()
71 |
72 | await client.start_notify(notify_characteristic.uuid, notification_handler)
73 |
74 | # Send each query and wait for responses
75 | for current_query in query_iterator:
76 | query_command, query_desc = current_query
77 | try:
78 | print(f"Sending query: {query_command.strip()}")
79 | await client.write_gatt_char(write_characteristic.uuid, query_command.encode())
80 | await received_response_event.wait()
81 | received_response_event.clear()
82 |
83 | # Parse temperature
84 | temperature_str = response_data.get("NTC Temperature", "")
85 | if temperature_str.startswith(";RT "):
86 | temperature_value = int(temperature_str.replace(";RT ", "")) / 100
87 | print(f"Current Temperature: {temperature_value}°C")
88 |
89 | # Check temperature and send IFTTT request if above threshold
90 | if temperature_value > TEMPERATURE_THRESHOLD:
91 | print("Temperature exceeds threshold. Sending IFTTT request.")
92 | response = requests.post(IFTTT_URL, json={"value1": temperature_value})
93 | if response.status_code == 200:
94 | print("IFTTT request sent successfully.")
95 | else:
96 | print(f"Failed to send IFTTT request: {response.status_code}")
97 | else:
98 | print("Temperature data unavailable.")
99 |
100 | except Exception as e:
101 | print(f"Failed to send query {query_command.strip()}: {e}")
102 |
103 | await client.stop_notify(notify_characteristic.uuid)
104 | return response_data, write_characteristic
105 |
106 | async def run_queries(device):
107 | while True:
108 | try:
109 | await connect_and_send_queries(device)
110 | await asyncio.sleep(QUERY_DELAY) # Delay before next query cycle
111 | except Exception as e:
112 | print(f"An error occurred: {e}. Retrying in a few seconds...")
113 | await asyncio.sleep(10)
114 |
115 | if __name__ == "__main__":
116 | loop = asyncio.get_event_loop()
117 | try:
118 | selected_device = loop.run_until_complete(find_device_by_name())
119 | if selected_device:
120 | loop.run_until_complete(run_queries(selected_device))
121 | else:
122 | print("Device not found. Exiting.")
123 | except Exception as e:
124 | print(f"Critical error: {e}")
125 |
--------------------------------------------------------------------------------
/sample-projects/turn-on-led.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from bleak import BleakScanner, BleakClient
3 | import RPi.GPIO as GPIO
4 |
5 | # Configuration variables
6 | DEVICE_NAME = "YourDeviceName" # Replace with the exact name of your BLE device
7 | TEMPERATURE_THRESHOLD = 26.0 # Temperature threshold in Celsius
8 | LED_PIN = 17 # GPIO pin where the LED is connected
9 | QUERY_DELAY = 30 # Delay in seconds between each query
10 |
11 | # List of queries to send and their corresponding descriptions
12 | queries = {
13 | ";QT\r\n": "NTC Temperature"
14 | }
15 |
16 | # GPIO setup for the LED
17 | GPIO.setmode(GPIO.BCM)
18 | GPIO.setup(LED_PIN, GPIO.OUT)
19 |
20 | # Store the responses for querying
21 | response_data = {}
22 |
23 | async def find_device_by_name():
24 | print(f"Searching for BLE device named '{DEVICE_NAME}'...")
25 | devices = await BleakScanner.discover()
26 |
27 | for device in devices:
28 | if device.name == DEVICE_NAME:
29 | print(f"Device found: {device.name} - {device.address}")
30 | return device
31 | print("Device not found.")
32 | return None
33 |
34 | async def connect_and_send_queries(device):
35 | async with BleakClient(device.address) as client:
36 | print(f"Connected to {device.name} - {device.address}")
37 |
38 | services = client.services
39 | if not services:
40 | await client.get_services()
41 |
42 | write_characteristic = None
43 | notify_characteristic = None
44 |
45 | # Find writable and notify characteristics
46 | for service in services:
47 | for characteristic in service.characteristics:
48 | if "write" in characteristic.properties:
49 | write_characteristic = characteristic
50 | if "notify" in characteristic.properties:
51 | notify_characteristic = characteristic
52 | if write_characteristic and notify_characteristic:
53 | break
54 | if write_characteristic and notify_characteristic:
55 | break
56 |
57 | if not write_characteristic or not notify_characteristic:
58 | print("Necessary characteristics not found.")
59 | return None, None
60 |
61 | query_iterator = iter(queries.items())
62 | received_response_event = asyncio.Event()
63 |
64 | # Notification handler to process data
65 | def notification_handler(sender, data):
66 | response_str = data.decode().strip()
67 | current_query_key, current_query_desc = current_query
68 | response_data[current_query_desc] = response_str
69 | received_response_event.set()
70 |
71 | await client.start_notify(notify_characteristic.uuid, notification_handler)
72 |
73 | # Send each query and wait for responses
74 | for current_query in query_iterator:
75 | query_command, query_desc = current_query
76 | try:
77 | print(f"Sending query: {query_command.strip()}")
78 | await client.write_gatt_char(write_characteristic.uuid, query_command.encode())
79 | await received_response_event.wait()
80 | received_response_event.clear()
81 |
82 | # Check if temperature exceeds threshold
83 | temperature_str = response_data.get("NTC Temperature", "")
84 | if temperature_str.startswith(";RT "):
85 | temperature_value = int(temperature_str.replace(";RT ", "")) / 100
86 | print(f"Current Temperature: {temperature_value}°C")
87 |
88 | if temperature_value > TEMPERATURE_THRESHOLD:
89 | GPIO.output(LED_PIN, GPIO.HIGH)
90 | print(f"LED ON - Temperature is above {TEMPERATURE_THRESHOLD}°C")
91 | else:
92 | GPIO.output(LED_PIN, GPIO.LOW)
93 | print(f"LED OFF - Temperature is below or equal to {TEMPERATURE_THRESHOLD}°C")
94 |
95 | except Exception as e:
96 | print(f"Failed to send query {query_command.strip()}: {e}")
97 |
98 | await client.stop_notify(notify_characteristic.uuid)
99 | return response_data, write_characteristic
100 |
101 | async def run_queries(device):
102 | while True:
103 | try:
104 | await connect_and_send_queries(device)
105 | await asyncio.sleep(QUERY_DELAY) # Delay before next query cycle
106 | except Exception as e:
107 | print(f"An error occurred: {e}. Retrying in a few seconds...")
108 | await asyncio.sleep(10)
109 |
110 | if __name__ == "__main__":
111 | loop = asyncio.get_event_loop()
112 | try:
113 | selected_device = loop.run_until_complete(find_device_by_name())
114 | if selected_device:
115 | loop.run_until_complete(run_queries(selected_device))
116 | else:
117 | print("Device not found. Exiting.")
118 | except Exception as e:
119 | print(f"Critical error: {e}")
120 | finally:
121 | GPIO.cleanup() # Ensure GPIO cleanup on exit
122 |
--------------------------------------------------------------------------------
/web-dashboard/ble_responses.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "timestamp": "2024-10-25 15:26:25",
4 | "data": {
5 | "Battery Level": ";RL 76",
6 | "NTC Temperature": ";RT 2619",
7 | "Humidity": ";RH 5102",
8 | "TVOC Reading": ";RO 0",
9 | "CO2 Reading": ";RC 0"
10 | }
11 | },
12 | {
13 | "timestamp": "2024-10-25 15:28:09",
14 | "data": {
15 | "Battery Level": ";RL 72",
16 | "NTC Temperature": ";RT 2581",
17 | "Humidity": ";RH 5125",
18 | "TVOC Reading": ";RO 0",
19 | "CO2 Reading": ";RC 0"
20 | }
21 | },
22 | {
23 | "timestamp": "2024-10-25 15:29:03",
24 | "data": {
25 | "Battery Level": ";RL 72",
26 | "NTC Temperature": ";RT 2610",
27 | "Humidity": ";RH 5115",
28 | "TVOC Reading": ";RO 0",
29 | "CO2 Reading": ";RC 0"
30 | }
31 | },
32 | {
33 | "timestamp": "2024-10-25 15:29:43",
34 | "data": {
35 | "Battery Level": ";RL 72",
36 | "NTC Temperature": ";RT 2610",
37 | "Humidity": ";RH 5115",
38 | "TVOC Reading": ";RO 0",
39 | "CO2 Reading": ";RC 0"
40 | }
41 | },
42 | {
43 | "timestamp": "2024-10-25 15:30:33",
44 | "data": {
45 | "Battery Level": ";RL 72",
46 | "NTC Temperature": ";RT 2592",
47 | "Humidity": ";RH 5111",
48 | "TVOC Reading": ";RO 0",
49 | "CO2 Reading": ";RC 0"
50 | }
51 | },
52 | {
53 | "timestamp": "2024-10-25 15:31:11",
54 | "data": {
55 | "Battery Level": ";RL 72",
56 | "NTC Temperature": ";RT 2592",
57 | "Humidity": ";RH 5111",
58 | "TVOC Reading": ";RO 0",
59 | "CO2 Reading": ";RC 0"
60 | }
61 | },
62 | {
63 | "timestamp": "2024-10-25 15:38:25",
64 | "data": {
65 | "Battery Level": ";RL 9",
66 | "NTC Temperature": ";RT 2678",
67 | "Humidity": ";RH 5997",
68 | "TVOC Reading": ";RO 6943",
69 | "CO2 Reading": ";RC 0"
70 | }
71 | },
72 | {
73 | "timestamp": "2024-10-25 15:39:16",
74 | "data": {
75 | "Battery Level": ";RL 9",
76 | "NTC Temperature": ";RT 2676",
77 | "Humidity": ";RH 6012",
78 | "TVOC Reading": ";RO 0",
79 | "CO2 Reading": ";RC 0"
80 | }
81 | },
82 | {
83 | "timestamp": "2024-10-25 15:40:06",
84 | "data": {
85 | "Battery Level": ";RL 9",
86 | "NTC Temperature": ";RT 2676",
87 | "Humidity": ";RH 6012",
88 | "TVOC Reading": ";RO 0",
89 | "CO2 Reading": ";RC 0"
90 | }
91 | },
92 | {
93 | "timestamp": "2024-10-25 15:41:04",
94 | "data": {
95 | "Battery Level": ";RL 9",
96 | "NTC Temperature": ";RT 2641",
97 | "Humidity": ";RH 5999",
98 | "TVOC Reading": ";RO 0",
99 | "CO2 Reading": ";RC 0"
100 | }
101 | },
102 | {
103 | "timestamp": "2024-10-25 15:41:51",
104 | "data": {
105 | "Battery Level": ";RL 9",
106 | "NTC Temperature": ";RT 2641",
107 | "Humidity": ";RH 5999",
108 | "TVOC Reading": ";RO 0",
109 | "CO2 Reading": ";RC 0"
110 | }
111 | },
112 | {
113 | "timestamp": "2024-10-25 15:42:47",
114 | "data": {
115 | "Battery Level": ";RL 8",
116 | "NTC Temperature": ";RT 2670",
117 | "Humidity": ";RH 5990",
118 | "TVOC Reading": ";RO 0",
119 | "CO2 Reading": ";RC 0"
120 | }
121 | },
122 | {
123 | "timestamp": "2024-10-25 15:43:35",
124 | "data": {
125 | "Battery Level": ";RL 8",
126 | "NTC Temperature": ";RT 2670",
127 | "Humidity": ";RH 5990",
128 | "TVOC Reading": ";RO 0",
129 | "CO2 Reading": ";RC 0"
130 | }
131 | },
132 | {
133 | "timestamp": "2024-10-25 15:44:27",
134 | "data": {
135 | "Battery Level": ";RL 9",
136 | "NTC Temperature": ";RT 2668",
137 | "Humidity": ";RH 5980",
138 | "TVOC Reading": ";RO 0",
139 | "CO2 Reading": ";RC 0"
140 | }
141 | },
142 | {
143 | "timestamp": "2024-10-25 15:45:16",
144 | "data": {
145 | "Battery Level": ";RL 9",
146 | "NTC Temperature": ";RT 2668",
147 | "Humidity": ";RH 5980",
148 | "TVOC Reading": ";RO 0",
149 | "CO2 Reading": ";RC 0"
150 | }
151 | },
152 | {
153 | "timestamp": "2024-10-25 15:46:07",
154 | "data": {
155 | "Battery Level": ";RL 8",
156 | "NTC Temperature": ";RT 2664",
157 | "Humidity": ";RH 5971",
158 | "TVOC Reading": ";RO 0",
159 | "CO2 Reading": ";RC 0"
160 | }
161 | },
162 | {
163 | "timestamp": "2024-10-25 15:46:55",
164 | "data": {
165 | "Battery Level": ";RL 8",
166 | "NTC Temperature": ";RT 2664",
167 | "Humidity": ";RH 5971",
168 | "TVOC Reading": ";RO 0",
169 | "CO2 Reading": ";RC 0"
170 | }
171 | },
172 | {
173 | "timestamp": "2024-10-25 15:47:47",
174 | "data": {
175 | "Battery Level": ";RL 9",
176 | "NTC Temperature": ";RT 2661",
177 | "Humidity": ";RH 5965",
178 | "TVOC Reading": ";RO 0",
179 | "CO2 Reading": ";RC 0"
180 | }
181 | },
182 | {
183 | "timestamp": "2024-10-25 15:48:37",
184 | "data": {
185 | "Battery Level": ";RL 9",
186 | "NTC Temperature": ";RT 2661",
187 | "Humidity": ";RH 5965",
188 | "TVOC Reading": ";RO 0",
189 | "CO2 Reading": ";RC 0"
190 | }
191 | },
192 | {
193 | "timestamp": "2024-10-25 16:03:32",
194 | "data": {
195 | "Battery Level": ";RL 75",
196 | "NTC Temperature": ";RT 2562",
197 | "Humidity": ";RH 4975",
198 | "TVOC Reading": ";RO 0",
199 | "CO2 Reading": ";RC 0"
200 | }
201 | },
202 | {
203 | "timestamp": "2024-10-25 16:04:18",
204 | "data": {
205 | "Battery Level": ";RL 71",
206 | "NTC Temperature": ";RT 2606",
207 | "Humidity": ";RH 4992",
208 | "TVOC Reading": ";RO 0",
209 | "CO2 Reading": ";RC 0"
210 | }
211 | },
212 | {
213 | "timestamp": "2024-10-25 16:05:12",
214 | "data": {
215 | "Battery Level": ";RL 71",
216 | "NTC Temperature": ";RT 2606",
217 | "Humidity": ";RH 4992",
218 | "TVOC Reading": ";RO 0",
219 | "CO2 Reading": ";RC 0"
220 | }
221 | },
222 | {
223 | "timestamp": "2024-10-25 16:06:02",
224 | "data": {
225 | "Battery Level": ";RL 71",
226 | "NTC Temperature": ";RT 2597",
227 | "Humidity": ";RH 4978",
228 | "TVOC Reading": ";RO 0",
229 | "CO2 Reading": ";RC 0"
230 | }
231 | },
232 | {
233 | "timestamp": "2024-10-25 16:06:45",
234 | "data": {
235 | "Battery Level": ";RL 71",
236 | "NTC Temperature": ";RT 2597",
237 | "Humidity": ";RH 4978",
238 | "TVOC Reading": ";RO 0",
239 | "CO2 Reading": ";RC 0"
240 | }
241 | },
242 | {
243 | "timestamp": "2024-10-25 16:07:31",
244 | "data": {
245 | "Battery Level": ";RL 71",
246 | "NTC Temperature": ";RT 2621",
247 | "Humidity": ";RH 4968",
248 | "TVOC Reading": ";RO 0",
249 | "CO2 Reading": ";RC 0"
250 | }
251 | },
252 | {
253 | "timestamp": "2024-10-25 16:08:14",
254 | "data": {
255 | "Battery Level": ";RL 71",
256 | "NTC Temperature": ";RT 2621",
257 | "Humidity": ";RH 4968",
258 | "TVOC Reading": ";RO 0",
259 | "CO2 Reading": ";RC 0"
260 | }
261 | },
262 | {
263 | "timestamp": "2024-10-25 16:09:05",
264 | "data": {
265 | "Battery Level": ";RL 71",
266 | "NTC Temperature": ";RT 2625",
267 | "Humidity": ";RH 4964",
268 | "TVOC Reading": ";RO 0",
269 | "CO2 Reading": ";RC 0"
270 | }
271 | },
272 | {
273 | "timestamp": "2024-10-25 16:09:50",
274 | "data": {
275 | "Battery Level": ";RL 71",
276 | "NTC Temperature": ";RT 2625",
277 | "Humidity": ";RH 4964",
278 | "TVOC Reading": ";RO 0",
279 | "CO2 Reading": ";RC 0"
280 | }
281 | },
282 | {
283 | "timestamp": "2024-10-25 16:10:37",
284 | "data": {
285 | "Battery Level": ";RL 71",
286 | "NTC Temperature": ";RT 2608",
287 | "Humidity": ";RH 4962",
288 | "TVOC Reading": ";RO 0",
289 | "CO2 Reading": ";RC 0"
290 | }
291 | },
292 | {
293 | "timestamp": "2024-10-25 16:11:19",
294 | "data": {
295 | "Battery Level": ";RL 71",
296 | "NTC Temperature": ";RT 2608",
297 | "Humidity": ";RH 4962",
298 | "TVOC Reading": ";RO 0",
299 | "CO2 Reading": ";RC 0"
300 | }
301 | },
302 | {
303 | "timestamp": "2024-10-25 16:12:18",
304 | "data": {
305 | "Battery Level": ";RL 71",
306 | "NTC Temperature": ";RT 2635",
307 | "Humidity": ";RH 4958",
308 | "TVOC Reading": ";RO 0",
309 | "CO2 Reading": ";RC 0"
310 | }
311 | },
312 | {
313 | "timestamp": "2024-10-25 16:13:06",
314 | "data": {
315 | "Battery Level": ";RL 71",
316 | "NTC Temperature": ";RT 2635",
317 | "Humidity": ";RH 4958",
318 | "TVOC Reading": ";RO 0",
319 | "CO2 Reading": ";RC 0"
320 | }
321 | },
322 | {
323 | "timestamp": "2024-10-25 16:13:52",
324 | "data": {
325 | "Battery Level": ";RL 69",
326 | "NTC Temperature": ";RT 2613",
327 | "Humidity": ";RH 4960",
328 | "TVOC Reading": ";RO 0",
329 | "CO2 Reading": ";RC 0"
330 | }
331 | },
332 | {
333 | "timestamp": "2024-10-25 16:14:35",
334 | "data": {
335 | "Battery Level": ";RL 69",
336 | "NTC Temperature": ";RT 2613",
337 | "Humidity": ";RH 4960",
338 | "TVOC Reading": ";RO 0",
339 | "CO2 Reading": ";RC 0"
340 | }
341 | },
342 | {
343 | "timestamp": "2024-10-25 16:15:47",
344 | "data": {
345 | "Battery Level": ";RL 33",
346 | "NTC Temperature": ";RT 2642",
347 | "Humidity": ";RH 6190",
348 | "TVOC Reading": ";RO 0",
349 | "CO2 Reading": ";RC 0"
350 | }
351 | },
352 | {
353 | "timestamp": "2024-10-25 16:16:39",
354 | "data": {
355 | "Battery Level": ";RL 24",
356 | "NTC Temperature": ";RT 2650",
357 | "Humidity": ";RH 6199",
358 | "TVOC Reading": ";RO 0",
359 | "CO2 Reading": ";RC 0"
360 | }
361 | },
362 | {
363 | "timestamp": "2024-10-25 16:17:44",
364 | "data": {
365 | "Battery Level": ";RL 32",
366 | "NTC Temperature": ";RT 2422",
367 | "Humidity": ";RH 5213",
368 | "TVOC Reading": ";RO 3",
369 | "CO2 Reading": ";RC 0"
370 | }
371 | },
372 | {
373 | "timestamp": "2024-10-25 16:18:35",
374 | "data": {
375 | "Battery Level": ";RL 18",
376 | "NTC Temperature": ";RT 2419",
377 | "Humidity": ";RH 5218",
378 | "TVOC Reading": ";RO 0",
379 | "CO2 Reading": ";RC 0"
380 | }
381 | },
382 | {
383 | "timestamp": "2024-10-25 16:19:18",
384 | "data": {
385 | "Battery Level": ";RL 18",
386 | "NTC Temperature": ";RT 2419",
387 | "Humidity": ";RH 5218",
388 | "TVOC Reading": ";RO 0",
389 | "CO2 Reading": ";RC 0"
390 | }
391 | },
392 | {
393 | "timestamp": "2024-10-29 09:45:30",
394 | "data": {
395 | "Battery Level": ";RL 59",
396 | "NTC Temperature": ";RT 2407",
397 | "Humidity": ";RH 5944",
398 | "TVOC Reading": ";RO 110",
399 | "CO2 Reading": ";RC 0"
400 | }
401 | },
402 | {
403 | "timestamp": "2024-10-29 09:46:17",
404 | "data": {
405 | "Battery Level": ";RL 51",
406 | "NTC Temperature": ";RT 2433",
407 | "Humidity": ";RH 5959",
408 | "TVOC Reading": ";RO 0",
409 | "CO2 Reading": ";RC 0"
410 | }
411 | },
412 | {
413 | "timestamp": "2024-10-29 09:47:00",
414 | "data": {
415 | "Battery Level": ";RL 51",
416 | "NTC Temperature": ";RT 2433",
417 | "Humidity": ";RH 5959",
418 | "TVOC Reading": ";RO 0",
419 | "CO2 Reading": ";RC 0"
420 | }
421 | },
422 | {
423 | "timestamp": "2024-10-29 09:47:47",
424 | "data": {
425 | "Battery Level": ";RL 51",
426 | "NTC Temperature": ";RT 2448",
427 | "Humidity": ";RH 5942",
428 | "TVOC Reading": ";RO 0",
429 | "CO2 Reading": ";RC 0"
430 | }
431 | },
432 | {
433 | "timestamp": "2024-10-29 09:48:30",
434 | "data": {
435 | "Battery Level": ";RL 51",
436 | "NTC Temperature": ";RT 2448",
437 | "Humidity": ";RH 5942",
438 | "TVOC Reading": ";RO 0",
439 | "CO2 Reading": ";RC 0"
440 | }
441 | },
442 | {
443 | "timestamp": "2024-10-29 09:49:17",
444 | "data": {
445 | "Battery Level": ";RL 51",
446 | "NTC Temperature": ";RT 2451",
447 | "Humidity": ";RH 5938",
448 | "TVOC Reading": ";RO 0",
449 | "CO2 Reading": ";RC 0"
450 | }
451 | },
452 | {
453 | "timestamp": "2024-10-29 09:49:59",
454 | "data": {
455 | "Battery Level": ";RL 51",
456 | "NTC Temperature": ";RT 2451",
457 | "Humidity": ";RH 5938",
458 | "TVOC Reading": ";RO 0",
459 | "CO2 Reading": ";RC 0"
460 | }
461 | },
462 | {
463 | "timestamp": "2024-10-29 09:50:45",
464 | "data": {
465 | "Battery Level": ";RL 47",
466 | "NTC Temperature": ";RT 2473",
467 | "Humidity": ";RH 5936",
468 | "TVOC Reading": ";RO 0",
469 | "CO2 Reading": ";RC 0"
470 | }
471 | },
472 | {
473 | "timestamp": "2024-10-29 09:51:29",
474 | "data": {
475 | "Battery Level": ";RL 47",
476 | "NTC Temperature": ";RT 2473",
477 | "Humidity": ";RH 5936",
478 | "TVOC Reading": ";RO 0",
479 | "CO2 Reading": ";RC 0"
480 | }
481 | },
482 | {
483 | "timestamp": "2024-10-29 09:52:17",
484 | "data": {
485 | "Battery Level": ";RL 50",
486 | "NTC Temperature": ";RT 2409",
487 | "Humidity": ";RH 5938",
488 | "TVOC Reading": ";RO 0",
489 | "CO2 Reading": ";RC 0"
490 | }
491 | },
492 | {
493 | "timestamp": "2024-10-29 09:52:58",
494 | "data": {
495 | "Battery Level": ";RL 50",
496 | "NTC Temperature": ";RT 2409",
497 | "Humidity": ";RH 5938",
498 | "TVOC Reading": ";RO 0",
499 | "CO2 Reading": ";RC 0"
500 | }
501 | },
502 | {
503 | "timestamp": "2024-10-29 09:53:46",
504 | "data": {
505 | "Battery Level": ";RL 50",
506 | "NTC Temperature": ";RT 2459",
507 | "Humidity": ";RH 5940",
508 | "TVOC Reading": ";RO 0",
509 | "CO2 Reading": ";RC 0"
510 | }
511 | },
512 | {
513 | "timestamp": "2024-10-29 09:54:30",
514 | "data": {
515 | "Battery Level": ";RL 50",
516 | "NTC Temperature": ";RT 2459",
517 | "Humidity": ";RH 5940",
518 | "TVOC Reading": ";RO 0",
519 | "CO2 Reading": ";RC 0"
520 | }
521 | },
522 | {
523 | "timestamp": "2024-10-29 09:55:16",
524 | "data": {
525 | "Battery Level": ";RL 50",
526 | "NTC Temperature": ";RT 2419",
527 | "Humidity": ";RH 5938",
528 | "TVOC Reading": ";RO 0",
529 | "CO2 Reading": ";RC 0"
530 | }
531 | },
532 | {
533 | "timestamp": "2024-10-29 09:55:59",
534 | "data": {
535 | "Battery Level": ";RL 50",
536 | "NTC Temperature": ";RT 2419",
537 | "Humidity": ";RH 5938",
538 | "TVOC Reading": ";RO 0",
539 | "CO2 Reading": ";RC 0"
540 | }
541 | },
542 | {
543 | "timestamp": "2024-10-29 09:56:49",
544 | "data": {
545 | "Battery Level": ";RL 50",
546 | "NTC Temperature": ";RT 2426",
547 | "Humidity": ";RH 5939",
548 | "TVOC Reading": ";RO 0",
549 | "CO2 Reading": ";RC 0"
550 | }
551 | }
552 | ]
--------------------------------------------------------------------------------
/web-dashboard/templates/dashboard.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | BLE Data Dashboard
7 |
23 |
24 |
25 | BLE Data Dashboard
26 | This page shows the last 10 readings collected from the BLE device in real-time.
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/web-dashboard/your_flask_file.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, jsonify, render_template
2 | import json
3 | import os
4 |
5 | app = Flask(__name__)
6 |
7 | # API endpoint to fetch the last 10 JSON data entries
8 | @app.route('/api/data', methods=['GET'])
9 | def get_data():
10 | if os.path.exists('ble_responses.json'):
11 | with open('ble_responses.json', 'r') as f:
12 | all_data = json.load(f)
13 | # Get the last 10 entries only
14 | last_10_data = all_data[-10:] if all_data else []
15 | else:
16 | last_10_data = []
17 | return jsonify(last_10_data)
18 |
19 | # Webpage route
20 | @app.route('/')
21 | def dashboard():
22 | return render_template('dashboard.html')
23 |
24 | if __name__ == '__main__':
25 | app.run(debug=True)
26 |
--------------------------------------------------------------------------------
/web-tool.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
179 |
180 |
181 |
182 |
183 | Monty Web-Bluetooth Terminal
184 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
This browser tool allows you to connect to Monty Monitor device and stream all its available data, typically up to three months, into a CSV file using Web Bluetooth. To access this tool, you must use both a Web Bluetooth enabled device and Google Chrome browser.
268 |
Please note that the readings will be raw data with no pre-processing so may not be indicative of the monitoring conditions applied. Gas readings specifically require additional analysis to convert to PPM and PPB equivalents. Other data points are available beyond those reported by this tool however they require custom firmware programming, available on request.
269 |
270 |
271 |
272 |
273 | CONNECT 🔌
274 | DISCONNECT ❌
275 | GET ALL 🚀
276 | SAVE 💾
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
--------------------------------------------------------------------------------