py-air-control: Unable to get CoAP to work with AC2889/60

Firmware (from the app) reports as

Firmware Version: AWS_Philips_AIR@54.2 Device Version: 1.0.7 Model: AC2889/10

Any attempt to send a command just gets back {"status":"failed"} (looking at it via tcpdump). Also from tcpdump, the app appears to be exclusively using the cloud based system to talk to it. If I put both on a wifi network not connected to the internet, even the app doesn’t work.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 82 (40 by maintainers)

Most upvoted comments

When we look at a Wireshark capture, we can see that the packet is not truncated. But the packet received by the library is indeed truncated.

The problem comes from the CoAPthon3 library. We can see in coapthon/client/coap.py, line 239, method receive_datagram:

                datagram, addr = self._socket.recvfrom(1152)

I’ve cloned the CoAPthon3 library and modified this line to:

                datagram, addr = self._socket.recvfrom(1500)

Now it works like a charm!

I will try submitting a PR to this external dependency, but there has been no update to the pip package since 2018, even if several PRs have been accepted since…

@spider7611 thanks for the feedback

I see they merged the patch submitted by @bobzomer for this: https://github.com/Tanganelli/CoAPthon3/issues/29

maybe we can put dependency to the latest code on github until they release on PyPI.

Thoughts?

Can confirm that this gets data on my AC2889/10 on version 1.0.7. Thanks for all the work guys.

Good news: I’ve forked the project using a new git branch: https://github.com/Cyber1000/py-air-control/tree/coap_1_0_7

general info (Everyone is invited to test 🧪 )

Installation (maybe you need sudo):

git clone -b coap_1_0_7 https://github.com/Cyber1000/py-air-control.git && cd py-air-control
python3 setup.py install

What works for now:

airctrl --ipaddr 192.168.11.190 --version 1.0.7 -d
airctrl --ipaddr 192.168.11.190 --version 1.0.7

So you are getting information in json and list-format, writing to your device won’t work for now.

technical info (for everyone who likes to develop and improve my code/spike)

  • @rgerganov, @shexbeer and everyone who’s interested: not PR ready, changes in https://github.com/Cyber1000/py-air-control/blob/coap_1_0_7/pyairctrl/airctrl.py
  • I’ve removed the protocol-option and added this instead parser.add_argument('--version', help='set the version of your device ', choices=['0.1.0','0.2.1', '1.0.7'], default='0.1.0')
    • I’ve taken 0.1.0, as I didn’t know your version @rgerganov
    • I didn’t want to make a coap2, cause there may be a version 2 of coap sometimes and what to do if we have another device-sw-version (coap or not coap) in the future?
    • version would be independant of a protocol, though I must admit I’m not really happy with 0.1.0, 0.2.1, 1.0.7 -> any better suggestion is appreciated
  • I decided to make an abstract class HTTPAirClientBase
    • for now only my Version107Client inherits from it, maybe we could share some code
      • for example _dump_status would be nearly the same wether you have http or coap
      • some values might be missing from one version to another, but some of them are devicespecific anyway (I don’t have a waterlevel for example on my device)
    • any suggestions to get a valid base class are appreciated (or reasons against a base class)
  • It’s work in progress, for example my _dump_status just outputs the key/values like [iaql]: 5 and no human readable text like “Allergen index”
  • Annotations to my code are welcome (I can make a PR, though it’s not ready, if that’s better to make annotations)

Great work with the decryption! I’m not a python guy myself, but if it helps - I’ve put together a java sample here, which works with my AC3829/10.

@bocsitomcsi You seem to have the seem problem like https://github.com/rgerganov/py-air-control/issues/35#issuecomment-625288284 (btw. it’s AC2729/50 too) and some others here. The returning message is too long for a single coap-package, and is cut somewhere in the middle. I don’t know so far how to get multiple packages.

I just catch the exception now and you get a “Message from device got corrupted” therefore.

I’ll make a PR within the next 2 days with this known limitation and will look into this in the near future.

From your example:

encodedCounter: substring(0, 8) ‘23D56232’

