LinakDeskApp: Bluetooth command failed (code: 1, error: Invalid handle)

Glad to submit the first issue in this repository. Very nice work, this application looks great.

I am getting an error of some kind when I try to connect to my IKEA Linak powered standing desk.

Here is a link to the desk. https://www.ikea.com/gb/en/p/idasen-desk-sit-stand-black-dark-grey-s89280993/

If this desk isn’t supported I’m not here to chase you down to make you support the IKEA desks but could you maybe share some insights into how you managed to create this application? I’m not a Python programmer myself so I can’t help too much with coding.

I tried asking Linak if they had some documentation but they responded by saying that was proprietary information. Do you have any advice for someone who hasn’t programmed against a bluetooth interface before on how to reverse engineer and figure out how to connect to a Linak desk?

Bluetooth command failed (code: 1, error: Invalid handle) seems to be the important bit where it fails.

linak_dpg_bt.connection.BTLEConnection:read_characteristic_by_enum [connection.py:292] Got exception from bluepy while making a request: Bluetooth command failed (code: 1, error: Invalid handle)
linak_dpg_bt.connection:wrapper [connection.py:39] bluetooth exception occurred: <class 'bluepy.btle.BTLEGattError'> Bluetooth command failed (code: 1, error: Invalid handle)
linak_dpg_bt.connection.BTLEConnection:disconnect [connection.py:118] disconnecting

Here is the full log

2020-02-19 15:19:11,935 DEBUG    MainThread __main__:main [main.py:108] Starting the application
2020-02-19 15:19:11,935 DEBUG    MainThread __main__:main [main.py:109] Logger log file: /home/hermann/Programs/LinakDeskApp/log.txt
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-root'
2020-02-19 15:19:11,984 DEBUG    MainThread linakdeskapp.gui.suspenddetector.QSuspendTimer:start [suspenddetector.py:44] starting suspension detector
2020-02-19 15:19:11,985 DEBUG    MainThread linakdeskapp.gui.main_window.MainWindow:setIconTheme [main_window.py:102] setting tray theme: <TrayIconTheme.WHITE: ('office-chair-gray.png', 'office-chair-white.png', 'office-chair-red.png')>
2020-02-19 15:19:11,989 DEBUG    MainThread linakdeskapp.gui.main_window.MainWindow:loadSettings [main_window.py:142] loading app state from /root/.config/arnet/LinakDeskApp.ini
2020-02-19 15:19:11,990 DEBUG    MainThread linakdeskapp.gui.app_settings_widget.AppSettingsWidget:_toggleAutoReconnectTime [app_settings_widget.py:182] setting auto reconnect timer to 60
2020-02-19 15:19:11,990 DEBUG    MainThread linakdeskapp.gui.main_window.MainWindow:_tryReconnectOnStartup [main_window.py:98] trying reconnect on startup
2020-02-19 15:19:11,990 DEBUG    MainThread linakdeskapp.bt_device_connector.BTDeviceConnector:_changeConnectionStatus [bt_device_connector.py:150] changing connection state to ConnectionState.CONN_IN_PROGRESS

2020-02-19 15:19:12,20  DEBUG    Connect-1 linakdeskapp.bt_device_connector.ThreadWorker:run [bt_device_connector.py:287] Worker start
2020-02-19 15:19:12,23  DEBUG    Connect-1 linak_dpg_bt.connection.BTLEConnection:__init__ [connection.py:62] Constructed BTLEConnection object: <linak_dpg_bt.connection.BTLEConnection object at 0x7fb9c3045a60>
2020-02-19 15:19:12,23  DEBUG    Connect-1 linak_dpg_bt.linak_device.LinakDesk:__init__ [linak_device.py:109] Constructed LinakDesk object: <linak_dpg_bt.linak_device.LinakDesk object at 0x7fb9c30457c0>
2020-02-19 15:19:12,23  DEBUG    Connect-1 linak_dpg_bt.linak_device.LinakDesk:_connect [linak_device.py:347] Initializing the device
2020-02-19 15:19:12,23  DEBUG    Connect-1 linak_dpg_bt.connection.BTLEConnection:connect [connection.py:95] Trying to connect to 
2020-02-19 15:19:12,23  ERROR    Connect-1 linak_dpg_bt.linak_device.LinakDesk:initialize [linak_device.py:343] Initialization failed: <class 'ValueError'> Expected MAC address, got ''
2020-02-19 15:19:12,23  DEBUG    Connect-1 linakdeskapp.bt_device_connector.BTDeviceConnector:_initializeDevice [bt_device_connector.py:119] Could not connect to to device: <linak_dpg_bt.linak_device.LinakDesk object at 0x7fb9c30457c0>
2020-02-19 15:19:12,23  DEBUG    Connect-1 linakdeskapp.bt_device_connector.BTDeviceConnector:_changeConnectionStatus [bt_device_connector.py:150] changing connection state to ConnectionState.DISCONNECTED
2020-02-19 15:19:12,24  DEBUG    Connect-1 linakdeskapp.bt_device_connector.ThreadWorker:run [bt_device_connector.py:289] Worker complete

