Closed linusbierhoff closed 10 months ago
@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?
@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.
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.
@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.
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.
@rhyst nope :( still does not wake up with the suggested wakeup
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.
I always move from stored heigth 1 to stored heigth 2 and vice-versa
Maybe this is something we should pursue here.
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)
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!
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 🙂)
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.
@kaml123 works for me as well so far. Amazing! :pray:
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?
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.
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.
@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 😆
It looks like Python 3.8 isn't supported: https://peps.python.org/pep-0604/ in line https://github.com/rhyst/idasen-controller/blob/master/idasen_controller/gatt.py#L80 which you wrote in the https://github.com/rhyst/idasen-controller/blob/master/CHANGELOG.md?plain=1#L13.
I switched to Python 3.10
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?~
Can someone let me know if this version works on their DPG1C?
As I stated above https://github.com/rhyst/idasen-controller/issues/32#issuecomment-1033941195 I have the DPG1M. It's working for me quite well now.
Released 2.1.0 with these changes so closing this issue 🥳
Works like a charm! :)
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:Is there anything I need to be aware of when using the script on a raspberry pi or with a linak desk?