{"id":1775,"date":"2026-10-05T16:03:49","date_gmt":"2026-10-05T16:03:49","guid":{"rendered":"https:\/\/www.bleuio.com\/blog\/?p=1775"},"modified":"2026-10-05T19:07:08","modified_gmt":"2026-10-05T19:07:08","slug":"send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio","status":"publish","type":"post","link":"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/","title":{"rendered":"Send BLE Air Quality Data to Cloudflare D1 with Python and BleuIO"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">In this tutorial, we will build a simple BLE-to-cloud air quality gateway using <strong>HibouAir, BleuIO, Python, Cloudflare Workers, and Cloudflare D1<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A HibouAir sensor will advertise its air quality measurements over Bluetooth Low Energy. BleuIO, connected to a computer through USB, will scan for a specific HibouAir device and provide the BLE advertising data to a Python application through the serial port. The Python application will decode <strong>CO2, temperature, and humidity<\/strong>, display the measurements in the terminal, and upload them to a Cloudflare Worker. The Worker will then store the measurements in a Cloudflare D1 database.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This project is intentionally kept simple. It demonstrates how BleuIO can act as the Bluetooth interface for a desktop or gateway application without requiring the application itself to manage a Bluetooth stack.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why Cloudflare D1?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cloudflare D1 is a serverless SQL database built on SQLite. It works particularly well with Cloudflare Workers, allowing us to create a small HTTP API without running our own server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a project like this, there are several advantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>No database server to install or maintain<\/li>\n\n\n\n<li>No local web server required<\/li>\n\n\n\n<li>HTTPS is handled automatically<\/li>\n\n\n\n<li>Cloudflare Workers can communicate directly with D1<\/li>\n\n\n\n<li>D1 supports regular SQL<\/li>\n\n\n\n<li>A useful <strong>free tier<\/strong> is available<\/li>\n\n\n\n<li>The database remains accessible even when the Computer running the BLE gateway is offline<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This makes it convenient for prototypes, BLE gateways, environmental monitoring projects, demonstrations, and small IoT applications.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For this example, the Computer only needs to run the Python gateway application. Everything after the HTTP request is handled in the cloud.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Requirements<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">For this project, you will need:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Hardware<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><a href=\"https:\/\/www.bleuio.com\/bluetooth-low-energy-usb-ssd025.php\" target=\"_blank\" rel=\"noreferrer noopener\">BleuIO USB Bluetooth Low Energy dongle<\/a><\/strong><\/li>\n\n\n\n<li><strong><a href=\"https:\/\/www.hibouair.com\/specifications-air-quality-monitor-co2-sensor.php\" target=\"_blank\" rel=\"noreferrer noopener\">HibouAir CO2 air quality sensor<\/a><\/strong><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Software and services<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Python 3<\/li>\n\n\n\n<li>A free Cloudflare account<\/li>\n\n\n\n<li><code>pyserial<\/code><\/li>\n\n\n\n<li><code>requests<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For this example, the HibouAir sensor has the board ID:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>220069<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and BleuIO appears on the Mac at:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/dev\/cu.usbmodem4048FDEBA6D01<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Your serial port depends on operating system and HibouAir board ID may be different.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Create a Cloudflare D1 Database<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">First, log in to your <strong><a href=\"https:\/\/dash.cloudflare.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudflare account<\/a><\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">From the Cloudflare dashboard, go to the D1 database section and create a new database.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"515\" src=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-1024x515.png\" alt=\"\" class=\"wp-image-1776\" srcset=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-1024x515.png 1024w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-300x151.png 300w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-767x386.png 767w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-1536x773.png 1536w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image.png 1666w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">For this tutorial, we will call it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>hibouair-db<\/strong><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Once the database has been created, open its SQL Console.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run the following SQL:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE TABLE IF NOT EXISTS readings (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    board_id TEXT NOT NULL,\n    measured_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    co2 INTEGER NOT NULL,\n    temperature REAL NOT NULL,\n    humidity REAL NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_readings_board_time\nON readings(board_id, measured_at);<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"564\" src=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-1-1024x564.png\" alt=\"\" class=\"wp-image-1777\" style=\"width:688px;height:auto\" srcset=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-1-1024x564.png 1024w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-1-300x165.png 300w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-1-768x423.png 768w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-1-1536x846.png 1536w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-1-2048x1128.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This creates a simple table containing:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Database ID<\/li>\n\n\n\n<li>HibouAir board ID<\/li>\n\n\n\n<li>Timestamp<\/li>\n\n\n\n<li>CO\u2082<\/li>\n\n\n\n<li>Temperature<\/li>\n\n\n\n<li>Humidity<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>measured_at<\/code> field is generated automatically by D1 when the reading is inserted.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A stored record will look conceptually like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>id          1\nboard_id    220069\nmeasured_at 2026-10-05 14:42:30\nco2         623\ntemperature 24.1\nhumidity    43.7<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Create the Cloudflare Worker<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Next, create a Cloudflare Worker.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now go to:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Workers &amp; Pages \u2192 Create application<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Create a basic Worker. ( you can choose &#8220;Start with Hello world&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The Worker will sit between our Python application and the D1 database.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For this project, we can name it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>hibouair-api<\/strong><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and then press deploy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">After creating the Worker, open the code editor and replace the default code with the following :<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"413\" src=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-2-1024x413.png\" alt=\"\" class=\"wp-image-1779\" srcset=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-2-1024x413.png 1024w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-2-300x121.png 300w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-2-768x310.png 768w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-2-1536x620.png 1536w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-2-2048x827.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>export default {\n  async fetch(request, env) {\n    const url = new URL(request.url);\n\n    \/\/ API status\n    if (url.pathname === \"\/\" &amp;&amp; request.method === \"GET\") {\n      return Response.json({\n        service: \"HibouAir Cloud API\",\n        status: \"online\"\n      });\n    }\n\n    \/\/ Store a new sensor reading\n    if (url.pathname === \"\/api\/readings\" &amp;&amp; request.method === \"POST\") {\n      const suppliedKey = request.headers.get(\"X-API-Key\");\n\n      if (!suppliedKey || suppliedKey !== env.API_KEY) {\n        return Response.json(\n          {\n            success: false,\n            error: \"Unauthorized\"\n          },\n          {\n            status: 401\n          }\n        );\n      }\n\n      let data;\n\n      try {\n        data = await request.json();\n      } catch {\n        return Response.json(\n          {\n            success: false,\n            error: \"Invalid JSON\"\n          },\n          {\n            status: 400\n          }\n        );\n      }\n\n      const boardId = String(data.board_id || \"\").trim();\n      const co2 = Number(data.co2);\n      const temperature = Number(data.temperature);\n      const humidity = Number(data.humidity);\n\n      if (!boardId) {\n        return Response.json(\n          {\n            success: false,\n            error: \"board_id is required\"\n          },\n          {\n            status: 400\n          }\n        );\n      }\n\n      if (\n        !Number.isFinite(co2) ||\n        !Number.isFinite(temperature) ||\n        !Number.isFinite(humidity)\n      ) {\n        return Response.json(\n          {\n            success: false,\n            error: \"Invalid sensor values\"\n          },\n          {\n            status: 400\n          }\n        );\n      }\n\n      const result = await env.DB\n        .prepare(`\n          INSERT INTO readings (\n            board_id,\n            co2,\n            temperature,\n            humidity\n          )\n          VALUES (?, ?, ?, ?)\n        `)\n        .bind(\n          boardId,\n          co2,\n          temperature,\n          humidity\n        )\n        .run();\n\n      return Response.json({\n        success: true,\n        message: \"Reading stored\",\n        board_id: boardId,\n        co2: co2,\n        temperature: temperature,\n        humidity: humidity,\n        meta: result.meta\n      });\n    }\n\n    \/\/ Return the latest reading\n    if (\n      url.pathname === \"\/api\/readings\/latest\" &amp;&amp;\n      request.method === \"GET\"\n    ) {\n      const boardId =\n        url.searchParams.get(\"board_id\") || \"220069\";\n\n      const reading = await env.DB\n        .prepare(`\n          SELECT\n            id,\n            board_id,\n            measured_at,\n            co2,\n            temperature,\n            humidity\n          FROM readings\n          WHERE board_id = ?\n          ORDER BY id DESC\n          LIMIT 1\n        `)\n        .bind(boardId)\n        .first();\n\n      if (!reading) {\n        return Response.json(\n          {\n            success: false,\n            error: \"No readings found\"\n          },\n          {\n            status: 404\n          }\n        );\n      }\n\n      return Response.json({\n        success: true,\n        reading: reading\n      });\n    }\n\n    return Response.json(\n      {\n        success: false,\n        error: \"Not found\"\n      },\n      {\n        status: 404\n      }\n    );\n  }\n};<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Deploy the Worker.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At this stage, the Worker exists, but it is not yet connected to the D1 database.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Bind the D1 Database to the Worker<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Open the settings for the <code>hibouair-api<\/code> Worker.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Add a <strong>D1 database binding<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Variable name: DB\nDatabase: hibouair-db<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The variable name must be:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>DB<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">because the Worker accesses the database through:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>env.DB<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>await env.DB\n    .prepare(\"SELECT * FROM readings\")\n    .all();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Once the binding has been added, deploy the Worker again if necessary.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"517\" src=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-3-1024x517.png\" alt=\"\" class=\"wp-image-1782\" srcset=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-3-1024x517.png 1024w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-3-300x151.png 300w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-3-767x387.png 767w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-3-1536x775.png 1536w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-3.png 1922w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Add an API Key<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Our Python application will be publicly connected to the Cloudflare Worker over HTTPS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We therefore do not want anybody who discovers the Worker URL to be able to insert arbitrary sensor measurements.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We can protect the write endpoint using a simple API key.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On the Computer, generate a random key:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>python3 -c \"import secrets; print(secrets.token_hex(32))\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This will generate a long random value.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Copy it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now create a Worker secret environment variable named:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>API_KEY<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and paste the generated value as its value.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The Python application will include this key in every request using:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>X-API-Key<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The Worker checks:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const suppliedKey =\n    request.headers.get(\"X-API-Key\");<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and compares it against:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>env.API_KEY<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"517\" src=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-4-1024x517.png\" alt=\"\" class=\"wp-image-1783\" srcset=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-4-1024x517.png 1024w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-4-300x151.png 300w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-4-767x387.png 767w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-4-1536x775.png 1536w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-4.png 1922w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Test the Worker<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cloudflare will provide a Worker URL similar to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>https:&#47;&#47;hibouair-api.your-account.workers.dev<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Open the base Worker URL in a browser.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You should receive something similar to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"service\": \"HibouAir Cloud API\",\n  \"status\": \"online\"\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If you see this response, the Worker is running.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Our Python application will send measurements to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>https:&#47;&#47;hibouair-api.your-account.workers.dev\/api\/readings<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The latest stored reading can later be retrieved from:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>https:&#47;&#47;hibouair-api.your-account.workers.dev\/api\/readings\/latest<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: Download the Python Project<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The source code for the BleuIO Python gateway is available on GitHub:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>GitHub:<\/strong> <a href=\"https:\/\/github.com\/smart-sensor-devices-ab\/bleuio-cloudflare-d1-database\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/smart-sensor-devices-ab\/bleuio-cloudflare-d1-database<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Clone the repository:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>git clone https:\/\/github.com\/smart-sensor-devices-ab\/bleuio-cloudflare-d1-database<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then enter the project directory:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd bleuio-cloudflare-d1-database<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The project structure is intentionally small:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>bleuio-cloudflare-d1\/\n\u2502\n\u251c\u2500\u2500 main.py\n\u251c\u2500\u2500 info.example.txt\n\u251c\u2500\u2500 requirements.txt\n\u2514\u2500\u2500 .gitignore<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The Python application handles:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>Connecting to BleuIO<\/li>\n\n\n\n<li>Sending BleuIO AT commands<\/li>\n\n\n\n<li>Searching for the HibouAir board ID<\/li>\n\n\n\n<li>Reading BLE advertising data<\/li>\n\n\n\n<li>Decoding CO2, temperature, and humidity<\/li>\n\n\n\n<li>Displaying the measurement in the terminal<\/li>\n\n\n\n<li>Sending the measurement to Cloudflare<\/li>\n\n\n\n<li>Repeating the process at a configurable interval<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Step 7: Install the Python Dependencies<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">It is recommended to create a Python virtual environment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>python3 -m venv .venv<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Activate it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>source .venv\/bin\/activate<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then install the required libraries:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pip install -r requirements.txt<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The application only requires:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pyserial\nrequests<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>pyserial<\/code> is used to communicate with BleuIO through the USB serial port.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>requests<\/code> is used to send the decoded measurements to the Cloudflare Worker over HTTPS.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 8: Configure the Project<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The repository includes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>info.example.txt<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Copy it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cp info.example.txt info.txt<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then open:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>info.txt<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The configuration looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>BLEUIO_PORT=\/dev\/cu.usbmodem4048FDEBA6D01\nHIBOUAIR_SENSOR_ID=220069\n\nSCAN_INTERVAL_SECONDS=30\n\nCLOUDFLARE_API_URL=https:\/\/hibouair-api.YOUR-SUBDOMAIN.workers.dev\/api\/readings\nCLOUDFLARE_API_KEY=YOUR_REAL_API_KEY<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You need to update the values for your own environment.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">BLEUIO_PORT<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">On our Mac, BleuIO is available at:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/dev\/cu.usbmodem4048FDEBA6D01<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Your port may be different.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can normally find USB serial devices with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ls \/dev\/cu.*<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Look for the device corresponding to BleuIO.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On windows , look for COM port on device manager.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">HIBOUAIR_SENSOR_ID<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">For this project, our HibouAir board ID is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>220069<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Replace this with the board ID of the HibouAir device you want to monitor.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">SCAN_INTERVAL_SECONDS<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">This determines how often the application starts a new HibouAir scan.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For this tutorial:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SCAN_INTERVAL_SECONDS=30<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">means approximately one scan every 30 seconds.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can later change this without modifying the Python source code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SCAN_INTERVAL_SECONDS=60<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">for approximately one reading per minute.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Or:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SCAN_INTERVAL_SECONDS=300<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">for approximately one reading every five minutes.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">CLOUDFLARE_API_URL<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Replace this with the URL of the Worker created earlier:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CLOUDFLARE_API_URL=https:\/\/hibouair-api.YOUR-SUBDOMAIN.workers.dev\/api\/readings<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">CLOUDFLARE_API_KEY<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Enter the same secret that was configured in the Cloudflare Worker:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CLOUDFLARE_API_KEY=YOUR_REAL_API_KEY<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>info.txt<\/code> file should not be committed to GitHub because it contains the API key.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 9: How the HibouAir Data Is Read<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">BleuIO communicates with our Python application using simple AT commands over the serial port.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The application first connects to BleuIO and places it in central mode.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It then searches for our HibouAir board ID using:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>AT+FINDSCANDATA=220069=5<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Here:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>220069<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">is the HibouAir board ID, and:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>5<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">defines the scan duration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">BleuIO handles the Bluetooth Low Energy scanning and returns the matching BLE advertising information over serial.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This means the Python application does not need to directly communicate with the operating system&#8217;s native Bluetooth API.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">BleuIO then returns the advertising data to Python.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 10: Decode the HibouAir Advertisement<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The Python application searches the advertising payload for the HibouAir data section:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>5B070<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For the HibouAir advertisement used in this example, the application extracts the three measurements needed for this project. Temperature, Humidity and CO2. The result becomes a normal Python structure similar to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"co2\": 623,\n    \"temperature\": 24.1,\n    \"humidity\": 43.7\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The application also performs basic validation before uploading the measurements.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, obviously invalid temperature, humidity, or CO2 values are rejected instead of being stored in the database.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 11: Sending the Measurement to Cloudflare<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Once a valid advertisement has been decoded, Python prepares a JSON request.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"board_id\": \"220069\",\n  \"co2\": 623,\n  \"temperature\": 24.1,\n  \"humidity\": 43.7\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is sent using an HTTP <code>POST<\/code> request to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/api\/readings<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The API key is included in the request header:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>X-API-Key<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The Cloudflare Worker validates the key and inserts the measurement into D1.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The Python application therefore never connects directly to the database.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 12: Run the Application<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before running the project, make sure no other program is currently using the BleuIO serial port.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, close any active BleuIO connection in:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>BleuIO application<\/li>\n\n\n\n<li><code>screen<\/code><\/li>\n\n\n\n<li>serial monitor<\/li>\n\n\n\n<li>terminal program<\/li>\n\n\n\n<li>other Python application<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Then run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>python3 main.py<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The application should connect to BleuIO.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You should see output similar to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>BleuIO + HibouAir + Cloudflare D1\n---------------------------------\n\nBleuIO port : \/dev\/cu.usbmodem4048FDEBA6D01\nHibouAir ID : 220069\nScan interval: 30 seconds\n\nTrying BleuIO on \/dev\/cu.usbmodem4048FDEBA6D01 at 115200 baud...\n\nBleuIO connected successfully.\n\nSetting BleuIO to central mode...\nReady.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The application will then start scanning:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Scanning for HibouAir 220069...\n\nCommand: AT+FINDSCANDATA=220069=5<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">When a valid HibouAir advertisement is received and decoded, the terminal will display something similar to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>============================================\n          HIBOUAIR AIR QUALITY\n============================================\n\n  CO2                623 ppm\n  Temperature       24.1 \u00b0C\n  Humidity          43.7 %RH\n\n============================================\n\nUploading to Cloudflare D1...\n\u2713 Reading stored successfully.\n\nNext scan in 23 seconds...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The application continues running automatically.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There is no need to restart it for each measurement.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"682\" src=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-5-1024x682.png\" alt=\"\" class=\"wp-image-1789\" style=\"width:527px;height:auto\" srcset=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-5-1024x682.png 1024w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-5-300x200.png 300w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-5-767x511.png 767w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-5.png 1144w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">To stop the application, press:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Ctrl + C<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 13: Check the Data in Cloudflare D1<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Now return to the Cloudflare D1 database console.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SELECT *\nFROM readings\nORDER BY id DESC\nLIMIT 10;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see the measurements coming from HibouAir.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>id  board_id  measured_at          co2  temperature  humidity\n\n12  220069    2026-10-05 14:58:20  618  24.1         43.8\n11  220069    2026-10-05 14:57:50  621  24.1         43.8\n10  220069    2026-10-05 14:57:20  623  24.1         43.7<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You can also explore the data from your database. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"271\" src=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-6-1024x271.png\" alt=\"\" class=\"wp-image-1790\" style=\"aspect-ratio:3.778730582694876;width:648px;height:auto\" srcset=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-6-1024x271.png 1024w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-6-300x79.png 300w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-6-1536x407.png 1536w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-6-766x203.png 766w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-6-2048x543.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"524\" src=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-7-1024x524.png\" alt=\"\" class=\"wp-image-1792\" srcset=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-7-1024x524.png 1024w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-7-300x153.png 300w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-7-766x392.png 766w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-7-2048x1048.png 2048w, https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/image-7-1536x786.png 1536w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Step 14: Check the Latest Reading Through the API<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We also created a simple read endpoint.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Open:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>https:&#47;&#47;YOUR-WORKER.workers.dev\/api\/readings\/latest<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The Worker will query D1 and return the latest measurement.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"success\": true,\n  \"reading\": {\n    \"id\": 12,\n    \"board_id\": \"220069\",\n    \"measured_at\": \"2026-10-05 14:58:20\",\n    \"co2\": 618,\n    \"temperature\": 24.1,\n    \"humidity\": 43.8\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Building a Dashboard Later<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This tutorial intentionally stops at the database and API level.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The objective is to demonstrate the complete BLE-to-cloud data path while keeping the example easy to understand.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At this point, the data is already available in the cloud.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A developer could later build a dashboard showing:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Current CO\u2082\nCurrent Temperature\nCurrent Humidity\n\nCO\u2082 history\nTemperature history\nHumidity history<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A dashboard could request historical readings through additional Worker API endpoints.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It could also display thresholds, warnings, averages, minimum\/maximum measurements, or other analytics.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Those features are deliberately outside the scope of this example because the main goal is to demonstrate how easily BLE data can be brought into a serverless cloud architecture using BleuIO.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We kept the project intentionally simple so that the individual parts are easy to understand and modify.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">From here, the project can be expanded in many directions. Multiple HibouAir sensors could be monitored, additional air quality parameters could be stored, historical API endpoints could be created, and a complete cloud dashboard could be built on top of the existing data.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this tutorial, we will build a simple BLE-to-cloud air quality gateway using HibouAir, BleuIO, Python, Cloudflare Workers, and Cloudflare D1. A HibouAir sensor will advertise its air quality measurements over Bluetooth Low Energy. BleuIO, connected to a computer through USB, will scan for a specific HibouAir device and provide the BLE advertising data to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1794,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,2],"tags":[],"class_list":["post-1775","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-bleuio","category-bleuio-tutorial"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.6 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Send BLE Air Quality Data to Cloudflare D1 with Python and BleuIO - BleuIO - Create Bluetooth Low Energy application<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Send BLE Air Quality Data to Cloudflare D1 with Python and BleuIO - BleuIO - Create Bluetooth Low Energy application\" \/>\n<meta property=\"og:description\" content=\"In this tutorial, we will build a simple BLE-to-cloud air quality gateway using HibouAir, BleuIO, Python, Cloudflare Workers, and Cloudflare D1. A HibouAir sensor will advertise its air quality measurements over Bluetooth Low Energy. BleuIO, connected to a computer through USB, will scan for a specific HibouAir device and provide the BLE advertising data to [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/\" \/>\n<meta property=\"og:site_name\" content=\"BleuIO - Create Bluetooth Low Energy application\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-05T16:03:49+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-10-05T19:07:08+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/bleuio-cloudflare.webp\" \/>\n\t<meta property=\"og:image:width\" content=\"1376\" \/>\n\t<meta property=\"og:image:height\" content=\"768\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/webp\" \/>\n<meta name=\"author\" content=\"BleuIO\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\\\/\"},\"author\":{\"name\":\"BleuIO\",\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/#\\\/schema\\\/person\\\/89bc581382d5964043f96efc54b75b80\"},\"headline\":\"Send BLE Air Quality Data to Cloudflare D1 with Python and BleuIO\",\"datePublished\":\"2026-10-05T16:03:49+00:00\",\"dateModified\":\"2026-10-05T19:07:08+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\\\/\"},\"wordCount\":1556,\"commentCount\":0,\"image\":{\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/bleuio-cloudflare.webp\",\"articleSection\":[\"BleuIO\",\"BleuIO tutorial\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\\\/\",\"url\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\\\/\",\"name\":\"Send BLE Air Quality Data to Cloudflare D1 with Python and BleuIO - BleuIO - Create Bluetooth Low Energy application\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/bleuio-cloudflare.webp\",\"datePublished\":\"2026-10-05T16:03:49+00:00\",\"dateModified\":\"2026-10-05T19:07:08+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/#\\\/schema\\\/person\\\/89bc581382d5964043f96efc54b75b80\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/bleuio-cloudflare.webp\",\"contentUrl\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/bleuio-cloudflare.webp\",\"width\":1376,\"height\":768,\"caption\":\"bleuio cloudflare\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Send BLE Air Quality Data to Cloudflare D1 with Python and BleuIO\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/\",\"name\":\"BleuIO - Create Bluetooth Low Energy application\",\"description\":\"Learn Bluetooth Low Energy programming and build Bluetooth Low Energy Application\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/#\\\/schema\\\/person\\\/89bc581382d5964043f96efc54b75b80\",\"name\":\"BleuIO\",\"sameAs\":[\"https:\\\/\\\/www.bleuio.com\\\/blog\"],\"url\":\"https:\\\/\\\/www.bleuio.com\\\/blog\\\/author\\\/biadmin\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Send BLE Air Quality Data to Cloudflare D1 with Python and BleuIO - BleuIO - Create Bluetooth Low Energy application","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/","og_locale":"en_US","og_type":"article","og_title":"Send BLE Air Quality Data to Cloudflare D1 with Python and BleuIO - BleuIO - Create Bluetooth Low Energy application","og_description":"In this tutorial, we will build a simple BLE-to-cloud air quality gateway using HibouAir, BleuIO, Python, Cloudflare Workers, and Cloudflare D1. A HibouAir sensor will advertise its air quality measurements over Bluetooth Low Energy. BleuIO, connected to a computer through USB, will scan for a specific HibouAir device and provide the BLE advertising data to [&hellip;]","og_url":"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/","og_site_name":"BleuIO - Create Bluetooth Low Energy application","article_published_time":"2026-10-05T16:03:49+00:00","article_modified_time":"2026-10-05T19:07:08+00:00","og_image":[{"width":1376,"height":768,"url":"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/bleuio-cloudflare.webp","type":"image\/webp"}],"author":"BleuIO","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/#article","isPartOf":{"@id":"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/"},"author":{"name":"BleuIO","@id":"https:\/\/www.bleuio.com\/blog\/#\/schema\/person\/89bc581382d5964043f96efc54b75b80"},"headline":"Send BLE Air Quality Data to Cloudflare D1 with Python and BleuIO","datePublished":"2026-10-05T16:03:49+00:00","dateModified":"2026-10-05T19:07:08+00:00","mainEntityOfPage":{"@id":"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/"},"wordCount":1556,"commentCount":0,"image":{"@id":"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/#primaryimage"},"thumbnailUrl":"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/bleuio-cloudflare.webp","articleSection":["BleuIO","BleuIO tutorial"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/","url":"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/","name":"Send BLE Air Quality Data to Cloudflare D1 with Python and BleuIO - BleuIO - Create Bluetooth Low Energy application","isPartOf":{"@id":"https:\/\/www.bleuio.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/#primaryimage"},"image":{"@id":"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/#primaryimage"},"thumbnailUrl":"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/bleuio-cloudflare.webp","datePublished":"2026-10-05T16:03:49+00:00","dateModified":"2026-10-05T19:07:08+00:00","author":{"@id":"https:\/\/www.bleuio.com\/blog\/#\/schema\/person\/89bc581382d5964043f96efc54b75b80"},"breadcrumb":{"@id":"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/#primaryimage","url":"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/bleuio-cloudflare.webp","contentUrl":"https:\/\/www.bleuio.com\/blog\/wp-content\/uploads\/2026\/10\/bleuio-cloudflare.webp","width":1376,"height":768,"caption":"bleuio cloudflare"},{"@type":"BreadcrumbList","@id":"https:\/\/www.bleuio.com\/blog\/send-ble-air-quality-data-to-cloudflare-d1-with-python-and-bleuio\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.bleuio.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Send BLE Air Quality Data to Cloudflare D1 with Python and BleuIO"}]},{"@type":"WebSite","@id":"https:\/\/www.bleuio.com\/blog\/#website","url":"https:\/\/www.bleuio.com\/blog\/","name":"BleuIO - Create Bluetooth Low Energy application","description":"Learn Bluetooth Low Energy programming and build Bluetooth Low Energy Application","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.bleuio.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.bleuio.com\/blog\/#\/schema\/person\/89bc581382d5964043f96efc54b75b80","name":"BleuIO","sameAs":["https:\/\/www.bleuio.com\/blog"],"url":"https:\/\/www.bleuio.com\/blog\/author\/biadmin\/"}]}},"_links":{"self":[{"href":"https:\/\/www.bleuio.com\/blog\/wp-json\/wp\/v2\/posts\/1775","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.bleuio.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.bleuio.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.bleuio.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.bleuio.com\/blog\/wp-json\/wp\/v2\/comments?post=1775"}],"version-history":[{"count":14,"href":"https:\/\/www.bleuio.com\/blog\/wp-json\/wp\/v2\/posts\/1775\/revisions"}],"predecessor-version":[{"id":1799,"href":"https:\/\/www.bleuio.com\/blog\/wp-json\/wp\/v2\/posts\/1775\/revisions\/1799"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.bleuio.com\/blog\/wp-json\/wp\/v2\/media\/1794"}],"wp:attachment":[{"href":"https:\/\/www.bleuio.com\/blog\/wp-json\/wp\/v2\/media?parent=1775"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.bleuio.com\/blog\/wp-json\/wp\/v2\/categories?post=1775"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.bleuio.com\/blog\/wp-json\/wp\/v2\/tags?post=1775"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}