Skip to content

Commit

Permalink
Merge pull request #3 from mishnz/main
Browse files Browse the repository at this point in the history
Add support for DewertOkin BLE Controller
  • Loading branch information
dennyreiter authored Jan 20, 2022
2 parents 576ed6d + 22ba204 commit eb98875
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 2 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# mqtt-bed

MQTT control for Serta adjustable beds with Bluetooth, like the [Serta Motion Perfect III](https://www.serta.com/sites/ssb/serta.com/uploads/2016/adjustable-foundations/MotionPerfectIII_Manual_V004_04142016.pdf) And 'Glide' with the jiecang ble controller (Dream Motion app)
MQTT control for Serta adjustable beds with Bluetooth, like the [Serta Motion Perfect III](https://www.serta.com/sites/ssb/serta.com/uploads/2016/adjustable-foundations/MotionPerfectIII_Manual_V004_04142016.pdf), 'Glide' with the jiecang ble controller (Dream Motion app) and 'A H Beard' with the DerwentOkin ble controller ("Comfort Enhancement 2" aka "Comfort Plus" app).

Based upon code from https://github.com/danisla/iot-bed

## Requirements
See requirements.txt file. It now uses asyncio-mqtt and pygatt instead of a subprocess using gatttool.
The DerwentOkin modules uses bluepy instead of the deprecated pygatt/gatttool.
```
pip install -r requirements.txt
```
Expand Down
2 changes: 1 addition & 1 deletion config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
MQTT_SERVER_PORT = 1883
MQTT_TOPIC = "bed"

# Bed controller type, supported values are "serta" and "jiecang"
# Bed controller type, supported values are "serta", "jiecang" and "dewertokin"
BED_TYPE = "serta"

# Don't worry about these unless you want to
Expand Down
141 changes: 141 additions & 0 deletions controllers/dewertokin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#----------------------------------------------------------------------------
# Created By : https://github.com/mishnz
# Created Date: 14/01/2022
# version ='1.0'
# ---------------------------------------------------------------------------
""" DewertOkin HE150 controller module for mqtt-bed
https://github.com/karl0ss/mqtt-bed
I recently purchased a "Napp" https://napp.co.nz/ bed and mattress.
On arrival, the base is an "A.H. Beard" https://ahbeard.com/ base.
Digging into the internals the Bluetooth chips identify themselves as "Okin"
branded.
The controller unit as a whole is branded as a DewertOkin HE150
https://dewertokin.hu/termek/he-150/
The DewertOkin Android application for this is "Comfort Enhancement 2"
aka "Comfort Plus"
https://play.google.com/store/apps/details?id=com.dewertokin.comfortplus
Using this application I intercepted the Bluetooth codes.
I moved from the depreciated pygatt to bluepy due to connectivity issues.
The Bluetooth connection string for this bed uses "random" instead of
"public" like the other beds.
This module ended up being bigger than expected as the HE150 disconnects on a
lack of connectivity and on other unknown conditions.
The additional code manages a keepalive/heartbeat thread.
This module is more verbose than the others to aid in debugging.
Note: This module will work with some other "Okin"/"DewertOkin" models.
"""
# ---------------------------------------------------------------------------
# Imports
# ---------------------------------------------------------------------------
import bluepy.btle as ble
import time
import threading

class dewertokinBLEController:
def __init__(self, addr):
self.charWriteInProgress = False
self.addr = addr
self.commands = {
"Flat Preset": "040210000000",
"ZeroG Preset": "040200004000",
"TV Position": "040200003000",
"Quiet Sleep": "040200008000",
"Memory 1": "040200001000",
"Memory 2": "040200002000",
"Underlight": "040200020000",
"Lift Head": "040200000001",
"Lower Head": "040200000002",
"Lift Foot": "040200000004",
"Lower Foot": "040200000008",
# Note: Wave cycles "On High", "On Medium", "On Low", "Off"
"Wave Massage Cycle": "040280000000",
# Note: Head and Foot cycles "On Low, "On Medium", "On High", "Off"
"Head Massage Cycle": "040200000800",
"Foot Massage Cycle": "040200400000",
"Massage Off": "040202000000",
"Keepalive NOOP": "040200000000",
}
# Initialise the adapter and connect to the bed before we start waiting for messages.
self.connectBed(ble)
# Start the background polling/keepalive/heartbeat function.
thread = threading.Thread(target=self.bluetoothPoller, args=())
thread.daemon = True
thread.start()

# There seem to be a lot of conditions that cause the bed to disconnect Bluetooth.
# Here we use the value of 040200000000, which seems to be a noop.
# This lets us poll the bed, detect a disconnection and reconnect before the user notices.
def bluetoothPoller(self):
while True:
if self.charWriteInProgress is False:
try:
cmd = self.commands.get("Keepalive NOOP", None)
self.device.writeCharacteristic(0x0013, bytes.fromhex(cmd), withResponse=True)
print("Keepalive success!")
except:
print("Keepalive failed! (1/2)")
try:
# We perform a second keepalive check 0.5 seconds later before reconnecting.
time.sleep(0.5)
cmd = self.commands.get("Keepalive NOOP", None)
self.device.writeCharacteristic(0x0013, bytes.fromhex(cmd), withResponse=True)
print("Keepalive success!")
except:
# If both keepalives failed, we reconnect.
print("Keepalive failed! (2/2)")
self.connectBed(ble)
else:
# To minimise any chance of contention, we don't heartbeat if a charWrite is in progress.
print("charWrite in progress, heartbeat skipped.")
time.sleep(10)

# Separate out the bed connection to an infinite loop that can be called on init (or a communications failure).
def connectBed(self, ble):
while True:
try:
print("Attempting to connect to bed.")
self.device = ble.Peripheral(deviceAddr=self.addr, addrType='random')
print("Connected to bed.")
return
except:
pass
print("Error connecting to bed, retrying in one second.")
time.sleep(1)

# Separate out the command handling.
def sendCommand(self,name):
cmd = self.commands.get(name, None)
if cmd is None:
# print, but otherwise ignore Unknown Commands.
print("Unknown Command, ignoring.")
return
self.charWriteInProgress = True
try:
self.charWrite(cmd)
except:
print("Error sending command, attempting reconnect.")
start = time.time()
self.connectBed(ble)
end = time.time()
if ((end - start) < 5):
try:
self.charWrite(self, cmd)
except:
print("Command failed to transmit despite second attempt, dropping command.")
else:
print("Bluetooth reconnect took more than five seconds, dropping command.")
self.charWriteInProgress = False

# Separate charWrite function.
def charWrite(self, cmd):
print("Attempting to transmit command.")
self.device.writeCharacteristic(0x0013, bytes.fromhex(cmd), withResponse=True)
print("Command sent successfully.")
return
3 changes: 3 additions & 0 deletions mqtt-bed.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from contextlib import AsyncExitStack, asynccontextmanager
from asyncio_mqtt import Client, MqttError

from controllers.dewertokin import dewertokinBLEController
from controllers.jiecang import jiecangBLEController
from controllers.serta import sertaBLEController

Expand Down Expand Up @@ -103,6 +104,8 @@ async def main():
ble = sertaBLEController(ble_address)
elif BED_TYPE == "jiecang":
ble = jiecangBLEController(ble_address)
elif BED_TYPE == "dewertokin":
ble = dewertokinBLEController(ble_address)
else:
raise Exception("Unrecognised bed type: " + str(BED_TYPE))

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pygatt==4.0.5
pyserial==3.5
pexpect==4.8.0
ptyprocess==0.7.0
bluepy==1.3.0

0 comments on commit eb98875

Please sign in to comment.