Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DPG1C Support #32

Closed
linusbierhoff opened this issue Jan 16, 2022 · 72 comments
Closed

DPG1C Support #32

linusbierhoff opened this issue Jan 16, 2022 · 72 comments
Labels
enhancement New feature or request need hardware

Comments

@linusbierhoff
Copy link

Hi! I am not able to move my desk. When I try to run e.g. idasen-controller --sit my raspberry pi connects and gets the right height but nothing happens:

Connected D7:78:43:EE:CF:34
Height: 885mm
Timed out while waiting for desk
Final height: 885mm (Target: 683mm)
Disconnected

Is there anything I need to be aware of when using the script on a raspberry pi or with a linak desk?

@linusbierhoff linusbierhoff changed the title Cannot move Desk with DPG1C Cannot move desk with DPG1C Jan 16, 2022
@rhyst
Copy link
Owner

rhyst commented Jan 17, 2022

I only have a Idasen desk and I think the other Linak controllers use different commands so the scope of this tool was just for the Linak desk.

If you were able to find out what commands work on the DPG1C then it's possible that I could include them in the script.

@rhyst
Copy link
Owner

rhyst commented Jan 17, 2022

If you want to do this you can run this script using the Bleak python library https://github.com/hbldh/bleak/blob/develop/examples/service_explorer.py

If you were able to post the output here then we could try to work out what the correct characteristics or commands are for the DPG1C

@vniehues
Copy link

@linusbierhoff another option (if you have a Mac) would be Bluetillity.
You can just select the desk and browse every service and characteristic to provide them here.

@rhyst
Copy link
Owner

rhyst commented Jan 21, 2022

I also remembered that someone else apparently got the DPG1C working and claimed it used the same commands: #3

@dmitrym0
Copy link

I'm having trouble wit this as well. After reading #3, I reset the controller per the manual and re-paired it. But it only seems to be reading the status and not settings it:

~/w  ❯❯❯ idasen-controller --mac-address 4520FE33-3754-4E70-BF61-55D342263FE3 --move-to 910               main ⬆ ◼
Connected 4520FE33-3754-4E70-BF61-55D342263FE3
Height:  902mm
Timed out while waiting for desk
Final height:  902mm (Target:  910mm)
Disconnected

@dmitrym0
Copy link

Here's the output from the serviceexplorer script linked above: https://gist.github.com/dmitrym0/7394da30f2e543b5d4ac825a604d989b

@kabakakao
Copy link

I can confirm, that getting the status is working, but not setting a position

@rhyst
Copy link
Owner

rhyst commented Feb 7, 2022

@dmitrym0 thank you for getting that.

It's odd, it looks like it has all the right characteristics. The only thing I can think of is that for some reason that notifications on the height characteristic aren't working on the DPG1C.

If you are comfortable running this as a python script then some things that would be useful to try are:

  • There's a bunch of characteristics with : Unknown (read,notify) after them. Try replacing the value of UUID_HEIGHT with the UUID of those characteristics. Then run the script and see if it still reports the current height, and then if it moves.
  • You could try adding some print statements in the move_to and _move_to functions. It would be useful to know if it actually sends the initial move_up or move_down command and if the _move_to function is ever called.

I am currently away so limited to what I can try. One thing I will add when I am back at my desk is some better logging so you don't have to fiddle with the script if you don't want to.

@dmitrym0
Copy link

dmitrym0 commented Feb 7, 2022

I tried running locally with the following values:

#UUID_HEIGHT = '99fa0003-338a-1024-8a49-009c0215f78a'
#UUID_HEIGHT = '99fa0022-338a-1024-8a49-009c0215f78a'
#UUID_HEIGHT = '99fa0023-338a-1024-8a49-009c0215f78a'
#UUID_HEIGHT = '99fa0024-338a-1024-8a49-009c0215f78a'
#UUID_HEIGHT = '99fa0025-338a-1024-8a49-009c0215f78a'
#UUID_HEIGHT = '99fa0026-338a-1024-8a49-009c0215f78a'
#UUID_HEIGHT = '99fa0027-338a-1024-8a49-009c0215f78a'
#UUID_HEIGHT = '99fa0028-338a-1024-8a49-009c0215f78a'

No change.

I also instrumented the code with some prints; it looks like _move_to is not being called at all:

Connected 4520FE33-3754-4E70-BF61-55D342263FE3
Height:  855mm
move_to
move_to after height
about to subscribe
got height
Moving up
move_up
Timed out while waiting for desk
Final height:  855mm (Target:  900mm)
Disconnected

So move_up from the log above is this function. Once the desk start moving, we expect _move_to to get called?

If move_up doesn't start the movement, then I guess it makes sense that _move_to doesn't get called.

@rhyst
Copy link
Owner

rhyst commented Feb 8, 2022

If you run the script with --monitor and then move the desk using the physical switch, do you see the height being updated?

@dmitrym0
Copy link

dmitrym0 commented Feb 8, 2022

Yup:

~/w/g/idasen-controller ❯❯❯ python3 idasen_controller/main.py --mac-address 4520FE33-3754-4E70-BF61-55D342263FE3 --monitor                                                                                                                                     ✘ 130 master ✱
Connected 4520FE33-3754-4E70-BF61-55D342263FE3
Height: 1281mm
Height: 1281mm Speed: -4mm/s
Height: 1281mm Speed: -7mm/s
Height: 1280mm Speed: -1mm/s

@rhyst
Copy link
Owner

rhyst commented Feb 8, 2022

Aha, that must means that the movement commands are wrong then.

Apologies for the trial and error style approach but could you try replacing UUID_COMMAND with the different characteristics that you found earlier? I would imagine it would be one of the ones labelled: write-without-response,write

If that doesn't work then it might be that we have the right characteristic but we are sending the wrong data. In which case I'll probably have to get the android app and decompile it again to have a look.

@kabakakao
Copy link

I had a small success. I opened the iOS App and allowed the app to allow movement in both directions. After that the movement works but just for a few steps

@rhyst
Copy link
Owner

rhyst commented Feb 8, 2022

Ah that is interesting because in the decompiled app there is some code for sending a wakeup command. I wonder if that explains why this works intermittently.

I think the command could be defined as:

COMMAND_WAKEUP = bytearray(struct.pack("<H", 254))

And then send it just before we send the first movement command:

    # Listen for changes to desk height and send first move command (if we are 
    # not already at the target height).
    if not has_reached_target(initial_height, target):
        await subscribe(client, UUID_HEIGHT, _move_to)
        await client.write_gatt_char(UUID_COMMAND, COMMAND_WAKEUP )
        await client.write_gatt_char(UUID_COMMAND, COMMAND_STOP)
        if direction == "UP":
            asyncio.create_task(move_up(client))

(The app also sends a stop command immediately after wakeup so 🤷‍♂️ )

@voruti
Copy link
Contributor

voruti commented Feb 9, 2022

I got the same/a similar problem. The program is able to read all the values from the desk, but not able to trigger a movement.
I'm using the DPG1M on an Inwerk Masterlift 2.
Interestingly, everything worked a few days ago. Unfortunately, I really do not know what I have changed since then 😞.

I added a print(...) before all client.write_gatt_char(...) and it looks like they get called when they should.
The program is sending (for example) a COMMAND_UP and then waiting, while nothing happens at all. Now when I manually raise the table, idasen-controller detects this and starts to count (global count). Once it reaches 6, it sends another COMMAND_UP - and the desk stops moving 😆:

Log (ignore my strange log output in between if necessary)
> python3 idasen_controller/main.py --config config.yaml --stand
Connected 12:34:56:78:9A:BC
Height:  749mm
after subscribe
after wakeup
creating up:
created up
trying wait move done:
raw send up: 99fa0002-338a-1024-8a49-009c0215f78a bytearray(b'G\x00')

Nothing happens; I move the desk up manually