decodedCounter: remove zeros in front if they exist. hex -> decimal = 601186866

const keyAndIv = CleanHomeCoapHelper.bufferToHexString(CleanHomeCoapHelper.toMD5('JiangPan' + encodedCounter));
const secretKey = keyAndIv.substring(0, keyAndIv.length / 2);
const iv = keyAndIv.substring(keyAndIv.length / 2, keyAndIv.length);
const encodedMessage = response.substring(8, response.length - 64);
return CleanHomeCoapHelper.fromAES(encodedMessage, secretKey, iv);


result: {
  encodedCounter: '23D56232',
  keyAndIv: '10917AAD9D5AD7FB36F62901C8072233',
  secretKey: '10917AAD9D5AD7FB',
  iv: '36F62901C8072233',
  encodedMessage: '3CC15DD4C1E80B11EF5D2E70493CF5C470671A5B2AD54578006A3B9C965D8F39D99D0152F37AA5286C1AEEF6C5D5A608FA5AB38206532E88C27C4FDA3437561F687D59CDA0BCA2F09E78B2BA5EEC6AFD99354646A66231031375CBACA80D6BCE66244EB08269A121F326D089971C6F9BE1D6649D453C98D961FA3D4767D3A8512B5E92FF7F1D285FD420FD527057C419A3B2063EB87831B7C6E843830E6DEAC187DD63CE2B8F0B756F8224C2C40799812923A4398298A493C7DD38A240BCA5AB182BC9582687F2EF1DDB0D2FD945CD4F616BA8CE66D4731148BB93B001D47B1B28C4A8C621298A8EBB41C4AFA3696F593E6C60397B8361DE2FF4A98BE99FD84874B10A70111139154E30E61E0A10E85CD6E8942A9943F076FEDC8E31008BAA5C068A96C117042EF80AE6E2D897C97660F24775DE571F09C32064C2FEB85A4EDA80AD1C5EA2CA385DA83C1883601B4D5A5EDFD5DA5FB02DBA44FFA4980142D1A157B6EBC48D3D71CA91A4433E7E5542732CCE6EAB9C27171AAEDFE360A9A07919A2BC75466EF6AE7ED8FBD1577477AE75D857B54FEB43476E6A5E41D331F1A6CBE0B700D87FF74882C0D0EBAE09D33ED17DC55DC00CE28420000BD414F513682B40B6EFF436331A708522C02F80D2408CBAE4DF4259A5E23285BD385AEDCC64B2653C71A4FBE38094EF2B1C00036DA2B0E4C39F916454C64A48707B66A587A894',
  message: '{"state":{"reported":{"name":"Luftreiniger","type":"AC2889","modelid":"AC2889/10","swversion":"1.0.7","om":"1","pwr":"1","cl":false,"aqil":0,"uil":"0","dt":0,"dtrs":0,"mode":"A","pm25":1,"iaql":1,"aqit":4,"ddp":"0","err":0,"fltt1":"A3","fltt2":"C7","fltsts0":358,"fltsts1":4798,"fltsts2":2398,"ota":"no","Runtime":8842135,"WifiVersion":"AWS_Philips_AIR@54.2","ProductId":"<longNumber>","DeviceId":"<longNumber>","StatusType":"localcontrol","ConnectType":"Localcontrol"}}}'
}

I just tried it out:

  • This error is only there if your device sends messages bigger than 1152 bytes and you have a CoAPthon3 1.0.1 version (and not the current master CoAPthon3@89d5173)
  • My device sends smaller packages (less information), so CoAPthon3 1.0.1 version works well and I can’t really test your problem therefore.

My best guess is that you have installed CoAPthon3 multiple times:

  • the newer version with sudo/root (installed in /usr/local)
  • the official 1.0.1-version without sudo (automatically installed with py-air-control in your homedir), that leads to this error you mentioned

I would try as “normal” user:

sudo pip3 uninstall CoApthon3
pip3 uninstall CoApthon3
pip3 install -U git+https://github.com/Tanganelli/CoAPthon3@89d5173

