dvcrn / cocoro-sdk

Unofficial TypeScript SDK for the SHARP Cocoro API
5 stars 1 forks source link

Are there any plans to support air purifiers? #2

Closed shossk closed 2 years ago

shossk commented 2 years ago

Hi, thanks for providing a useful sdk.

cocoro also includes an air purifier, and it would be great if you could include an air purifier in the name of Git, is that too much work?

It looks like the TEMPERATURE property names are grouped under "F1" instead of "FA", so I'll have to check the binary again.

dvcrn commented 2 years ago

Since I don't own a sharp air purifier it's a bit hard for me to debug and test it

But it should be similar to how I implemented the aircon. Could you maybe dump the box info of your purifier with properties and status as json? (Method is 'queryDevices')

If it's very different maybe we need to differentiate device types by different classes

shossk commented 2 years ago
boxInfo.json
```JSON { "box": [ { "boxId": "https://db.cloudlabs.sharp.co.jp/clpf/key/xxx", "maxFlag": false, "pairingFlag": true, "pairedTerminalNum": 1, "timezone": "Asia/Tokyo", "terminalAppInfo": [ { "terminalAppId": "https://db.cloudlabs.sharp.co.jp/clpf/key/xxx", "appName": "spremote_i:1:2.2.16", "userNumber": 0 } ], "echonetData": [ { "maker": "SHARP", "series": null, "model": "KILS70", "serialNumber": null, "echonetNode": "7c-xx-xx-xx-f7-36", "echonetObject": "012345", "echonetAttr": "xx", "echonetProperty": "xx", "deviceId": 1053396, "simulPerfModeFlag": false, "propertyUpdatedAt": "2022-01-04T21:13:52", "labelData": { "id": 12345, "place": "リビング", "name": "空気清浄機", "deviceType": "AIR_CLEANER", "zipCd": "12345", "yomi": "くうきせいじょうき", "lSubInfo": "{\"room_data\":{\"size\": 15.0, \"struct\": \"prefab\", \"unit\": \"tatami\"}}" } } ] } ] } ```
deviceProperty.json
```JSON { "deviceProperty": { "deviceId": 1053396, "echonetNode": "7c-xx-xx-xx-f7-36", "echonetObject": "012345", "registerLevel": 3, "label": "空気清浄機", "className": "空気清浄器", "maker": "SHARP", "series": null, "model": "KILS70", "place": "リビング", "propertyUpdatedAt": "2022-01-04T18:47:47", "property": [ { "statusName": "動作状態", "statusCode": "80", "get": true, "set": true, "inf": true, "valueType": "valueSingle", "valueSingle": [ { "name": "ON", "code": "30" }, { "name": "OFF", "code": "31" } ], "valueRange": null }, { "statusName": "設置場所", "statusCode": "81", "get": true, "set": true, "inf": true, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "規格Ver", "statusCode": "82", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "識別番号", "statusCode": "83", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "瞬時消費電力計測値", "statusCode": "84", "get": true, "set": false, "inf": false, "valueType": "valueRange", "valueSingle": null, "valueRange": { "type": "int", "min": "0", "max": "65533", "step": "1", "unit": "W" } }, { "statusName": "積算消費電力計測値", "statusCode": "85", "get": true, "set": false, "inf": false, "valueType": "valueRange", "valueSingle": null, "valueRange": { "type": "int", "min": "0", "max": "999999999", "step": "1", "unit": "0.001kWh" } }, { "statusName": "メーカ異常コード", "statusCode": "86", "get": true, "set": false, "inf": true, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "電流制限設定", "statusCode": "87", "get": true, "set": true, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "異常発生状態", "statusCode": "88", "get": true, "set": false, "inf": true, "valueType": "valueSingle", "valueSingle": [ { "name": "異常発生有", "code": "41" }, { "name": "異常発生無", "code": "42" } ], "valueRange": null }, { "statusName": "異常内容", "statusCode": "89", "get": true, "set": false, "inf": true, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "メーカーコード", "statusCode": "8A", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "事業場コード", "statusCode": "8B", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "商品コード", "statusCode": "8C", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "製造番号", "statusCode": "8D", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "製造年月日", "statusCode": "8E", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "節電動作設定", "statusCode": "8F", "get": true, "set": true, "inf": false, "valueType": "valueSingle", "valueSingle": [ { "name": "節電動作中", "code": "41" }, { "name": "通常動作中", "code": "42" } ], "valueRange": null }, { "statusName": "遠隔操作設定", "statusCode": "93", "get": true, "set": true, "inf": false, "valueType": "valueSingle", "valueSingle": [ { "name": "公衆回線未経由", "code": "41" }, { "name": "公衆回線経由", "code": "42" } ], "valueRange": null }, { "statusName": "現在時刻設定", "statusCode": "97", "get": true, "set": true, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "現在年月日設定", "statusCode": "98", "get": true, "set": true, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "電力制限設定", "statusCode": "99", "get": true, "set": true, "inf": false, "valueType": "valueRange", "valueSingle": null, "valueRange": { "type": null, "min": "0", "max": "65535", "step": "1", "unit": "W" } }, { "statusName": "積算運転時間", "statusCode": "9A", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "状変プロパティマップ", "statusCode": "9D", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "Setプロパティマップ", "statusCode": "9E", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "Getプロパティマップ", "statusCode": "9F", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "風量設定", "statusCode": "A0", "get": true, "set": true, "inf": true, "valueType": "valueSingle", "valueSingle": [ { "name": "自動", "code": "41" }, { "name": "風量1", "code": "31" }, { "name": "風量2", "code": "32" }, { "name": "風量3", "code": "33" }, { "name": "風量4", "code": "34" }, { "name": "風量5", "code": "35" }, { "name": "風量6", "code": "36" }, { "name": "風量7", "code": "37" }, { "name": "風量8", "code": "38" } ], "valueRange": null }, { "statusName": "空気汚れ検知状態", "statusCode": "C0", "get": true, "set": false, "inf": false, "valueType": "valueSingle", "valueSingle": [ { "name": "検知有", "code": "41" }, { "name": "検知無", "code": "42" } ], "valueRange": null }, { "statusName": "煙(タバコ)動作設定", "statusCode": "C1", "get": true, "set": false, "inf": false, "valueType": "valueSingle", "valueSingle": [ { "name": "検知有", "code": "41" }, { "name": "検知無", "code": "42" } ], "valueRange": null }, { "statusName": "光触媒動作設定", "statusCode": "C2", "get": true, "set": true, "inf": false, "valueType": "valueSingle", "valueSingle": [ { "name": "光触媒ON", "code": "41" }, { "name": "光触媒OFF", "code": "42" } ], "valueRange": null }, { "statusName": "フィルター交換状態", "statusCode": "E1", "get": true, "set": false, "inf": false, "valueType": "valueSingle", "valueSingle": [ { "name": "交換時期通知有", "code": "41" }, { "name": "交換時期通知無", "code": "42" } ], "valueRange": null }, { "statusName": "空気清浄機基本情報", "statusCode": "F0", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "運転状態詳細", "statusCode": "F1", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "運転状態詳細2", "statusCode": "F2", "get": true, "set": false, "inf": true, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "運転状態詳細3", "statusCode": "F3", "get": true, "set": true, "inf": true, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "独自プロパティF4", "statusCode": "F4", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "独自プロパティF5", "statusCode": "F5", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "独自プロパティF6", "statusCode": "F6", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "独自プロパティF7", "statusCode": "F7", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "独自プロパティF8", "statusCode": "F8", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "独自プロパティF9", "statusCode": "F9", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "独自プロパティF10", "statusCode": "FA", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "独自プロパティF11", "statusCode": "FB", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "独自プロパティF12", "statusCode": "FC", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "機器FWUP情報", "statusCode": "FD", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "独自プロパティF14", "statusCode": "FE", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null }, { "statusName": "独自プロパティF15", "statusCode": "FF", "get": true, "set": false, "inf": false, "valueType": "valueBinary", "valueSingle": null, "valueRange": null } ], "status": [ { "statusCode": "80", "valueType": "valueSingle", "valueSingle": { "code": "30" }, "valueRange": null, "valueBinary": null }, { "statusCode": "81", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "00" } }, { "statusCode": "82", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "00004900" } }, { "statusCode": "83", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "84", "valueType": "valueRange", "valueSingle": null, "valueRange": { "type": "int", "code": "16" }, "valueBinary": null }, { "statusCode": "85", "valueType": "valueRange", "valueSingle": null, "valueRange": { "type": "int", "code": "80" }, "valueBinary": null }, { "statusCode": "86", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "080000050000000000000000" } }, { "statusCode": "87", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "88", "valueType": "valueSingle", "valueSingle": { "code": "42" }, "valueRange": null, "valueBinary": null }, { "statusCode": "89", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "0000" } }, { "statusCode": "8A", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "000005" } }, { "statusCode": "8B", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "313034" } }, { "statusCode": "8C", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "4B494C533730000000000000" } }, { "statusCode": "8D", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "8E", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "8F", "valueType": "valueSingle", "valueSingle": { "code": null }, "valueRange": null, "valueBinary": null }, { "statusCode": "93", "valueType": "valueSingle", "valueSingle": { "code": null }, "valueRange": null, "valueBinary": null }, { "statusCode": "97", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "98", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "99", "valueType": "valueRange", "valueSingle": null, "valueRange": { "type": null, "code": null }, "valueBinary": null }, { "statusCode": "9A", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "9D", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "8081868889A0C0F3" } }, { "statusCode": "9E", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "8081A0F3F4" } }, { "statusCode": "9F", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "80818284858688898A8B8C9D9E9FA0C0F0F1F2F3F4FCFD" } }, { "statusCode": "A0", "valueType": "valueSingle", "valueSingle": { "code": "41" }, "valueRange": null, "valueBinary": null }, { "statusCode": "C0", "valueType": "valueSingle", "valueSingle": { "code": "41" }, "valueRange": null, "valueBinary": null }, { "statusCode": "C1", "valueType": "valueSingle", "valueSingle": { "code": null }, "valueRange": null, "valueBinary": null }, { "statusCode": "C2", "valueType": "valueSingle", "valueSingle": { "code": null }, "valueRange": null, "valueBinary": null }, { "statusCode": "E1", "valueType": "valueSingle", "valueSingle": { "code": null }, "valueRange": null, "valueBinary": null }, { "statusCode": "F0", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "00000000070000003D0185080BB80BB804E2E14A380000000000000000040001" } }, { "statusCode": "F1", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "5200D813300EFF00F00000000001CD00070000000700000485000000000000000000000000000000" } }, { "statusCode": "F2", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "09000000000000000000000000002100001900FFFF00000081FF0020010000000000000000000000" } }, { "statusCode": "F3", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "0000000020000000C000000000FF00FF0000000000000000000000" } }, { "statusCode": "F4", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } }, { "statusCode": "F5", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "F6", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "F7", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "F8", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "F9", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "FA", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "FB", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "FC", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "0207E3" } }, { "statusCode": "FD", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": "06070100010000000000000000000000000000000000000000" } }, { "statusCode": "FE", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } }, { "statusCode": "FF", "valueType": "valueBinary", "valueSingle": null, "valueRange": null, "valueBinary": { "code": null } } ] } } ```

