├── .env.example
├── .gitignore
├── README.md
├── __init__.py
├── images
├── command_transform.jpeg
├── light.jpg
├── panasonic.jpeg
├── running_mode.jpg
└── toplogy.jpg
├── panasonic.py
├── requirements.txt
├── server.py
├── setup.sh
└── supervisor.conf
/.env.example:
--------------------------------------------------------------------------------
1 | PANASONIC_SSID=
2 | PANASONIC_ACW_TC=
3 | PANASONIC_USER_ID=
4 |
5 | PANASONIC_DEVICE_ID=
6 | PANASONIC_TOKEN=
7 |
8 | PANASONIC_AIR_CONDITION_DEVICE_ID=
9 | PANASONIC_AIR_CONDITION_TOKEN=
10 |
11 | BEMFA_CLIENT_ID=
12 | BEMFA_TOPICS=xxx001,xxx002
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | venv/
2 | .env
3 | __pycache__/
4 | *.pyc
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A stupid way to extend miot/xiaoai.
2 |
3 | ### Demo for Panasonic Bath Heater `FV-RB20VL1`
4 |
5 |
6 |
7 | 1. 逆向 `Panasonic Smart China`,获得控制浴霸的请求信息(HTTP 请求),详见 `apps/panasonic.py`;
8 |
9 | 2. 通过米家 APP 添加 `巴法`,以支持第三方设备,实现监听巴法云的 MQTT
10 | 消息,巴法云目前不支持浴霸,需要使用其他设备进行替代并映射,因灯与运行模式完全独立,建议实现两个设备,详见 `server.py`;
11 |
12 |
13 | 3. 为了更好的体验,需要在小爱音箱 APP 中,创建训练,转换浴霸指令为巴法云支持的指令。
14 |
15 |
16 | ### Installation:
17 |
18 | `copy .env.example .env`
19 |
20 | `python3 -m venv venv`
21 |
22 | `source venv/bin/activate`
23 |
24 | `pip install -r requirements.txt`
25 |
26 | ### Run server:
27 |
28 | `python apps/server.py`
29 |
30 | ### Changelog
31 |
32 | 2024-06-29
33 |
34 | 1. 重构代码,新增松下空调
35 | 2. replace config.py with dotenv
36 | 3. 小爱训练计划中已支持第三方设备(巴法云)展示与命令映射,配置体验稍有提升
37 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamsk/miot-http-support/16fe488664a04417ad779d86ca9c3738961c5453/__init__.py
--------------------------------------------------------------------------------
/images/command_transform.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamsk/miot-http-support/16fe488664a04417ad779d86ca9c3738961c5453/images/command_transform.jpeg
--------------------------------------------------------------------------------
/images/light.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamsk/miot-http-support/16fe488664a04417ad779d86ca9c3738961c5453/images/light.jpg
--------------------------------------------------------------------------------
/images/panasonic.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamsk/miot-http-support/16fe488664a04417ad779d86ca9c3738961c5453/images/panasonic.jpeg
--------------------------------------------------------------------------------
/images/running_mode.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamsk/miot-http-support/16fe488664a04417ad779d86ca9c3738961c5453/images/running_mode.jpg
--------------------------------------------------------------------------------
/images/toplogy.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamsk/miot-http-support/16fe488664a04417ad779d86ca9c3738961c5453/images/toplogy.jpg
--------------------------------------------------------------------------------
/panasonic.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import requests
4 | from dotenv import load_dotenv
5 |
6 | load_dotenv()
7 |
8 |
9 | class Panasonic(object):
10 | deviceId = ''
11 | token = ''
12 | url = ''
13 | params = {}
14 | headers = {
15 | 'Host': 'app.psmartcloud.com',
16 | 'Accept': 'application/json, text/javascript, */*; q=0.01',
17 | 'X-Requested-With': 'XMLHttpRequest',
18 | 'Accept-Language': 'en-US,en;q=0.9',
19 | # 'Accept-Encoding': 'gzip, deflate, br',
20 | 'Content-Type': 'application/json',
21 | 'Origin': 'https://app.psmartcloud.com',
22 | 'xtoken': f'SSID={os.getenv("PANASONIC_SSID", "")}',
23 | 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148',
24 | # 'Referer': '', # no need
25 | # 'Content-Length': '468', # no need
26 | 'Connection': 'keep-alive',
27 | }
28 | cookies = {
29 | 'acw_tc': os.getenv("PANASONIC_ACW_TC", ""),
30 | }
31 |
32 | def update_params(self, action):
33 | pass
34 |
35 | def trigger(self):
36 | data = {
37 | "id": 1,
38 | "usrId": os.getenv("PANASONIC_USER_ID", ""),
39 | "deviceId": self.deviceId,
40 | "token": self.token,
41 | "params": self.params,
42 | }
43 | # import pdb;
44 | # pdb.set_trace()
45 | response = requests.post(self.url, headers=self.headers, cookies=self.cookies, json=data, verify=False)
46 | print(response.json())
47 |
48 | def control(self, action=''):
49 | self.update_params(action)
50 | self.trigger()
51 |
52 |
53 | class BathHeater(Panasonic):
54 | deviceId = os.getenv("PANASONIC_DEVICE_ID", "")
55 | token = os.getenv("PANASONIC_TOKEN", "")
56 | url = 'https://app.psmartcloud.com/App/ADevSetStatusInfoFV54BA1C'
57 | params = {
58 | "DIYnextwindDirectionSet": 255,
59 | "DIYnextwindKindSet": 255,
60 | "warmTempset": 255,
61 | "DIYnextWarmTempset": 255,
62 | "DIYnextStepNo": 2,
63 | "windDirectionSet": 255,
64 | "timeSet": 3, # 默认15分钟
65 | "DIYnextRunningMode": 255,
66 | "windKindSet": 255,
67 | "DIYnextTimeSet": 255,
68 | }
69 |
70 | def update_params(self, action):
71 | running_modes = {
72 | '打开灯': 1,
73 | '关闭灯': 0,
74 | '换气': 38,
75 | '取暖': 37,
76 | '凉干燥': 40,
77 | '热干燥': 42,
78 | '待机': 32,
79 | }
80 | light_set = running_modes[action] if action in ['打开灯', '关闭灯'] else 255
81 | running_mode = running_modes[action] if action in ['换气', '取暖', '凉干燥', '热干燥', '待机'] else 255
82 | self.params['runningMode'] = running_mode
83 | self.params['lightSet'] = light_set
84 |
85 |
86 | class AirCondition(Panasonic):
87 | deviceId = os.getenv("PANASONIC_AIR_CONDITION_DEVICE_ID", "")
88 | token = os.getenv("PANASONIC_AIR_CONDITION_TOKEN", "")
89 | url = 'https://app.psmartcloud.com/App/ACDevSetStatusInfoAW'
90 | params = {
91 | 'runMode': 3,
92 | 'forceRunning': 0,
93 | 'remoteForbidMode': 0,
94 | 'remoteMode': 0,
95 | 'setTemperature': 54,
96 | 'setHumidity': 128,
97 | 'windSet': 3,
98 | 'exchangeWindSet': 0,
99 | 'portraitWindSet': 15,
100 | 'orientationWindSet': 13,
101 | 'nanoeG': 0,
102 | 'nanoe': 0,
103 | 'ecoMode': 0,
104 | 'muteMode': 1,
105 | 'filterReset': 0,
106 | 'powerful': 0,
107 | 'powerfulMode': 0,
108 | 'thermoMode': 0,
109 | 'buzzer': 1,
110 | 'autoRunMode': 3,
111 | 'unusualPresent': 0,
112 | 'runForbidden': 0,
113 | 'inhaleTemperature': 27,
114 | 'outsideTemperature': 32,
115 | 'insideHumidity': 255,
116 | 'alarmCode': 'F093',
117 | 'nanoeModule': 0,
118 | 'TDWindModule': 0,
119 | }
120 |
121 | def update_params(self, action):
122 | running_modes = {
123 | 'on': 1,
124 | 'off': 0,
125 | }
126 | self.params['runStatus'] = running_modes[action]
127 |
128 |
129 | if __name__ == '__main__':
130 | device = BathHeater()
131 | device.control('打开灯')
132 | # device.control('关闭灯')
133 | exit()
134 | device = AirCondition()
135 | device.control('on')
136 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests
2 | paho-mqtt
3 | python-dotenv
4 |
--------------------------------------------------------------------------------
/server.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import paho.mqtt.client as mqtt
4 | from dotenv import load_dotenv
5 |
6 | from panasonic import AirCondition, BathHeater
7 |
8 | load_dotenv()
9 |
10 |
11 | def on_connect(client, userdata, flags, rc):
12 | print("Connected with result code " + str(rc))
13 | for topic in os.getenv("BEMFA_TOPICS", "").split(","):
14 | client.subscribe(topic)
15 |
16 |
17 | def on_message(client, userdata, msg):
18 | print(msg.topic + " " + str(msg.payload))
19 | if msg.topic == 'bathbully005':
20 | cmds = msg.payload.decode().split('#')
21 | running_mode = cmds[1] if len(cmds) > 1 else cmds[0]
22 | device = BathHeater()
23 | mappings = {
24 | 'on': '打开灯', # 打开空调 <-> 打开浴霸灯
25 | 'off': '关闭灯', # 关闭空调 <-> 关闭浴霸灯
26 | '3': '取暖', # 空调打开辅热模式 <-> 取暖
27 | '4': '换气', # 空调开启柔风模式 <-> 换气
28 | '5': '热干燥', # 空调开启干燥模式 <-> 热干燥
29 | '6': '待机', # 空调开启睡眠模式 <-> 浴霸待机
30 | '7': '凉干燥', # 空调开启节能模式 <-> 凉干燥
31 | }
32 | device.control(action=mappings[running_mode])
33 | if msg.topic == 'aircondition005':
34 | action = msg.payload.decode()
35 | device = AirCondition()
36 | device.control(action)
37 |
38 |
39 | client = mqtt.Client(client_id=os.getenv("BEMFA_CLIENT_ID", ""))
40 | client.on_connect = on_connect
41 | client.on_message = on_message
42 | client.connect("bemfa.com", 9501, 60)
43 | client.loop_forever()
44 |
--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------
1 | ln -s /data/www/miot-http-support/supervisor.conf /etc/supervisor/conf.d/miot-http-support.conf
2 |
--------------------------------------------------------------------------------
/supervisor.conf:
--------------------------------------------------------------------------------
1 | [program:miot-http-support]
2 | directory = /data/www/%(program_name)s
3 | command=/data/www/%(program_name)s/venv/bin/python server.py
4 | stdout_logfile_maxbytes=0
5 | stdout_logfile_backups = 0
6 | stdout_logfile = /data/logs/%(program_name)s/stdout.log
7 | stderr_logfile = /data/logs/%(program_name)s/stderr.log
8 |
--------------------------------------------------------------------------------