new count: 1
Height:  749mm Target: 1156mm Speed:  6mm/s
new count: 2
Height:  749mm Target: 1156mm Speed:  8mm/s
new count: 3
Height:  750mm Target: 1156mm Speed: 11mm/s
new count: 4
Height:  750mm Target: 1156mm Speed: 14mm/s
new count: 5
Height:  750mm Target: 1156mm Speed: 16mm/s
new count: 6
Height:  751mm Target: 1156mm Speed: 20mm/s
raw send up: 99fa0002-338a-1024-8a49-009c0215f78a bytearray(b'G\x00')
new count: 1
Height:  752mm Target: 1156mm Speed: 23mm/s
new count: 2
Height:  752mm Target: 1156mm Speed: 25mm/s
new count: 3
Height:  754mm Target: 1156mm Speed: 28mm/s
new count: 4
Height:  754mm Target: 1156mm Speed: 21mm/s
new count: 5
Height:  755mm Target: 1156mm Speed: 13mm/s
new count: 6
Height:  755mm Target: 1156mm Speed:  9mm/s
raw send up: 99fa0002-338a-1024-8a49-009c0215f78a bytearray(b'G\x00')
new count: 1
Height:  755mm Target: 1156mm Speed:  5mm/s
new count: 2
Height:  755mm Target: 1156mm Speed:  0mm/s
raw send stop
waited for done
raw send reference_stop
Final height:  755mm (Target: 1156mm)
raw send stop
raw send reference_stop
Disconnected

(If you look closely at the speed, you can see that the up command stops the desk.)


I also tried this

sending a wakeup command

with and without the following stop command, but it seems to have no effect. (To make it clear: My desk does not move for a few steps like @kabakakao described, but stays completely still. I tried the wakeup command anyway.)


My `service_explorer.py` for completeness
INFO:__main__:Connected: True
INFO:__main__:[Service] 00001801-0000-1000-8000-00805f9b34fb (Handle: 8): Generic Attribute Profile
INFO:__main__:  [Characteristic] 00002a05-0000-1000-8000-00805f9b34fb (Handle: 9): Service Changed (indicate), Value: None
INFO:__main__:          [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 11): Client Characteristic Configuration) | Value: b'\x02\x00'
INFO:__main__:[Service] 99fa0001-338a-1024-8a49-009c0215f78a (Handle: 12): Unknown
INFO:__main__:  [Characteristic] 99fa0002-338a-1024-8a49-009c0215f78a (Handle: 13): Unknown (write-without-response,write), Value: None
INFO:__main__:  [Characteristic] 99fa0003-338a-1024-8a49-009c0215f78a (Handle: 15): Unknown (read,notify), Value: b''
INFO:__main__:          [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 17): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__:[Service] 99fa0010-338a-1024-8a49-009c0215f78a (Handle: 18): Unknown
INFO:__main__:  [Characteristic] 99fa0011-338a-1024-8a49-009c0215f78a (Handle: 19): Unknown (read,write-without-response,write,notify), Value: b'\x01\x02\xa3\x0c'
INFO:__main__:          [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 21): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__:[Service] 0000180a-0000-1000-8000-00805f9b34fb (Handle: 22): Device Information
INFO:__main__:  [Characteristic] 00002a29-0000-1000-8000-00805f9b34fb (Handle: 23): Manufacturer Name String (read), Value: b'LINAK A/S'
INFO:__main__:  [Characteristic] 00002a24-0000-1000-8000-00805f9b34fb (Handle: 25): Model Number String (read), Value: b'DPG'
INFO:__main__:[Service] 99fa0020-338a-1024-8a49-009c0215f78a (Handle: 27): Unknown
INFO:__main__:  [Characteristic] 99fa0021-338a-1024-8a49-009c0215f78a (Handle: 28): Unknown (read,notify), Value: b'\xc6\x04\x00\x00'
INFO:__main__:          [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 30): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__:  [Characteristic] 99fa0022-338a-1024-8a49-009c0215f78a (Handle: 31): Unknown (read,notify), Value: b'\x00\x00\x01\x00'
INFO:__main__:          [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 33): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__:  [Characteristic] 99fa0023-338a-1024-8a49-009c0215f78a (Handle: 34): Unknown (read,notify), Value: b'\x00\x00\x01\x00'
INFO:__main__:          [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 36): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__:  [Characteristic] 99fa0024-338a-1024-8a49-009c0215f78a (Handle: 37): Unknown (read,notify), Value: b'\x00\x00\x01\x00'
INFO:__main__:          [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 39): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__:  [Characteristic] 99fa0025-338a-1024-8a49-009c0215f78a (Handle: 40): Unknown (read,notify), Value: b'\x00\x00\x01\x00'
INFO:__main__:          [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 42): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__:  [Characteristic] 99fa0026-338a-1024-8a49-009c0215f78a (Handle: 43): Unknown (read,notify), Value: b'\x00\x00\x01\x00'
INFO:__main__:          [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 45): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__:  [Characteristic] 99fa0027-338a-1024-8a49-009c0215f78a (Handle: 46): Unknown (read,notify), Value: b'\x00\x00\x01\x00'
INFO:__main__:          [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 48): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__:  [Characteristic] 99fa0028-338a-1024-8a49-009c0215f78a (Handle: 49): Unknown (read,notify), Value: b'\x00\x00\x01\x00'
INFO:__main__:          [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 51): Client Characteristic Configuration) | Value: b'\x00\x00'
INFO:__main__:  [Characteristic] 99fa0029-338a-1024-8a49-009c0215f78a (Handle: 52): Unknown (read), Value: b'\x01'
INFO:__main__:  [Characteristic] 99fa002a-338a-1024-8a49-009c0215f78a (Handle: 54): Unknown (read), Value: b'\x01'
INFO:__main__:[Service] 99fa0030-338a-1024-8a49-009c0215f78a (Handle: 56): Unknown
INFO:__main__:  [Characteristic] 99fa0031-338a-1024-8a49-009c0215f78a (Handle: 57): Unknown (write-without-response,write), Value: None
INFO:__main__:  [Characteristic] 99fa0032-338a-1024-8a49-009c0215f78a (Handle: 59): Unknown (write-without-response,write), Value: None
INFO:__main__:  [Characteristic] 99fa0033-338a-1024-8a49-009c0215f78a (Handle: 61): Unknown (write-without-response,write), Value: None
INFO:__main__:  [Characteristic] 99fa0034-338a-1024-8a49-009c0215f78a (Handle: 63): Unknown (write-without-response,write), Value: None