Thank you! I'll attach the json!

Can you tell just by looking at the json? I think it's the same to some extent, but the temperature and humidity locations are different, and there seems to be more data for PM2.5 and dust.

By the way, the temperature and humidity data seems to be here.

{
  "statusCode": "F1",
  "valueType": "valueBinary",
  "valueSingle": null,
  "valueRange": null,
  "valueBinary": {
    "code": "5200D813300EFF00F00000000001CD00070000000700000485000000000000000000000000000000"
  }
}

offset 4 is temperature offset 5 is humidity

I've been working on forking it over here, but it's still hard to separate the classes for air conditioners and air purifiers, so if you can do that, I can test it and submit a pull request.

I can send you the data if you need it.

dvcrn commented 2 years ago

Could you capture a few requests from the app?

Like link it to something like Proxyman or charles, then open the cocoro app and change a few values in increments.

Since the cocoro class is just interfacing with Device, I think it would make sense to add this as a separate device (https://github.com/dvcrn/cocoro-sdk/blob/master/src/device.ts#L43), maybe a subclass? Since all a Device has to do is prepare the update queue. The Cocoro class is then batching it to the sharp servers

Maybe we can try to subclass/inherit from Device and see what is common, and what is different. (Device -> Aircon, Device -> AirPurifier)

In either way we need to overwrite / redefine a few methods like getRoomTemperature() (https://github.com/dvcrn/cocoro-sdk/blob/master/src/device.ts#L186), etc to read from the different properties

shossk commented 2 years ago

I think subclassing is a good idea too. I'm sure I can help you implement and tweak methods and such once you get to a certain level of subclassing.

The sending part is the same as the method of executeQueuedUpdates, so just send the status part.

花粉(pollen)

json
```JSON { "status": [{ "valueBinary": { "code": "010100001300000000000000000000000000000000000000000000" }, "statusCode": "F3", "valueType": "valueBinary" }] } ```

効果実感(realization of effects)

json
```JSON { "status": [{ "valueBinary": { "code": "010100004000000000000000000000000000000000000000000000" }, "statusCode": "F3", "valueType": "valueBinary" }] } ```

おまかせ(AI auto)

json
```JSON { "status": [{ "valueBinary": { "code": "010100002000000000000000000000000000000000000000000000" }, "statusCode": "F3", "valueType": "valueBinary" }] } ```

自動(auto)

json
```JSON { "status": [{ "valueBinary": { "code": "010100001000000000000000000000000000000000000000000000" }, "statusCode": "F3", "valueType": "valueBinary" }] } ```

おやすみ(night)

json
```JSON { "status": [{ "valueBinary": { "code": "010100001100000000000000000000000000000000000000000000" }, "statusCode": "F3", "valueType": "valueBinary" }] } ```

静音(silent)

json
```JSON { "status": [{ "valueBinary": { "code": "010100001400000000000000000000000000000000000000000000" }, "statusCode": "F3", "valueType": "valueBinary" }] } ```

中(medium)

json
```JSON { "status": [{ "valueBinary": { "code": "010100001500000000000000000000000000000000000000000000" }, "statusCode": "F3", "valueType": "valueBinary" }] } ```

強(high)

json
```JSON { "status": [{ "valueBinary": { "code": "010100001600000000000000000000000000000000000000000000" }, "statusCode": "F3", "valueType": "valueBinary" }] } ```

humidification off

json
```JSON { "status": [{ "valueBinary": { "code": "000900000000000000000000000000000000000000000000000000" }, "statusCode": "F3", "valueType": "valueBinary" }] } ```

humidification on

json
```JSON { "status": [{ "valueBinary": { "code": "000900000000000000000000000000FF0000000000000000000000" }, "statusCode": "F3", "valueType": "valueBinary" }] } ```

power off

json
```JSON { "status": [{ "valueSingle": { "code": "31" }, "statusCode": "80", "valueType": "valueSingle" }, { "valueBinary": { "code": "000300000000000000000000000000000000000000000000000000" }, "statusCode": "F3", "valueType": "valueBinary" }] } ```

power on

json
```JSON { "status": [{ "valueSingle": { "code": "30" }, "statusCode": "80", "valueType": "valueSingle" }, { "valueBinary": { "code": "00030000000000000000000000FF00000000000000000000000000" }, "statusCode": "F3", "valueType": "valueBinary" }] } ```

Another feature that air conditioners do not have is a part that displays the status of the air. I'd like you to get it for homebridge :)

This is probably it.


# smell
(I have no idea why there are two.)

F2 -> line 30:31
F2 -> line 36:37 

# PM2.5

F2 -> line 36:37
dvcrn commented 2 years ago

Thanks for providing! Looks good

So it's writing mainly to 運転状態詳細3 (F3)

      {
        "statusName": "運転状態詳細3",
        "statusCode": "F3",
        "get": true,
        "set": true,
        "inf": true,
        "valueType": "valueBinary",
        "valueSingle": null,
        "valueRange": null
      },

eg: Humidification Off: 000900000000000000000000000000000000000000000000000000 Humidification On: 000900000000000000000000000000FF0000000000000000000000

(Humidification setting is likely 0009, and FF/00 defines state)

It looks simple to implement these actions on first look...

I will see when I can find some time to setup the subclassing, but you can also just do

class Purifier extends Device

and ignore all of the existing methods in Device, and just focus on implementing things specific to your purifier. So we can build a quick WIP to test, and split it properly later

For setting the binary data you can see how I did it with State8 here - https://github.com/dvcrn/cocoro-sdk/blob/master/src/state.ts#L21 and

shossk commented 2 years ago

Thank you for waiting.

I've committed the temporary submodularization.

Sorry for the messy writing style... (I want to be able to write neatly in typescript like you do) I hope you can put it together nicely.

If you have any questions, please let me know.