Most blood glucose meters are designed around a fairly straightforward workflow: take a measurement, read the result from the display and, if required, synchronize the meter with the manufacturer’s application. That works perfectly well, but for anyone already running Home Assistant it raises an interesting question: can those measurements be retrieved locally and brought directly into Home Assistant?
That was the goal of this project.
The idea is to eventually implement this for a friend who already uses Home Assistant. The solution therefore needed to be reliable, reasonably simple to operate and, preferably, completely local. There should be no requirement to modify the glucose meter, no dependence on a manufacturer’s cloud API and no complicated procedure whenever a measurement is taken.
The meter used for the project is an Accu-Chek Instant. An ESP32-C6 running ESPHome acts as the Bluetooth bridge between the meter and Home Assistant.
What initially sounded like a fairly simple Bluetooth project turned out to involve secure BLE pairing, Bluetooth bonding, the standard Bluetooth Glucose Service, the Record Access Control Point, IEEE-11073 floating-point values, historical record retrieval and some custom ESPHome C++ code.
The result, however, is surprisingly simple to use.
Take a measurement normally, wake the meter when synchronization is required and press Sync Accu-Chek in Home Assistant. A few seconds later the latest measurement appears on the dashboard and is automatically stored in MySQL.
The complete data path looks like this:
Accu-Chek Instant
-- Bluetooth LE --
ESP32-C6 / ESPHome
-- -- --
Home Assistant
-- -- --
MySQL
ESPHome provides the following information to Home Assistant:
- Glucose mmol/L
- Glucose mg/dL
- Measurement timestamp
- Sequence number
Home Assistant then takes care of the dashboard, statistics and database automation.
What this project does — and what it doesn’t do
First, this does not turn the Accu-Chek Instant into a continuous glucose monitor. The Accu-Chek remains a normal blood glucose meter and measurements are still performed exactly as intended by the manufacturer.
The normal procedure remains unchanged. Insert a test strip, which wakes the meter automatically, wait for the meter to request the sample, perform the measurement and read the result from the Accu-Chek display.
The difference comes afterwards.
The measurement is stored internally by the Accu-Chek. The ESP32 can later connect to the meter using Bluetooth Low Energy and retrieve those stored records.
The practical workflow therefore becomes:
1. Take measurement
2. Accu-Chek stores result
3. Wake meter
4. Press Sync Accu-Chek
5. ESPHome retrieves records
6. Home Assistant updates
7. Measurement stored in MySQL
One particularly useful discovery during development was that the meter doesn’t only expose the most recent measurement. Using the Bluetooth Glucose Service’s Record Access Control Point, the ESP32 can request all measurements currently stored in the meter. That made it possible to retrieve historical measurements as well.
Hardware and software
The hardware requirements for this project are relatively modest. The most important component besides the glucose meter itself is an ESP32 capable of Bluetooth Low Energy communication. For this implementation I used an ESP32-C6 running ESPHome.
The main components are:
- Accu-Chek Instant glucose meter
- ESP32-C6 based development board
- ESPHome
- Home Assistant
- Pyscript for Home Assistant
- MySQL or MariaDB for permanent storage
- custom:button-card for the dashboard
- custom:statistics-graph-chart-card for historical visualization
- card-mod for some additional dashboard styling
The ESP32 is dedicated to communicating with the glucose meter. It doesn’t need to be physically connected to the Accu-Chek because all communication takes place over Bluetooth Low Energy.
Step 1 – Finding the Accu-Chek over Bluetooth
The first step was simply establishing whether the meter could be discovered by the ESP32.
The Accu-Chek advertises itself as a Bluetooth Low Energy device. Once its BLE MAC address is known, ESPHome can be configured as a BLE client.
I won’t publish the real Bluetooth address of the meter used during development. Throughout this article I’ll use the following placeholder instead:
AA:BB:CC:DD:EE:FF
Replace that address with the BLE MAC address of your own Accu-Chek.
The basic ESPHome Bluetooth configuration looks like this:
esp32_ble:
io_capability: keyboard_only
esp32_ble_tracker:
scan_parameters:
active: true
continuous: true
ble_client:
- mac_address: AA:BB:CC:DD:EE:FF
id: accu_chek
auto_connect: false
An important setting here is:
auto_connect: false
There is no reason for the ESP32 to maintain a permanent connection to the glucose meter. The Accu-Chek isn’t a continuously broadcasting sensor such as a temperature sensor. Instead, the ESP32 connects only when a synchronization is requested. That turned out to be a much cleaner approach.
Step 2 – A Bluetooth connection isn’t enough
The first connection attempt actually looked promising. ESPHome was able to find the meter, connect to it and discover its Bluetooth services.
The log contained lines similar to:
[11:10:20.993][I][main:1131]: ========================================
[11:10:20.994][I][main:1134]: === ACCU-CHEK CONNECTED ===
[11:10:20.995][I][main:1137]: === REQUESTING BLE ENCRYPTION ===
[11:10:20.999][I][accu_chek:726]: esp_ble_set_encryption result: 0
[11:10:21.002][D][ble_client.automation:199]: Write type: ESP_GATT_WRITE_TYPE_RSP
[11:10:21.004][D][ble_client.automation:210]: Found characteristic 0x2A52 on device AA:BB:CC:DD:EE:FF
[11:10:21.006][D][esp32_ble_client:411]: [0] [AA:BB:CC:DD:EE:FF] cfg_mtu status 0, mtu 23
[11:10:21.180][I][esp32_ble_client:572]: [0] [AA:BB:CC:DD:EE:FF] auth complete addr: AA:BB:CC:DD:EE:FF
[11:10:21.181][D][esp32_ble_client:577]: [0] [AA:BB:CC:DD:EE:FF] auth success type = 0 mode = 5
At this point it was tempting to think the difficult part had already been solved. Unfortunately, a BLE connection and a trusted BLE connection are two different things.
The Accu-Chek protects its data using Bluetooth security. The ESP32 could connect and discover the meter’s services, but protected communication required authentication and encryption. Without that step, simply connecting wasn’t enough to retrieve the glucose history.
Step 3 – Secure Bluetooth pairing
ESPHome therefore needs to support the pairing procedure expected by the meter.
The BLE configuration uses:
esp32_ble:
io_capability: keyboard_only
When the ESP32 connects, the configuration explicitly requests an encrypted connection. The relevant part of the BLE client configuration looks like this:
on_connect:
then:
- logger.log:
level: INFO
format: "=== ACCU-CHEK CONNECTED ==="
- logger.log:
level: INFO
format: "=== REQUESTING BLE ENCRYPTION ==="
- lambda: |-
esp_err_t err = esp_ble_set_encryption(
id(accu_chek)->get_remote_bda(),
ESP_BLE_SEC_ENCRYPT_MITM
);
ESP_LOGI(
"accu_chek",
"esp_ble_set_encryption result: %d",
err
);
During the initial pairing procedure the meter can request authentication. The actual pairing credential is deliberately not included in this article. It should also never be placed in a public GitHub repository or shown in a screenshot.
If it needs to be stored in the ESPHome configuration, put it in secrets.yaml, for example:
accu_chek_passkey: YOUR_PRIVATE_PASSKEY
The ESPHome configuration can then reference the secret rather than containing the actual value.
Step 4 – Confirming that Bluetooth authentication succeeded
This was one of the important breakthroughs in the project.
Once encryption and authentication were working, the ESPHome log showed a very clear sequence:
=== ACCU-CHEK CONNECTED ===
=== REQUESTING BLE ENCRYPTION ===
esp_ble_set_encryption result: 0
auth complete
auth success type = 0 mode = 5
The most important line is:
auth success type = 0 mode = 5
That confirms that authentication completed successfully.
After the initial pairing, subsequent connections don’t necessarily show the original passkey exchange again. That’s expected because the ESP32 and Accu-Chek can reuse their existing Bluetooth bond. This is actually preferable for normal operation: pairing is something that should be established once rather than repeated for every synchronization.
The next lines in the log:
marks the transition from Bluetooth authentication to actual glucose-data retrieval.
Step 5 – Discovering the Bluetooth Glucose Service
Once secure communication was established, the next question was: where is the actual glucose data?
Fortunately, the Accu-Chek uses the standardized Bluetooth Glucose Service. The service UUID is:
0x1808
Inside that service are several standardized characteristics. The important ones for this project are:
0x2A18 -- Glucose Measurement
0x2A34 -- Glucose Measurement Context
0x2A52 -- Record Access Control Point
The first one, 0x2A18, carries glucose measurements. The second, 0x2A34, can contain additional context associated with a measurement. The third, 0x2A52, is particularly important because it is the Record Access Control Point, usually abbreviated to RACP.
Simply subscribing to 0x2A18 isn’t enough to retrieve the stored history. The meter has to be told which records we want. That’s what the RACP is for.
Step 6 – Requesting all stored glucose records
The Bluetooth Glucose Service defines commands that can be written to the Record Access Control Point. For this project we want all measurements stored in the meter.
The command is remarkably small:
01 01
Those two bytes mean:
01 -- Report Stored Records
01 -- All Records
In ESPHome, the request can be written like this:
- ble_client.ble_write:
id: accu_chek
service_uuid: "1808"
characteristic_uuid: "2A52"
value:
- 0x01
- 0x01
Once the meter accepts that command, it starts sending its stored measurements through the Glucose Measurement characteristic.
The synchronization sequence therefore becomes:
Connect
Authenticate
Enable notifications
Send RACP 01 01
Receive stored glucose records
Receive completion response
Publish newest record
This was the point where the project became really interesting, because the meter started returning not just one measurement but a complete series of stored records.
Step 7 – Recognizing when the download is complete
The meter eventually responds through the RACP characteristic to indicate that the requested operation has finished.
A successful completion response looks like:
06 00 01 01
The bytes represent:
06 -- Response Code
00 -- Null Operator
01 -- Report Stored Records
01 -- Success
This gives ESPHome a reliable indication that the complete record download has finished. That’s useful because I don’t want Home Assistant to be updated repeatedly while ESPHome is stepping through historical measurements.
Instead, ESPHome receives the complete history, remembers the newest valid record and only publishes that record once the RACP operation reports success.
A useful log excerpt at this stage looks like:
[11:10:24.136][I][main:816]: === REQUESTING ALL STORED GLUCOSE RECORDS ===
[11:10:24.242][I][accu_chek_glucose:518]: ----------------------------------------
[11:10:24.242][I][accu_chek_glucose:523]: Sequence: 2
[11:10:24.242][I][accu_chek_glucose:529]: Measurement time: 2026-04-19 07:47:35
[11:10:24.242][I][accu_chek_glucose:535]: Time offset: +122 minutes
[11:10:24.242][I][accu_chek_glucose:541]: Glucose: 8.1 mmol/L
[11:10:24.242][I][accu_chek_glucose:547]: Glucose: 146 mg/dL
[11:10:24.242][I][accu_chek_glucose:553]: Sample type: 8
[11:10:24.242][I][accu_chek_glucose:559]: Sample location: 15
[11:10:24.242][I][accu_chek_glucose:518]: ----------------------------------------
[11:10:24.242][I][accu_chek_glucose:523]: Sequence: 3
[11:10:24.242][I][accu_chek_glucose:529]: Measurement time: 2026-04-20 08:06:45
[11:10:24.242][I][accu_chek_glucose:535]: Time offset: +123 minutes
[11:10:24.242][I][accu_chek_glucose:541]: Glucose: 8.4 mmol/L
[11:10:24.242][I][accu_chek_glucose:547]: Glucose: 152 mg/dL
[11:10:24.243][I][accu_chek_glucose:553]: Sample type: 8
[11:10:24.243][I][accu_chek_glucose:559]: Sample location: 15
[11:10:24.243][I][accu_chek_glucose:518]: ----------------------------------------
[11:10:24.243][I][accu_chek_glucose:523]: Sequence: 4
[11:10:24.243][I][accu_chek_glucose:529]: Measurement time: 2026-04-20 10:28:47
[11:10:24.243][I][accu_chek_glucose:535]: Time offset: +122 minutes
[11:10:24.243][I][accu_chek_glucose:541]: Glucose: 9.6 mmol/L
[11:10:24.243][I][accu_chek_glucose:547]: Glucose: 173 mg/dL
[11:10:24.243][I][accu_chek_glucose:553]: Sample type: 8
[11:10:24.243][I][accu_chek_glucose:559]: Sample location: 15
Step 8 – Decoding the glucose record
Receiving the Bluetooth packet is only half the job. The bytes still have to be interpreted.
A Bluetooth Glucose Measurement can contain several pieces of information:
- Flags
- Sequence number
- Base timestamp
- Optional time offset
- Glucose concentration
- Measurement units
- Sample type
- Sample location
- Optional sensor status information
The sequence number turned out to be particularly useful. Each measurement stored by the meter receives a sequence number, for example:
23
24
25
26
That gives us a convenient way to identify individual measurements later.
The timestamp is reconstructed from the date and time contained in the BLE record. Where a time offset is present, that is also taken into account before the final timestamp is published. The result is a timestamp such as:
2026-08-19 08:51:16
This represents when the measurement was actually taken, not when Home Assistant synchronized it. That distinction becomes very useful when importing historical measurements.
Step 9 – Decoding IEEE-11073 SFLOAT
The glucose concentration isn’t stored as a normal integer or conventional IEEE floating-point value. The Bluetooth Glucose Service uses an IEEE-11073 16-bit SFLOAT representation.
The value contains a signed mantissa and a signed base-10 exponent. These have to be decoded before the actual glucose concentration can be calculated. Conceptually:
SFLOAT
-> extract exponent
-> extract mantissa
-> apply 10^exponent
-> glucose concentration
This decoding happens inside the ESPHome lambda. Once the raw concentration has been reconstructed, it can be exposed in the units required by Home Assistant.
Step 10 – mmol/L and mg/dL
I wanted Home Assistant to expose both common glucose units:
mmol/L
mg/dL
The ESPHome device therefore provides separate sensors for each.
One detail became important later in the project: don’t calculate the mg/dL display value from the already-rounded mmol/L value shown in Home Assistant.
For example, the dashboard may display:
8.9 mmol/L
161.0 mg/dL
At first glance, someone might calculate:
8.9 x 18 = 160.2
and conclude that 161.0 mg/dL must be wrong.
But the 8.9 mmol/L value shown on the dashboard has already been rounded to one decimal place. The underlying BLE value can contain more precision. For that reason the implementation preserves the original BLE-derived values rather than taking the rounded Home Assistant mmol/L value and converting it again.
Step 11 – Creating the ESPHome sensors
The finished ESPHome device exposes four useful entities to Home Assistant:
Accu-Chek Glucose
Accu-Chek Glucose mgdl
Accu-Chek Measurement Time
Accu-Chek Sequence Number
The main glucose sensor looks like this:
sensor:
- platform: template
id: accu_chek_glucose
name: "Accu-Chek Glucose"
icon: mdi:diabetes
unit_of_measurement: "mmol/L"
accuracy_decimals: 1
state_class: measurement
update_interval: never
The important setting here is:
state_class: measurement
This allows Home Assistant to build statistics for the sensor, which becomes useful later when creating the historical graph.
The mg/dL sensor is similar:
- platform: template
id: accu_chek_glucose_mgdl
name: "Accu-Chek Glucose mgdl"
icon: mdi:diabetes
unit_of_measurement: "mg/dL"
accuracy_decimals: 1
update_interval: never
Originally the mg/dL sensor used:
accuracy_decimals: 0
That resulted in whole-number presentation. It was later changed to:
accuracy_decimals: 1
which allows Home Assistant to display values such as:
161.0 mg/dL
The important point here is that only the display precision was changed. The underlying BLE conversion wasn’t replaced with a calculation based on the rounded mmol/L value.
Step 12 – Keeping track of the newest record
When all stored records are requested, the meter can return multiple measurements. ESPHome therefore keeps track of the newest record during each synchronization session.
The relevant global variables are conceptually:
accu_chek_latest_valid
accu_chek_latest_sequence
accu_chek_latest_mmol
accu_chek_latest_mgdl
accu_chek_latest_time
When another measurement arrives with a newer sequence number, those values are replaced. Only after the RACP reports that the complete download was successful are they published to Home Assistant.
The publication itself looks roughly like:
id(accu_chek_glucose).publish_state(id(accu_chek_latest_mmol));
id(accu_chek_glucose_mgdl).publish_state(id(accu_chek_latest_mgdl));
id(accu_chek_sequence).publish_state(id(accu_chek_latest_sequence));
id(accu_chek_measurement_time).publish_state(id(accu_chek_latest_time));
That prevents the Home Assistant dashboard from cycling through the historical values every time a synchronization is performed.
Step 13 – Why I chose manual synchronization
An obvious question is why not synchronize automatically immediately after every measurement.
The normal measurement sequence is approximately:
Insert test strip
Meter wakes
Meter waits for sample
Measurement performed
Result displayed
Meter eventually switches off
In theory, the ESP32 could continuously watch Bluetooth advertisements and attempt to determine when a new measurement has been completed. In practice, that introduces a lot of additional logic. The ESP32 would have to distinguish between the meter merely waking up and the measurement actually being finished, handle failed connection attempts and potentially interfere with the normal meter workflow.
For this project, manual synchronization is a much cleaner solution. Take the measurement normally. When synchronization is required, wake the meter and press the Home Assistant button. That’s it.
Step 14 – Creating the ESPHome Sync button
ESPHome exposes a template button that starts the synchronization. A simplified version looks like this:
button:
- platform: template
name: "Sync Accu-Chek"
icon: mdi:sync
on_press:
then:
- lambda: |-
id(accu_chek_latest_valid) = false;
id(accu_chek_latest_sequence) = 0;
- logger.log:
level: INFO
format: "=== ACCU-CHEK SYNC STARTED ==="
- ble_client.connect:
id: accu_chek
- delay: 3s
- logger.log:
level: INFO
format: "=== REQUESTING ALL STORED GLUCOSE RECORDS ==="
- ble_client.ble_write:
id: accu_chek
service_uuid: "1808"
characteristic_uuid: "2A52"
value:
- 0x01
- 0x01
- delay: 20s
- ble_client.disconnect:
id: accu_chek
- logger.log:
level: INFO
format: "=== ACCU-CHEK SYNC FINISHED ==="
The exact timing can be adjusted if necessary, but the idea is simple: reset the current synchronization state, connect, allow the secure connection and subscriptions to become ready, request the stored records and finally disconnect.
Step 15 – The meter needs to be awake
This produced one of the more confusing errors during testing. Sometimes pressing Sync resulted in:
ESP_GATTC_DISCONNECT_EVT, reason 0x100
ESP_GATTC_OPEN_EVT in DISCONNECTING state (status=133)
Connection open error, status=133
That initially looked like a Bluetooth stack problem. In practice, the meter simply wasn’t ready to accept the connection.
The most reliable sequence turned out to be:
Wake Accu-Chek
Wait briefly
Press Sync Accu-Chek
Once the meter is awake, the expected log sequence should appear:
Connection open
Service discovery complete
=== ACCU-CHEK CONNECTED ===
=== REQUESTING BLE ENCRYPTION ===
auth success
=== REQUESTING ALL STORED GLUCOSE RECORDS ===
So if you encounter status 133, don’t immediately start rewriting the BLE implementation. First make sure the meter is awake and available.
Step 16 – Importing historical measurements
One of the best parts of using RACP is that synchronization isn’t limited to measurements taken after Home Assistant was installed.
The request:
01 01
asks the meter to report all stored records. That means a newly installed ESPHome device can retrieve measurements that were already stored in the Accu-Chek.
Each record includes its original measurement time and sequence number, so those historical values don’t have to be treated as if they were measured at synchronization time. This became particularly useful when creating the MySQL database.
Step 17 – Why add MySQL when Home Assistant already has history?
Home Assistant’s Recorder and statistics functionality are perfectly capable of showing historical sensor data. For this project, however, I also wanted a separate permanent record of every individual glucose measurement.
The MySQL table therefore serves a slightly different purpose from Home Assistant’s statistics.
Home Assistant statistics are used for:
Dashboard graphs
Trends
Quick historical visualization
MySQL is used for:
Individual measurements
Original timestamps
Sequence numbers
Permanent structured storage
This separation keeps the dashboard simple while still providing a proper database that can be used for future analysis.
Step 18 – Creating the MySQL table
The database table needs to contain more than just the glucose value. Useful columns include:
id
sequence_number
measured_at
glucose_mmol
glucose_mgdl
sample_type
sample_location
source
synced_at
A suitable table can be created with:
CREATE TABLE glucose_measurements (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
sequence_number INT UNSIGNED NOT NULL,
measured_at DATETIME NOT NULL,
glucose_mmol DECIMAL(4,1) NOT NULL,
glucose_mgdl DECIMAL(6,1) NOT NULL,
sample_type TINYINT UNSIGNED DEFAULT NULL,
sample_location TINYINT UNSIGNED DEFAULT NULL,
source VARCHAR(50) NOT NULL DEFAULT 'Accu-Chek Instant',
synced_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_sequence_source (sequence_number, source),
INDEX idx_measured_at (measured_at)
);
The sequence number is especially useful because it can prevent the same meter record from being stored repeatedly. The unique key:
UNIQUE KEY uq_sequence_source (sequence_number, source)
provides database-level duplicate protection.
Step 19 – Fixing mg/dL precision in MySQL
The mg/dL column was initially stored as an integer type. That worked, but it meant values were permanently reduced to whole numbers, for example:
160.2 -> 160
That wasn’t what I wanted. The column was therefore changed to a decimal:
ALTER TABLE glucose_measurements
MODIFY glucose_mgdl DECIMAL(6,1) NULL;
With that change, MySQL can retain values such as:
135.0
145.0
161.0
228.0
This change alone isn’t enough, though. The value also has to remain a float while Home Assistant sends it to MySQL.
Step 20 – Writing directly to MySQL from Home Assistant
Initially, it would have been possible to create a PHP endpoint and have Home Assistant call that endpoint. But that would add another component purely for inserting a database row.
Since Pyscript was already available in Home Assistant, it made more sense to perform the insert directly.
The script is stored under:
/config/pyscript/
for example:
/config/pyscript/accu_chek_mysql_insert.py
The script exposes a Home Assistant action:
pyscript.accu_chek_mysql_insert
A simplified version of the database insert looks like this:
import pymysql
@service
def accu_chek_mysql_insert(
sequence_number=None,
measured_at=None,
glucose_mmol=None,
glucose_mgdl=None,
):
connection = pymysql.connect(
host="MYSQL_SERVER",
user="MYSQL_USER",
password="MYSQL_PASSWORD",
database="MYSQL_DATABASE",
port=3306,
autocommit=True,
)
try:
with connection.cursor() as cursor:
sql = """
INSERT IGNORE INTO glucose_measurements (
sequence_number, measured_at,
glucose_mmol, glucose_mgdl,
sample_type, sample_location, source
) VALUES (%s, %s, %s, %s, %s, %s, %s)
"""
cursor.execute(
sql,
(
int(sequence_number),
measured_at,
float(glucose_mmol),
float(glucose_mgdl),
8,
15,
"Accu-Chek Instant",
),
)
finally:
connection.close()
Naturally, replace the example server, username, password and database with your own configuration, preferably using a secure mechanism rather than publishing credentials directly inside the script.
Step 21 – A small but important Python change
There was one small detail in the Pyscript that mattered for mg/dL precision.
Originally, the insert converted the value using:
int(glucose_mgdl)
That immediately removes the decimal portion. The correct version is:
float(glucose_mgdl)
The final insert therefore uses:
cursor.execute(
sql,
(
int(sequence_number),
measured_at,
float(glucose_mmol),
float(glucose_mgdl),
8,
15,
"Accu-Chek Instant",
),
)
With that change, the entire path preserves the intended precision:
Accu-Chek BLE value
-> ESPHome float
-> Home Assistant sensor
-> Pyscript float()
-> MySQL DECIMAL(6,1)
Step 22 – Testing the Pyscript before creating the automation
Before involving an automation, it’s worth testing the database insert manually.
Home Assistant Developer Tools can call:
action: pyscript.accu_chek_mysql_insert
data:
sequence_number: 999
measured_at: "2026-08-18 21:01:00"
glucose_mmol: 12.7
glucose_mgdl: 228.0
After running it, check MySQL. If the row exists, you know this entire part works:
Home Assistant -> Pyscript -> MySQL
That means any remaining problem is somewhere in the ESPHome synchronization or automation logic rather than the database connection. Delete the temporary test row afterwards.
Step 23 – Automatically storing new measurements
Once the Pyscript works, Home Assistant can store new synchronized measurements automatically.
The sequence number is particularly useful as the trigger. Using only the glucose value would be less reliable because two consecutive legitimate measurements could have exactly the same glucose value. A sequence number, on the other hand, identifies the individual meter record.
A generic automation looks like:
alias: "Accu-Chek: Store new glucose measurement in MySQL"
description: Store a newly synchronized Accu-Chek measurement in MySQL
triggers:
- trigger: state
entity_id:
- sensor.accu_chek_sequence_number
conditions:
- condition: template
value_template: >
{{ trigger.from_state is not none and
trigger.to_state is not none and
trigger.from_state.state not in ['unknown', 'unavailable', 'none', ''] and
trigger.to_state.state not in ['unknown', 'unavailable', 'none', ''] and
trigger.to_state.state | int > trigger.from_state.state | int }}
actions:
- delay:
seconds: 1
- action: pyscript.accu_chek_mysql_insert
data:
sequence_number: >
{{ states('sensor.accu_chek_sequence_number') | int }}
measured_at: >
{{ states('sensor.accu_chek_measurement_time') }}
glucose_mmol: >
{{ states('sensor.accu_chek_glucose') | float }}
glucose_mgdl: >
{{ states('sensor.accu_chek_glucose_mgdl') | float }}
mode: single
Replace the generic entity IDs with the entities generated by your own ESPHome device. The short delay gives all four ESPHome entities time to settle before the database values are collected. Combined with the unique key in MySQL, this provides good protection against accidentally inserting the same measurement more than once.
Step 24 – Checking the database
After a successful synchronization, the MySQL table should contain something along these lines:
sequence_number | measured_at | glucose_mmol | glucose_mgdl | sample_type | sample_location | source | synced_at
26 | 2026-08-19 08:51:16 | 8.9 | 161.0 | 8 | 15 | Accu-Chek Instant | 2026-08-19 08:52:53
25 | 2026-08-18 21:01:00 | 12.7 | 228.0 | 8 | 15 | Accu-Chek Instant | 2026-08-18 21:20:40
24 | 2026-08-18 16:19:46 | 8.0 | 145.0 | 8 | 15 | Accu-Chek Instant | 2026-08-18 21:20:40
23 | 2026-08-18 16:03:44 | 7.5 | 135.0 | 8 | 15 | Accu-Chek Instant | 2026-08-18 21:20:40
Notice the difference between measured_at and synced_at. measured_at is the timestamp stored by the Accu-Chek when the measurement was taken. synced_at tells us when the record was inserted into MySQL. That’s especially useful when historical records are imported long after they were originally measured.
Step 25 – Building a dedicated Home Assistant view
Initially I considered placing the glucose information inside a popup on an existing dashboard. After experimenting with it, I decided that a dedicated view was much better.
A dedicated Blood Sugar view opens faster, particularly on a desktop or wall-mounted tablet, and gives the historical graph much more space.
The final layout uses two columns. The left column contains:
Current glucose card
Sync Accu-Chek button
The right column contains:
Historical glucose statistics graph
On a wide display, this produces a clean and balanced dashboard.
Step 26 – Creating the current glucose card
The current measurement card uses custom:button-card. The card displays:
- Current glucose in mmol/L
- Current glucose in mg/dL
- Sequence number
- Original measurement time
- A subtle background color based on the current value
The main mmol/L value is deliberately large so that it can be read from a distance.
The timestamp is kept compact:
Last measurement: 19 Aug - 08:51
instead of displaying something much longer such as:
2026-08-19 08:51:16
For a dashboard, the shorter version is much easier to scan.
Step 27 – Adding subtle glucose range colors
The card background changes depending on the glucose value. For this example dashboard, the presentation ranges are:
Below 3.9 mmol/L -- Red
3.9 to 8.9 mmol/L -- Green
9.0 mmol/L and above -- Yellow
The color is deliberately very faint. I wanted a visual indication without turning the dashboard into a collection of bright warning panels.
The button-card state configuration looks like this:
state:
- operator: template
value: |
[[[
const v = parseFloat(entity.state);
return !isNaN(v) && v < 3.9; ]]] styles: card: - background: rgba(244, 67, 54, 0.10) - border: 1px solid rgba(244, 67, 54, 0.24) - operator: template value: | [[[ const v = parseFloat(entity.state); return !isNaN(v) && v >= 3.9 && v < 9.0; ]]] styles: card: - background: rgba(76, 175, 80, 0.10) - border: 1px solid rgba(76, 175, 80, 0.24) - operator: template value: | [[[ const v = parseFloat(entity.state); return !isNaN(v) && v >= 9.0;
]]]
styles:
card:
- background: rgba(255, 193, 7, 0.10)
- border: 1px solid rgba(255, 193, 7, 0.24)
These are dashboard presentation thresholds, not personalized medical targets. When implementing this for another person, the visual ranges should be configured appropriately for that person and their healthcare guidance.
Step 28 – Displaying the mg/dL value correctly
Once the ESPHome sensor and MySQL database supported decimal mg/dL values, the dashboard needed one final adjustment.
The original card used:
return `${Math.round(v)} mg/dL`;
That deliberately rounds the value to a whole number. To display one decimal instead, use:
return `${v.toFixed(1)} mg/dL`;
Now the complete chain is consistent:
ESPHome -- 161.0 mg/dL
Home Assistant -- 161.0 mg/dL
Dashboard -- 161.0 mg/dL
MySQL -- 161.0 mg/dL
Step 29 – Creating the dashboard Sync button
The synchronization button is intentionally large because it’s the main interaction on the page. A generic version looks like this:
type: custom:button-card
entity: button.accu_chek_sync
name: Sync Accu-Chek
icon: mdi:sync
show_state: false
tap_action:
action: call-service
service: button.press
service_data:
entity_id: button.accu_chek_sync
styles:
card:
- height: 110px
- border-radius: 24px
- padding: 20px 32px
- background: var(--card-background-color)
- box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22)
grid:
- grid-template-areas: '"i n"'
- grid-template-columns: 60px 1fr
icon:
- width: 40px
- color: var(--primary-color)
- justify-self: start
name:
- justify-self: start
- font-size: 28px
- font-weight: 500
The large button works particularly well on a tablet because there is no tiny control to hunt for. Wake the meter, tap Sync and wait a few seconds.
Step 30 – Adding the historical glucose graph
The right-hand side of the dashboard contains the historical graph. For this I used:
custom:statistics-graph-chart-card
The final configuration looks like:
type: custom:statistics-graph-chart-card
show_legend: true
tooltip_match_axis: false
tooltip_order: default
height: 356
gauge_value_position: below
datetime_format: DD/MM
x_grid_style: dashed
y_grid_style: dashed
show_date_picker: true
date_picker_modes:
- day
- week
- month
- last_7d
- last_15d
- last_30d
entities:
- entity: sensor.accu_chek_glucose
name: Glucose
annotations: []
card_mod:
style: |
ha-card {
border-radius: 24px;
overflow: hidden;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.20);
border: none;
}
The height was deliberately set to 356, because that visually matches the height of the cards in the left-hand column. The 24px border radius also matches the other cards and gives the complete view a much more modern appearance.
Step 31 – Home Assistant long-term statistics
Because the main glucose sensor contains:
state_class: measurement
Home Assistant can build statistics for the sensor. That means I don’t need to keep enormous amounts of Recorder state history purely to maintain the graph.
The architecture therefore ends up with two different but complementary history systems:
Home Assistant statistics -- long-term dashboard visualization
MySQL -- permanent individual measurement records
This is a useful separation. Home Assistant remains responsible for what it does best: displaying states and statistics. MySQL provides structured data that can later be used for reports, exports or more detailed analysis.
Step 32 – Why the sensor can appear empty after an ESPHome reinstall
During development I installed a new ESPHome firmware and suddenly the dashboard showed no current glucose value. At first that looked like the history or sensor configuration had broken.
The explanation was much simpler. The ESP32 had restarted, and these template sensors only receive their values after a synchronization with the Accu-Chek.
The solution was simply:
Wake meter
Press Sync Accu-Chek
The current value then appeared again. This is worth remembering while developing the ESPHome configuration. A freshly restarted ESP32 doesn’t automatically know the most recent measurement until it has retrieved it from the meter again.
Troubleshooting – Connected but no measurements
If the ESP32 connects but nothing useful happens afterwards, work through the BLE process one stage at a time. A useful checklist is:
1. Is the Accu-Chek awake?
2. Does ESPHome show Connection open?
3. Does service discovery complete?
4. Is BLE encryption requested?
5. Does the log show auth success?
6. Is Glucose Service 0x1808 available?
7. Is Glucose Measurement 0x2A18 subscribed?
8. Is RACP 0x2A52 available?
9. Is command 01 01 sent?
10. Are glucose records received?
11. Does RACP eventually return 06 00 01 01?
Breaking the process into these individual stages makes BLE troubleshooting much easier than treating synchronization as one giant black box.
Troubleshooting – ESP_GATTC status 133
If the log contains:
ESP_GATTC_DISCONNECT_EVT, reason 0x100
ESP_GATTC_OPEN_EVT in DISCONNECTING state (status=133)
Connection open error, status=133
first make sure the meter is awake and ready to communicate. Also check that another phone or device isn’t currently occupying the Bluetooth connection.
If the existing bond is valid, waking the meter and trying Sync again may be all that’s required.
Troubleshooting – Authentication succeeds but the passkey isn’t requested
This is normally a good sign rather than a problem. After the initial secure pairing, the ESP32 and meter can retain their Bluetooth bond.
A later connection can therefore show:
=== REQUESTING BLE ENCRYPTION ===
auth complete
auth success type = 0 mode = 5
without requesting the original pairing credential again. That means the stored bond is doing its job.
Troubleshooting – mg/dL is always a whole number
There are three different layers to check.
ESPHome — make sure the sensor uses:
accuracy_decimals: 1
Pyscript — make sure the insert uses:
float(glucose_mgdl)
and not:
int(glucose_mgdl)
MySQL — make sure the column is decimal:
glucose_mgdl DECIMAL(6,1)
If any one of these layers deliberately converts the value to an integer, the decimal precision can be lost.
Security and privacy
A glucose integration deserves a little more attention to privacy than a normal Home Assistant temperature sensor.
There are several things I would never publish together with a project like this:
- Bluetooth pairing credential
- Wi-Fi password
- ESPHome API encryption key
- Home Assistant credentials
- MySQL username and password
- Personally identifiable measurement history
- Publicly accessible database information
For Wi-Fi, use ESPHome secrets:
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
For the ESPHome API:
api:
encryption:
key: !secret esphome_api_key
Do the same for the Accu-Chek pairing credential.
Even though a BLE MAC address isn’t the same thing as a password, I would still replace the actual meter address with a placeholder when publishing the configuration. Readers don’t need the real address to reproduce the project.
A note about medical use
This project should be treated as a home automation, visualization and data-logging integration.
The ESP32 is not measuring glucose. Home Assistant is not measuring glucose. MySQL certainly isn’t measuring glucose. The measurement originates from the Accu-Chek meter. The integration simply retrieves, decodes, displays and stores the value reported by that meter.
If the value displayed by Home Assistant ever appears inconsistent with the value shown by the glucose meter itself, the meter display should be treated as the original measurement while the integration is investigated.
This project isn’t intended to provide diagnosis, treatment decisions or personalized medical guidance.
The finished architecture
After all the experimentation, the final architecture is actually quite clean:
Accu-Chek Instant
-> Bluetooth Glucose Service
-> ESP32-C6 / ESPHome
-> Home Assistant entities
-> Dashboard and statistics
-> Pyscript
-> MySQL
Each component has a clearly defined job.
ESPHome handles:
Bluetooth connection
Authentication
RACP
Glucose decoding
Latest-record selection
Home Assistant handles:
Current state
Dashboard
Statistics
Automation
Pyscript handles:
Database insert
MySQL handles:
Permanent individual measurement history
That separation should also make the system much easier to maintain later.
The final user experience
The amount of technical work behind the integration is considerably greater than the amount of work required to actually use it. That’s exactly how it should be.
The normal procedure is:
1. Take the glucose measurement normally.
2. Wake the Accu-Chek when synchronization is required.
3. Open the Blood Sugar view in Home Assistant.
4. Press Sync Accu-Chek.
5. Wait a few seconds.
6. The latest measurement appears on the dashboard.
7. Home Assistant stores the measurement in MySQL automatically.
The dashboard might then show:
Glucose -- #26
8.9 mmol/L
161.0 mg/dL
Last measurement: 19 Aug - 08:51
At the same time, the historical graph receives the new measurement and the database contains the corresponding record with its original measurement timestamp.
Conclusion
This project started with a fairly simple question: can an Accu-Chek Instant glucose meter be read directly by Home Assistant?
The answer turned out to be yes, but establishing a Bluetooth connection was only the first step. The ESP32 had to establish a secure relationship with the meter, encryption had to be enabled, the standardized Bluetooth Glucose Service had to be understood, the Record Access Control Point had to be used to retrieve stored measurements and the IEEE-11073 SFLOAT concentration values had to be decoded correctly.
Historical records introduced another interesting problem: Home Assistant shouldn’t cycle through every old value during synchronization. ESPHome therefore processes the complete download, keeps track of the newest record and only publishes that record once RACP confirms that the download has completed successfully.
After that, the Home Assistant side is refreshingly conventional. ESPHome provides normal sensor entities, a template button starts synchronization, Home Assistant statistics provide the historical graph and a small Pyscript stores each measurement permanently in MySQL.
There is no manufacturer cloud API involved in the data path and no modification to the glucose meter itself.
Most importantly, the normal measurement procedure remains unchanged. For the friend this is eventually intended for, using the system doesn’t require understanding Bluetooth services, RACP commands, SFLOAT values, ESPHome lambdas or MySQL. They only need to take the measurement normally and press Sync Accu-Chek.
Everything else happens behind the scenes.
Attached the full ESP Home yaml. Things that needs to be changed:
- manual_ip or remove the whole manual_ip block to make ita dynamic ip address
- “passkey” (which is the 6 digit pincode from the back of the Accu-Chek Instant device
- the api key for your esphome device
Attachments
-
esphome-bluetooth-proxy-office
File size: 21 KB Downloads: 11
Related Posts
May 30, 2026
Monitor QNAP Updates in Home Assistant
May 28, 2026