// Scan for desk

2020-02-19 15:19:16,45  DEBUG    MainThread linakdeskapp.bt_device_connector.BTDeviceConnector:scanDevices [bt_device_connector.py:74] Scanning bluetooth devices
2020-02-19 15:19:18,277 DEBUG    MainThread linak_dpg_bt.linak_device.LinakDesk:__del__ [linak_device.py:112] Deleting LinakDesk object: <linak_dpg_bt.linak_device.LinakDesk object at 0x7fb9c30457c0>
2020-02-19 15:19:18,277 DEBUG    MainThread linak_dpg_bt.connection.BTLEConnection:__del__ [connection.py:87] Deleting BTLEConnection object: <linak_dpg_bt.connection.BTLEConnection object at 0x7fb9c3045a60>
2020-02-19 15:19:18,277 DEBUG    MainThread linak_dpg_bt.connection.BTLEConnection:disconnect [connection.py:118] disconnecting
2020-02-19 15:19:26,298 DEBUG    MainThread linakdeskapp.bt_device_connector.BTDeviceConnector:scanDevices [bt_device_connector.py:90] Scanning finished
2020-02-19 15:19:26,300 DEBUG    MainThread linakdeskapp.gui.suspenddetector.QSuspendTimer:checkResumed [suspenddetector.py:62] resumed from suspend / hibernation after 10.512005[s]
2020-02-19 15:19:26,316 DEBUG    MainThread linakdeskapp.bt_device_connector.BTDeviceConnector:scanDevices [bt_device_connector.py:74] Scanning bluetooth devices
2020-02-19 15:19:36,458 DEBUG    MainThread linakdeskapp.bt_device_connector.BTDeviceConnector:scanDevices [bt_device_connector.py:90] Scanning finished
2020-02-19 15:19:36,458 DEBUG    MainThread linakdeskapp.gui.suspenddetector.QSuspendTimer:checkResumed [suspenddetector.py:62] resumed from suspend / hibernation after 10.158263[s]
2020-02-19 15:19:40,27  DEBUG    MainThread linakdeskapp.bt_device_connector.BTDeviceConnector:scanDevices [bt_device_connector.py:74] Scanning bluetooth devices
2020-02-19 15:19:50,281 DEBUG    MainThread linakdeskapp.bt_device_connector.BTDeviceConnector:scanDevices [bt_device_connector.py:90] Scanning finished
2020-02-19 15:19:50,283 DEBUG    MainThread linakdeskapp.gui.suspenddetector.QSuspendTimer:checkResumed [suspenddetector.py:62] resumed from suspend / hibernation after 10.97479[s]
2020-02-19 15:19:54,537 DEBUG    MainThread linakdeskapp.bt_device_connector.BTDeviceConnector:_changeConnectionStatus [bt_device_connector.py:150] changing connection state to ConnectionState.CONN_IN_PROGRESS

// Connect to my IKEA desk, failed connection

