├── .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 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 |
WELCOME TO THE MONTY WEB-BLUETOOTH TERMINAL!
265 | 266 | 270 | 271 |
272 |
273 | 274 | 275 | 276 | 277 |
278 | 279 | 280 |
281 | 282 | 283 | 284 | --------------------------------------------------------------------------------