Maybe you have also installed some version with pip (and not pip3) as you mentioned correctly, that the README was wrong. Then a uninstall command with pip, similar to the shown above might help.

I also spend some time looking into this and came to the conclusion that putting a dependency to source control in setup.py is deprecated and considered bad practice. This is what the pip documentation says about cases like ours:

Requirements files are used to override a dependency with a local patch that lives in version control. For example, suppose a dependency SomeDependency from PyPI has a bug, and you can’t wait for an upstream fix. You could clone/copy the src, make the fix, and place it in VCS with the tag sometag. You’d reference it in your requirements file with a line like so:

git+https://myvcs.com/some_dependency@sometag#egg=SomeDependency

So I updated the requirements file and put a note in the README (which needs to be fixed 😃 ).

After this modification:

The problem comes from the CoAPthon3 library. We can see in coapthon/client/coap.py, line 239, method receive_datagram:

                datagram, addr = self._socket.recvfrom(1152)
I've cloned the CoAPthon3 library and modified this line to:

                datagram, addr = self._socket.recvfrom(1500)

Working my model Id: AC3858/50 too! airctrl --ipaddr 192.168.1.102 --protocol coap --pwr 1 Need home assistant integration! 😃

[name] Name: Living Room [type] Type: AC3858 [modelid] ModelId: AC3858/50 [swversion] Version: Ms4102 [language] language: EN [om] Fan speed: 1 [pwr] Power: ON [cl] Child lock: False [aqil] Light brightness: 0 [uil] Buttons light: OFF [uaset] uaset: A [mode] Mode: AG [pm25] PM25: 3 [iaql] Allergen index: 1 [aqit] Air quality notification threshold: 10 [tvoc] tvoc: 1 [ddp] Used index: PM2.5 [rddp] rddp: 1 [fltt1] HEPA filter type: NanoProtect Filter Series 3 (FY2422) [fltt2] Active carbon filter type: none [fltsts0] Pre-filter and Wick: clean in 155 hours [fltsts1] HEPA filter: replace in 4560 hours [fltsts2] Active carbon filter: replace in 65535 hours [filna] filna: AC3036 [filid] filid: AC3036… [ota] Over the air updates: no [Runtime] Runtime: 0.96 hours [WifiVersion] WifiVersion: AWS_Philips_AIR@53 [ProductId] ProductId: … [DeviceId] DeviceId: … [StatusType] StatusType: localcontrol [ConnectType] ConnectType: Localcontrol

I have merged @Cyber1000’s PR into master, you are welcome to test with HEAD. I will release a new PyPI version when we fix the issues with the truncated CoAP response.

Thanks to everyone who contributed to the project so far!

@Cyber1000 Just to mention this: Your fork also works for my AC2889/10! Thanks!

➜ pipx install git+https://github.com/Cyber1000/py-air-control.git@coap_1_0_7
…
➜ airctrl --version 1.0.7 --ipaddr 192.168.1.49
[name]: Schlafzimmer
[type]: AC2889
[modelid]: AC2889/10
[swversion]: 1.0.7
[om]: 1
[pwr]: 1
…

@Cyber1000 Here is the encrypted payload:

$ airctrl --ipaddr 192.168.1.24 --version 1.0.7 -d
073CBCB1DCABB8CE96A60C81CBF8A6A9E57F4C9D0F0AD86C4B1A1F005874061AD6063DC72432138ABDD0A5AFEDD1D3333D430758DE3BD294BE026B2D1630A71C089EE74036C02F5D8954FEBA93A2886D3F8252263F770F3320ACEFB643FC65C97F0AC762B312AB18203D98F303DC3E1C7C8A8E3E35C86ED5C126121632088D569B50CFED6B72C4E44727E31E0AEE800B022B8B8F71D45CE19A50A0E94471661D9D4CA6694A75C81E5347FAE48E9F4706F6B169B8232BD23CCC9DD6353E2776199A5281EF351AA19AA8E1E003F7DAC6C93A05958EA71E1E55F0A78F63EC4F7D6258FD7F85D994AFF1815DFBC9714F85465FF0955884A3B39F55DDB62F19C7AD3A24AB1E66BEC78FA6FC727AF8C5BD28DB74655F836B71DD8872002F61FFCDF4799D40214F692953B4690A6B00FFBA5C260E5CDD998C2D8FB813EBC8E658B3B5E10907695268DA5DE213D5F134704AAA6E47B3DE42208BB9694F7E0F0414F42292E865A983EAFF0044D01E93F2E0ACF2F8FA4263064749ACE8A5A05FDD36EF1CF301A3311BE5FCE5E2B71085F752E1D4020A25BCECA9DDA4D577046395C82698B413666D7C3DAA0E7A4CEA7C1FD9DCB4FD791D82C12FA385E827F6B22D5C8F4FC6E0DE967BEEC5F365C39317CED6C2F6D86F3C03440724D9AC026A692C38D71BE227C4E59E828F02BB4CCB156919B1D989D76E4585DB6874FABFAD2495FC0365A53F542FACB244EBD35C80F4BEFA2CCB7A44A12A8B6D77D23388137600419878F3D946F9303C8003A7FB920105C2167DA0C9F0C6A238126DE0
Traceback (most recent call last):
  File "/home/Lorianne/philips/bin/airctrl", line 8, in <module>
    sys.exit(main())
  File "/home/Lorianne/philips/lib/python3.8/site-packages/pyairctrl/airctrl.py", line 768, in main
    c.get_status(debug=args.debug)
  File "/home/Lorianne/philips/lib/python3.8/site-packages/pyairctrl/airctrl.py", line 636, in get_status
    status = self._get()
  File "/home/Lorianne/philips/lib/python3.8/site-packages/pyairctrl/airctrl.py", line 678, in _get
    decrypted_payload = self._decrypt_payload(encrypted_payload)
  File "/home/Lorianne/philips/lib/python3.8/site-packages/pyairctrl/airctrl.py", line 664, in _decrypt_payload
    decoded_message = AES.new(bytes(secret_key.encode('utf8')), AES.MODE_CBC, bytes(iv.encode('utf8'))).decrypt(bytes.fromhex(encoded_message))
  File "/home/Lorianne/philips/lib/python3.8/site-packages/Cryptodome/Cipher/_mode_cbc.py", line 246, in decrypt
    raise ValueError("Data must be padded to %d byte boundary in CBC mode" % self.block_size)
ValueError: Data must be padded to 16 byte boundary in CBC mode

This can be decrypted if we get a bigger chunk from the payload:

msg = encrypted_payload[8:-8].upper()
cipher = AES.new(secret_key.encode('ascii'), AES.MODE_CBC, iv.encode('ascii'))
print(cipher.decrypt(msg))

this is the result:

b'{"state":{"reported":{"name":"Maison","type":"AC3039","modelid":"AC3039/10","swversion":"Ms3104","language":"EN","DeviceVersion":"1.0.4","om":"s","pwr":"1","cl":false,"aqil":0,"uil":"0","uaset":"A","mode":"S","pm25":11,"iaql":3,"aqit":4,"tvoc":1,"ddp":"1","rddp":"1","err":0,"fltt1":"A3","fltt2":"none","fltsts0":129,"fltsts1":4752,"fltsts2":65535,"filna":"0","filid":"0","ota":"ck","Runtime":1037325155,"WifiVersion":"AWS_Philips_AIR@54.2","ProductId":"660c6902649b11e9a1e3061302926720","DeviceId":"0b529c58af8111e9a1e3061302926720","StatusType":"localcontrol'

As you can see it is truncated. I guess this means we should either increase the COAP response buffer or make another request.

@Cyber1000 Iam running this

[modelid]     ModelId: AC2729/10
[swversion]   Version: 0.2.1

I’ve got a friend who does Android reversing who is willing to take at least a brief look at the app. I can go looking for a uart if I can’t return the AC2889. Will probably open up the AC3259/60 regardless to add a switch for the speaker.