2020-02-19 15:19:54,539 DEBUG    Connect-2 linakdeskapp.bt_device_connector.ThreadWorker:run [bt_device_connector.py:287] Worker start
2020-02-19 15:19:54,539 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:__init__ [connection.py:62] Constructed BTLEConnection object: <linak_dpg_bt.connection.BTLEConnection object at 0x7fb9c3045ca0>
2020-02-19 15:19:54,540 DEBUG    Connect-2 linak_dpg_bt.linak_device.LinakDesk:__init__ [linak_device.py:109] Constructed LinakDesk object: <linak_dpg_bt.linak_device.LinakDesk object at 0x7fb9c3045d00>
2020-02-19 15:19:54,540 DEBUG    Connect-2 linak_dpg_bt.linak_device.LinakDesk:_connect [linak_device.py:347] Initializing the device
2020-02-19 15:19:54,540 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:connect [connection.py:95] Trying to connect to f1:0c:41:3a:4b:20
2020-02-19 15:19:54,786 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:connect [connection.py:112] Connected to f1:0c:41:3a:4b:20
2020-02-19 15:19:55,322 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:231] Subscribing to Characteristic.HEIGHT_SPEED[99FA0021-338A-1024-8A49-009C0215F78A, 0x1d]
2020-02-19 15:19:55,323 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:236] Writing value <class 'bytes'>:0x01 0x00 to Characteristic.HEIGHT_SPEED[99FA0021-338A-1024-8A49-009C0215F78A, 0x1d] w_resp=False
2020-02-19 15:19:55,324 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:231] Subscribing to Characteristic.TWO[99FA0022-338A-1024-8A49-009C0215F78A, 0x20]
2020-02-19 15:19:55,324 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:236] Writing value <class 'bytes'>:0x01 0x00 to Characteristic.TWO[99FA0022-338A-1024-8A49-009C0215F78A, 0x20] w_resp=False
2020-02-19 15:19:55,325 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:231] Subscribing to Characteristic.THREE[99FA0023-338A-1024-8A49-009C0215F78A, 0x23]
2020-02-19 15:19:55,325 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:236] Writing value <class 'bytes'>:0x01 0x00 to Characteristic.THREE[99FA0023-338A-1024-8A49-009C0215F78A, 0x23] w_resp=False
2020-02-19 15:19:55,326 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:231] Subscribing to Characteristic.FOUR[99FA0024-338A-1024-8A49-009C0215F78A, 0x26]
2020-02-19 15:19:55,326 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:236] Writing value <class 'bytes'>:0x01 0x00 to Characteristic.FOUR[99FA0024-338A-1024-8A49-009C0215F78A, 0x26] w_resp=False
2020-02-19 15:19:55,326 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:231] Subscribing to Characteristic.FIVE[99FA0025-338A-1024-8A49-009C0215F78A, 0x29]
2020-02-19 15:19:55,327 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:236] Writing value <class 'bytes'>:0x01 0x00 to Characteristic.FIVE[99FA0025-338A-1024-8A49-009C0215F78A, 0x29] w_resp=False
2020-02-19 15:19:55,327 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:231] Subscribing to Characteristic.SIX[99FA0026-338A-1024-8A49-009C0215F78A, 0x2c]
2020-02-19 15:19:55,327 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:236] Writing value <class 'bytes'>:0x01 0x00 to Characteristic.SIX[99FA0026-338A-1024-8A49-009C0215F78A, 0x2c] w_resp=False
2020-02-19 15:19:55,328 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:231] Subscribing to Characteristic.SEVEN[99FA0027-338A-1024-8A49-009C0215F78A, 0x2f]
2020-02-19 15:19:55,328 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:236] Writing value <class 'bytes'>:0x01 0x00 to Characteristic.SEVEN[99FA0027-338A-1024-8A49-009C0215F78A, 0x2f] w_resp=False
2020-02-19 15:19:55,329 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:231] Subscribing to Characteristic.EIGHT[99FA0028-338A-1024-8A49-009C0215F78A, 0x32]
2020-02-19 15:19:55,329 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:subscribe_to_notification_enum [connection.py:236] Writing value <class 'bytes'>:0x01 0x00 to Characteristic.EIGHT[99FA0028-338A-1024-8A49-009C0215F78A, 0x32] w_resp=False
2020-02-19 15:19:55,330 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:read_characteristic_by_enum [connection.py:284] Reading char: Characteristic.MASK[99FA0029-338A-1024-8A49-009C0215F78A, 0x35]
2020-02-19 15:19:55,516 ERROR    Connect-2 linak_dpg_bt.connection.BTLEConnection:read_characteristic_by_enum [connection.py:292] Got exception from bluepy while making a request: Bluetooth command failed (code: 1, error: Invalid handle)
2020-02-19 15:19:55,517 ERROR    Connect-2 linak_dpg_bt.connection:wrapper [connection.py:39] bluetooth exception occurred: <class 'bluepy.btle.BTLEGattError'> Bluetooth command failed (code: 1, error: Invalid handle)
2020-02-19 15:19:55,517 DEBUG    Connect-2 linak_dpg_bt.connection.BTLEConnection:disconnect [connection.py:118] disconnecting
2020-02-19 15:19:55,521 ERROR    Connect-2 linak_dpg_bt.linak_device.LinakDesk:initialize [linak_device.py:343] Initialization failed: <class 'bluepy.btle.BTLEGattError'> Bluetooth command failed (code: 1, error: Invalid handle)
2020-02-19 15:19:55,522 DEBUG    Connect-2 linakdeskapp.bt_device_connector.BTDeviceConnector:_initializeDevice [bt_device_connector.py:119] Could not connect to to device: <linak_dpg_bt.linak_device.LinakDesk object at 0x7fb9c3045d00>
2020-02-19 15:19:55,522 DEBUG    Connect-2 linakdeskapp.bt_device_connector.BTDeviceConnector:_changeConnectionStatus [bt_device_connector.py:150] changing connection state to ConnectionState.DISCONNECTED
2020-02-19 15:19:55,522 DEBUG    Connect-2 linakdeskapp.bt_device_connector.ThreadWorker:run [bt_device_connector.py:289] Worker complete

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 30 (13 by maintainers)