@voruti
Copy link
Contributor

voruti commented Feb 9, 2022

I thought maybe the up command for my desk is using another number than 71 and did this:

async def move_up(client):
    print('raw send up with loop:', UUID_COMMAND)
    for i in range(270):
        print(i)
        await client.write_gatt_char(UUID_COMMAND, bytearray(struct.pack("<H", i)))
        time.sleep(1)
    print('up loop finsihed', UUID_COMMAND)

but nothing happened 😄.

@rhyst
Copy link
Owner

rhyst commented Feb 19, 2022

So one thing I realised was that maybe the wake up command is being sent to the "reference input" characterisitc rather than the "command" characteristic. Perhaps someone could try:

    # Listen for changes to desk height and send first move command (if we are 
    # not already at the target height).
    if not has_reached_target(initial_height, target):
        await subscribe(client, UUID_HEIGHT, _move_to)
        COMMAND_WAKEUP = bytearray(struct.pack("<H", 254))
        await client.write_gatt_char(UUID_REFERENCE_INPUT, COMMAND_WAKEUP )
        await client.write_gatt_char(UUID_COMMAND, COMMAND_STOP)
        if direction == "UP":
            asyncio.create_task(move_up(client))

@Nepomucene
Copy link

Made changes above, and tried a "move-to" followed by a "sit".
After the "sit", I now have the following error
(Note that I'll have to wait a couple of hours to see if the wakeup worked)

Height: 733mm Target: 730mm Speed: -34mm/s
Height: 732mm Target: 730mm Speed: -26mm/s
Height: 731mm Target: 730mm Speed: -17mm/s
Task exception was never retrieved
future: <Task finished coro=<unsubscribe() done, defined at /home/pi/.local/lib/python3.7/site-packages/idasen_controller/main.py:312> exception=BleakDBusError('org.bluez.Error.Failed', 'No notify session started')>
Traceback (most recent call last):
File "/home/pi/.local/lib/python3.7/site-packages/idasen_controller/main.py", line 314, in unsubscribe
await client.stop_notify(uuid)
File "/home/pi/.local/lib/python3.7/site-packages/bleak/backends/bluezdbus/client.py", line 963, in stop_notify
assert_reply(reply)
File "/home/pi/.local/lib/python3.7/site-packages/bleak/backends/bluezdbus/utils.py", line 23, in assert_reply
raise BleakDBusError(reply.error_name, reply.body)
bleak.exc.BleakDBusError: [org.bluez.Error.Failed] No notify session started
Final height: 730mm (Target: 730mm)
Disconnected

@Nepomucene
Copy link

Nepomucene commented Feb 21, 2022

Left it overnight and tried this morning. Sadly no movement :-(

Probably better if I add the command definition for WAKEUP 🤦‍♂️
But now need to wait a couple of hours for the controller to go back to sleep

Note that I have some bleak errors, so the previous fix is not fully working:
Height: 733mm Target: 730mm Speed: -34mm/s
Height: 732mm Target: 730mm Speed: -26mm/s
Height: 731mm Target: 730mm Speed: -20mm/s
Task exception was never retrieved
future: <Task finished coro=<unsubscribe() done, defined at /home/pi/.local/lib/python3.7/site-packages/idasen_controller/main.py:313> exception=BleakDBusError('org.bluez.Error.Failed', 'No notify session started')>
Traceback (most recent call last):
File "/home/pi/.local/lib/python3.7/site-packages/idasen_controller/main.py", line 315, in unsubscribe
await client.stop_notify(uuid)
File "/home/pi/.local/lib/python3.7/site-packages/bleak/backends/bluezdbus/client.py", line 963, in stop_notify
assert_reply(reply)
File "/home/pi/.local/lib/python3.7/site-packages/bleak/backends/bluezdbus/utils.py", line 23, in assert_reply
raise BleakDBusError(reply.error_name, reply.body)
bleak.exc.BleakDBusError: [org.bluez.Error.Failed] No notify session started
Final height: 730mm (Target: 730mm)
Disconnected

@Nepomucene
Copy link

Sadly after 4h, the controller went to sleep, and I tried again. No result.
I have added a few prints here and there, and the "WAKEUP" and "STOP" are sent, but no action.

My question would be how confident are you with the exact WAKEUP command?
(Both "UUID_REFERENCE_INPUT" and "COMMAND_WAKEUP")
Note that you already seem to be sending the WAKEUP in the "move_up" and "move_down" so the addition here (#32 (comment)) may not be essential

@Nepomucene
Copy link

Nepomucene commented Feb 21, 2022

Played with the WAKEUP command:

  • Tried different UUID (0031 to 0034)
  • Tried different payloads (1 to 270)
    All failed

But also interestingly, tried just launching the app (no desk movement) and killing it (to close the bluetooth connection) instantly.
Commands instantly worked.

The wakeup is at app opening, not when sending up/down instructions (maybe also there)

@rhyst
Copy link
Owner

rhyst commented Mar 7, 2022

I've just pushed a change which may help with this if anyone is willing to test.

I was inspired by this repo to use a different characteristic to perform the height commands. As mentioned in that repo its essentially a "move to" command rather than a "move up/down" command which means that the desk now stops dead on the target height.

This works for me perfectly but I found I needed to add the wake up command, even on the Linak desk, and as it works on my desk I think that means I have got the wakeup command working. Maybe this will work on the DPC1C?

Change is only on github, not on pip yet.

@vniehues
Copy link

vniehues commented Mar 8, 2022

If somebody gets it to work with the DPC1C now, please report back! I'd love to add this to my homebridge plugin.

@dmitrym0
Copy link

dmitrym0 commented Mar 8, 2022

Just updated to the latest master, wanted to give this a go, but I can't seem to connect to the desk anymore. Do I use the Device identifier, in Bluetility on a Mac?

image

image

The desk appears to be paired:

image

I don't recall having these issues previously.

@rhyst
Copy link
Owner

rhyst commented Mar 8, 2022

Nothing has changed with the initial connection so it shouldn't be different 😓

Have you tried increasing the connection timeout? And maybe unpair and repair?

@vniehues
Copy link

vniehues commented Mar 8, 2022

@dmitrym0 did you recently update to macOS Monterey?
If yes, the problem might be related to #33

@dmitrym0
Copy link

dmitrym0 commented Mar 8, 2022

@dmitrym0 did you recently update to macOS Monterey?
If yes, the problem might be related to #33

Ahh you're bang on. Updated a couple of weeks ago. Thanks for pointing that out.

@mensfeld
Copy link

@AhmedKamal that is because our desks require additional command after the device goes to sleep. It seems that any accepted write to settings or desk rename does the job (tested with the linak app) but so far I was not able to reverse engineer the actual command

@AhmedKamal
Copy link

Yea, I tried to connect from the phone and then back and then it worked again.
I found a paid app that is able to control the desk which means it is feasible.
https://apps.apple.com/us/app/desk-remote-control/id1509037746

I'm not sure how to capture the Bluetooth communication. It has a macos app as well.

@mensfeld @LuisDiazUgena

@mensfeld
Copy link

@AhmedKamal unfortunately for me it's mac and I'm linux. I have the BLE communication of the original app from the android phone as well as I was able to decompile the android app but the complexity of the update code makes it hard for me to figure out :(

@LuisDiazUgena
Copy link

@mensfeld I have donwloaded the nordic nrf connect (any other ble scanner would work) and with that it's easy to get all the services for bluetooth. Maybe that is something that we can use?

@mensfeld
Copy link

mensfeld commented Jun 7, 2023

@LuisDiazUgena I have no idea how that works. I tried understanding the dumps from wireshark and I know, that there is an update that needs to be sent to the desk once in a while to "wake it" or unlock etc.

At the moment I gave up but I would love to have it working. It drives me crazy that I cannot automate desk journey through the day.

@LuisDiazUgena
Copy link

connecting to the desk in linux also output the whole list of characteristics. Those are here: https://pastebin.com/mYWYu0ub

Also, I;m trying to make a workaround trying to reset the docker container every half hour or something like that. Maybe that works. will keep this updated as I found out things.

@mensfeld
Copy link

mensfeld commented Jun 7, 2023

@LuisDiazUgena For me, the problem was not the characteristics but the usage of some of them. I mean one that is responsible for writing a name update/anything else that would just "wake" it up. The rest of the commands are pretty well-defined.

@rhyst
Copy link
Owner

rhyst commented Jun 7, 2023

I had another scan through the decompiled app to see if I could find anything to do with initialisation.

I found this function in com.linak.deskcontrol/sources/com/linak/sdk/connect/RxConnectionManager.java:

    /* access modifiers changed from: private */
    public final Completable setUpDevice(Device device, RxBleConnection rxBleConnection, DpgClient dpgClient) {
        Iterable<DpgCommand.ControlCommand> listOf = CollectionsKt.listOf(DpgCommand.ControlCommand.GET_CAPABILITIES,
                DpgCommand.ControlCommand.USER_ID, DpgCommand.ControlCommand.DESK_OFFSET,
                DpgCommand.ControlCommand.REMINDER_SETTING, DpgCommand.ControlCommand.GET_SET_MEMORY_POSITION_1,
                DpgCommand.ControlCommand.GET_SET_MEMORY_POSITION_2,
                DpgCommand.ControlCommand.GET_SET_MEMORY_POSITION_3,
                DpgCommand.ControlCommand.GET_SET_MEMORY_POSITION_4);
        Collection arrayList = new ArrayList(CollectionsKt.collectionSizeOrDefault(listOf, 10));
        for (DpgCommand.ControlCommand readCommand : listOf) {
            arrayList.add(DpgCommand.readCommand(readCommand));
        }
        List list = (List) arrayList;
        Completable doOnComplete = Completable
                .mergeArray(Observable.fromIterable(list)
                        .flatMapSingle(new RxConnectionManager$setUpDevice$dpgSetupCompletable$1(dpgClient))
                        .take((long) list.size()).ignoreElements()
                        .doOnComplete(RxConnectionManager$setUpDevice$dpgSetupCompletable$2.INSTANCE),
                        rxBleConnection
                                .readCharacteristic(LinakServices.Characteristic.GenericAccess.DEVICE_NAME.uuid())
                                .doOnSuccess(new RxConnectionManager$setUpDevice$nameReadCompletable$1(device))
                                .ignoreElement().retry(3),
                        rxBleConnection.readCharacteristic(LinakServices.Characteristic.ReferenceOutput.ONE.uuid())
                                .doOnSuccess(new RxConnectionManager$setUpDevice$referenceZeroReadCompletable$1(device))
                                .ignoreElement().retry(3).onErrorComplete())
                .doOnComplete(new RxConnectionManager$setUpDevice$1(device));
        Intrinsics.checkExpressionValueIsNotNull(doOnComplete, "Completable.mergeArray(d…anged()\n                }");
        return doOnComplete;
    }

which I guess is taking the list of commands and running them:

DpgCommand.ControlCommand.GET_CAPABILITIES,
DpgCommand.ControlCommand.USER_ID, 
DpgCommand.ControlCommand.DESK_OFFSET,
DpgCommand.ControlCommand.REMINDER_SETTING,
DpgCommand.ControlCommand.GET_SET_MEMORY_POSITION_1,
DpgCommand.ControlCommand.GET_SET_MEMORY_POSITION_2,
DpgCommand.ControlCommand.GET_SET_MEMORY_POSITION_3,
DpgCommand.ControlCommand.GET_SET_MEMORY_POSITION_4

This matches with what I found from that other repo earlier in the thread.

Another guess is that then the response is picked up in com.linak.deskcontrol/sources/com/linak/sdk/models/device/Dpg.java by a funciton called handleChangeDPG but as far as I can see this is just updating the app state and does not send further commands.

So it really seems like the only thing that the android app does is send that list of commands. Perhaps we just haven't found the correct byte encoding or something like that?

Trying to follow the code above it seems like it takes each command constant and runs:

DpgCommand.readCommand(readCommand));

This looks like it simply wraps the command constant in some other bytes:

    public static DpgCommand readCommand(ControlCommand controlCommand) {
        return new DpgCommand(new byte[]{ByteCompanionObject.MAX_VALUE, controlCommand.code, 0});
    }

It looks like ByteCompanionObject.MAX_VALUE is just java.lang.Byte.MAX_VALUE which is 127.

So for examples GET_CAPABILITIES has a value of 128 which I think wraps round to -128 then it seems like this creates a byte object new byte[]{127, -128,0} as the actual command. In Python that becomes:

>>> bytearray([127,128,0])
bytearray(b'\x7f\x80\x00')

(Python seems to want 128 and not -128).

This matches the command I asked someone to send earlier.

Long winded way to say I think the commands are correct BUT looking at this now I think they should be sent to UUID_DPG. Can someone modify the wakeup function to be:

async def wakeUp(client):
    await client.write_gatt_char(UUID_DPG, b"\x7F\x80\x00")
    await client.write_gatt_char(UUID_DPG, b"\x7F\x86\x00")
    await client.write_gatt_char(UUID_DPG, b"\x7F\x81\x00")
    await client.write_gatt_char(UUID_DPG, b"\x7F\x88\x00")
    await client.write_gatt_char(UUID_DPG, b"\x7F\x89\x00")
    await client.write_gatt_char(UUID_DPG, b"\x7F\x8a\x00")
    await client.write_gatt_char(UUID_DPG, b"\x7F\x8b\x00")
    await client.write_gatt_char(UUID_DPG, b"\x7F\x8c\x00")
    await client.write_gatt_char(UUID_DPG, b"\x7F\x8A\x00")
    await client.write_gatt_char(UUID_DPG, b"\x7F\x8B\x00")
    await client.write_gatt_char(UUID_DPG, b"\x7F\x8C\x00")
    await client.write_gatt_char(UUID_DPG, b"\x7F\x86\x80\x10\xe5\xc0\xca\xd8\xbe\xc4\x48\xe1\xa0\x80\x3e\x56\xf8\xd4\xcf\xca")
    await client.write_gatt_char(UUID_COMMAND, COMMAND_WAKEUP)

And try again? Note that COMMAND_WAKEUP I think does need to be on UUID_COMMAND as in the decompiled app it is a LinCommand not a DpgCommand. Also it may not be needed at all.

@mensfeld
Copy link

mensfeld commented Jun 7, 2023

@rhyst nope :( still does not wake up with the suggested wakeup

@LuisDiazUgena
Copy link

Hi guys,

I have been working on a different via to be able to use the desk with HA. Basically I have used a existing project that wraps the idasen controller on a docker enviroment in nodejs and added some logic and new mqtt topics in order to be able to control it using HA. I have created a PR to original repo and the code is in my fork meanwhile.

Basically I have been able to run flawleslly the server for a few days now without any issue.

Moving the desk from HA works, but for me it feel weird. I always move from stored heigth 1 to stored heigth 2 and viceversa, so I double tap the controller and I'm ready. Also, triggering the movement from the HA buttons have always been weird, because the movement has stopped several times before reaching the desired heigth.

@mensfeld
Copy link

I always move from stored heigth 1 to stored heigth 2 and vice-versa

Maybe this is something we should pursue here.

@kaml123
Copy link

kaml123 commented Oct 27, 2023

Hi all,

Please check if this will work for you:

async def wakeUp(client):
    await client.write_gatt_char(UUID_DPG, b"\x7F\x86\x00")
    await client.write_gatt_char(UUID_DPG, b"\x7F\x86\x80\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11")
    await client.write_gatt_char(UUID_COMMAND, COMMAND_WAKEUP)

@beckerhe
Copy link

Hi all,

Please check if this will work for you:

async def wakeUp(client):
    await client.write_gatt_char(UUID_DPG, b"\x7F\x86\x00")
    await client.write_gatt_char(UUID_DPG, b"\x7F\x86\x80\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11")
    await client.write_gatt_char(UUID_COMMAND, COMMAND_WAKEUP)

Yes! That works for me. Thank you for sharing!

@rhyst
Copy link
Owner

rhyst commented Oct 28, 2023

That's great! Thank you @kaml123 , do you have an explanation for what it's doing? I will confirm it works with idasen desk and add it in (unless you want to make a PR 🙂)

@kaml123
Copy link

kaml123 commented Oct 29, 2023

Hi @rhyst,
I also had a problem with DGC1C and started experimenting based on logs.
From that experiments I noticed that write 0x7F 0x86 0x00 to DPG characteristic will result an response with current USER_ID set in DPG1C.
In my case I got: \x01\x11\x00\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\0x0d\x0e\x0f\x10\x11
Analyzing response I got:

- byte[0] - status, 1 - OK, else ERROR
- byte[1] - length in bytes
- byte[2-18] - USER_ID

Please look at byte[2] which is the only one that differs between the one read and the one that is written. I think byte[2] determine if controller will handle movement or not.
Please remember that this USER_ID will probably be written to the EEPROM inside DPG1C controller, so often write will not be a good idea. The best idea will be to read USER_ID and check if byte[2] = 1 if not then write a proper USER_ID.
This change should also work with the Idasen desk.
Please prepare a fix.

@mensfeld
Copy link

@kaml123 works for me as well so far. Amazing! 🙏

@rhyst
Copy link
Owner

rhyst commented Oct 30, 2023

I have published 2.1.0.dev which attempts to set a user ID after connecting which is what @kaml123 suggestion does I think. You can specify the user id in your config file or as a CLI option but I don't think there's a reason to change it from the default.

Can someone let me know if this version works on their DPG1C?

@kaml123
Copy link

kaml123 commented Oct 30, 2023

Hi @rhyst,
Great job. I think we don't need to store user_id in config file. Please check #69 with my slightly modification.
I also added error handling for DPG and ability to get base height from controller when it is set to 0 in config file.

@voruti
Copy link
Contributor

voruti commented Oct 30, 2023

Hi,
I'm getting

Traceback (most recent call last):
  File "idasen_controller/main.py", line 10, in <module>
    from .config import config, Commands
ImportError: attempted relative import with no known parent package

when running poetry run idasen_controller/main.py --config config.yaml --server from the project root and know Python too little to understand what's wrong.

@rhyst
Copy link
Owner

rhyst commented Oct 30, 2023

@voruti ah because of the relative imports you need to run it like:

poetry run python -m idasen_controller.main --config config.yaml --server 

I also know Python too little to know why this is needed 😆

@voruti
Copy link
Contributor

voruti commented Oct 30, 2023

@voruti
Copy link
Contributor

voruti commented Oct 30, 2023

Traceback (most recent call last):
  File "/app/idasen_controller/main.py", line 210, in main
    await run_command(client)
  File "/app/idasen_controller/main.py", line 97, in run_command
    await Desk.move_to(client, target)
  File "/app/idasen_controller/desk.py", line 47, in move_to
    await ControlService.wakeup(client)
AttributeError: type object 'ControlService' has no attribute 'wakeup'

https://github.com/rhyst/idasen-controller/blob/master/idasen_controller/desk.py#L47 but there is no https://github.com/rhyst/idasen-controller/blob/master/idasen_controller/gatt.py#L148

What am I missing?

Fixed by #69

@voruti
Copy link
Contributor

voruti commented Oct 31, 2023

Can someone let me know if this version works on their DPG1C?

As I stated above #32 (comment) I have the DPG1M. It's working for me quite well now.

@rhyst
Copy link
Owner

rhyst commented Oct 31, 2023

Released 2.1.0 with these changes so closing this issue 🥳

@rhyst rhyst closed this as completed Oct 31, 2023
@mensfeld
Copy link

Works like a charm! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request need hardware
Projects
None yet
Development

No branches or pull requests