Most upvoted comments

@william-reed I made a script from scratch (using what I learned from this project) as I did not need a gui so if that’s good enough for you: https://github.com/rhyst/idasen-controller

I got my IKEA IDÅSEN yesterday. Here’s a TL;DR of what I tried (scroll below for some working stuff):

“Device discovery” via the GUI is broken. First of all, there’s this exception:

ERROR    Connect-1 linak_dpg_bt.linak_device.LinakDesk:initialize [linak_device.py:343] Initialization failed: <class 'ValueError'> Expected MAC address, got ''
Traceback (most recent call last):
  File "/home/jkt/work/prog/LinakDeskApp/lib/linak_bt_desk/linak_dpg_bt/linak_device.py", line 340, in initialize
    self._connect()
  File "/home/jkt/work/prog/LinakDeskApp/lib/linak_bt_desk/linak_dpg_bt/linak_device.py", line 348, in _connect
    with self._conn as conn:
  File "/home/jkt/work/prog/LinakDeskApp/lib/linak_bt_desk/linak_dpg_bt/synchronized.py", line 80, in decorator
    return func(self, *args, **kws)
  File "/home/jkt/work/prog/LinakDeskApp/lib/linak_bt_desk/linak_dpg_bt/connection.py", line 75, in __enter__
    self.connect()
  File "/home/jkt/work/prog/LinakDeskApp/lib/linak_bt_desk/linak_dpg_bt/synchronized.py", line 80, in decorator
    return func(self, *args, **kws)
  File "/home/jkt/work/prog/LinakDeskApp/lib/linak_bt_desk/linak_dpg_bt/connection.py", line 37, in wrapper
    return func(*args)
  File "/home/jkt/work/prog/LinakDeskApp/lib/linak_bt_desk/linak_dpg_bt/connection.py", line 101, in connect
    self._conn.connect(self._mac, addrType='random')
  File "/home/jkt/work/prog/LinakDeskApp/_py3.6/lib64/python3.6/site-packages/bluepy/btle.py", line 445, in connect
    self._connect(addr, addrType, iface)
  File "/home/jkt/work/prog/LinakDeskApp/_py3.6/lib64/python3.6/site-packages/bluepy/btle.py", line 423, in _connect
    raise ValueError("Expected MAC address, got %s" % repr(addr))
ValueError: Expected MAC address, got ''

I have no clue what that means.

But the app appears to work well enough up to triggering a device scan, which can find my desk. When I try to connect, though, it fails on these invalid Unicode data for the model. After ignoring that, it fails on DPGCommandType.USER_ID, also on DPGCommandType.GET_SETUP and DPGCommandType.PRODUCT_INFO, and when reading capabilities. It seems that the only useful info that can be read is Characteristic.MASK (on a second attempt only, i.e. via UUID 99FA0029-338A-1024-8A49-009C0215F78A) which says Mask[ActuatorType.Desk 0x1]. One can also read Characteristic.DEVICE_NAME[00002A00-0000-1000-8000-00805F9B34FB, 0x3] which corresponds to the BT device name.

I gave up on generic code and went directly to direct control at that point, and have some good news:

Direct manual control via BT works

  • Notifications on GATT 99fa0021-338a-1024-8a49-009c0215f78a work, it looks like a tuple of (position, speed) where both are little-endian int16_t. That’s good!
  • Absolute positioning via 99FA0031-338A-1024-8A49-009C0215F78A doesn’t appear to work out-of-the-box. I don’t know if I have to enable these somehow (got to RTFS), or if the HW just doesn’t support them.
  • I can ask the desk to go “one step up” or “one step down” via 99FA0002-338A-1024-8A49-009C0215F78A. These feel like roughly one-second steps, and I can also cancel this movement. This probably means that a control loop can be built.

It’s already too late now 😃, so I’ll see later if I can cleanly implement some discovery bypass, or if I’ll just implement some trivial positioning script with no GUI for me.

Thanks for your awesome work on reverse engineering these controllers.

It looks like handler numbers varies from device to device. I made small improvement in linak_bt_desk to use UUID when reading by handler fails. Try if it works.

@jktjkt That’s great news! Very impressive that you made this progress after one day with the desk 😄 I’d be very grateful if you could post even what you have so far as I suspect I’ll want to script it anyway.

I also managed to “subscribe” to the notifications directly in bluetoothctlbut had no idea what the hex that came out meant.

@HermannBjorgvin Did you get anywhere with this?

@anetczuk I have the same desk, and am in the same position as @HermannBjorgvin. I do also have the decompiled source for the app used to control this desk (“Desk Control”), so I can map some of the UUIDs to helpful sounding consts like “MASK” and “COMMAND”.

I found that I also had to wrap the model number in a try/catch but then I get the same error.

2020-05-26 20:54:08,574 DEBUG    Connect-1 linak_dpg_bt.connection.BTLEConnection:_send_command_single [connection.py:225] Sending DPGCommand[USER_ID, None]: 0x7F 0x86 0x00 to Characteristic.DPG[99FA0011-338A-1024-8A49-009C0215F78A, 0x14] w_resp=True
2020-05-26 20:54:08,672 ERROR    Connect-1 linak_dpg_bt.connection:wrapper [connection.py:39] bluetooth exception occurred: <class 'bluepy.btle.BTLEGattError'> Bluetooth command failed (code: 3, error: Attribute can't be written)

In the decompiled app source this UUID corresponds to the “DPG” const, and the GATTBrowser app lists the following poroperties for that characteristic:

Read Write Without Response Write Notify

Which seems to suggest it is writeable.

I also had a look at the MITM app you wrote, however I havent got it working yet as I don’t know which dbus library it wants (not dbus-python apparently?)

Sorry for the info dump, but would be very keen to help get this working for my desk if you have the time to point me in the right direction.

One thing You can do in this situation is or use my MITM tool I mentioned (to analyze it dynamically) earlier or try to decompile Android APK application and analyze source code statically.

It returns content from MANUFACTURER characteristic, but it looks like random array of bytes. Expected value is string. I handled the case with exception catch.

Thanks for the issue.

IKEA website does not provide technical details about hardware You have. First of all You can check model number of Your device. It should be written on label of controller. Logs says that Bluetooth was able to connect to the device, but problem is that there is no required attribute. Application has problem reading following BT attribute: 99FA0029-338A-1024-8A49-009C0215F78A. You can install “GATTBrowser” or similar application on Your Android device (phone or tablet) investigate device’s attributes. In repo there is also script listing all characteristics of BT device. You will find it in test/gatttool/020_list_services.ssh. Other scripts might be helpful too.

For purpose of this application I developed a tool (https://github.com/anetczuk/BluetoothGattMitm). You can use it to reverse engineer protocol between Your device and Linak Android application.