『瀚思彼岸』» 智能家居技术论坛

 找回密码
 立即注册
查看: 62792|回复: 29

[经验分享] 造梦者Dream-maker新风机接入HA经验分享

[复制链接]

2

主题

17

帖子

130

积分

论坛技术达人

积分
130
金钱
108
HASS币
20
发表于 2019-4-16 22:58:25 | 显示全部楼层 |阅读模式
本帖最后由 qgy18 于 2019-4-16 22:58 编辑

自从前些时候在树莓派上搭了一个 HA,就玩得乐此不疲,家里能接入的设备都接入了,唯独造梦者 Dream-maker 新风机,由于太小众,也没有公开的接入组件,论坛搜了下,甚至都没人提到它。

我不懂物联网协议,但我知道既然这个新风机提供了手机 APP 控制端,那么从网络请求入手就是很好的一个突破口了。

实际要做的很简单:
  • 对手机 APP 控制软件进行抓包,分析获取数据(Sensor)和控制新风机(Service)所需接口及参数;
  • 实现一个 HA 的 Custom Components,让设备继承自 FanEntity,控制框架基本都有了,实现具体逻辑就行(通过 Http Client);
  • 写一个 Package,准备面板所需参数及控制组件;


考虑到本文仅提供思路,不展开讲,放几张实际成果图(使用原生组件)以及关键代码(删掉了 config 及参数验证部分)。用这种方法,大部分支持手机控制的小众硬件应该都可以融入到 HA 大家庭,统一接受你的指挥。

1.jpg 2.jpg 3.jpg

import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv

from homeassistant.components.fan import (FanEntity, DOMAIN, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import Throttle

from datetime import timedelta

_LOGGER = logging.getLogger(__name__)

SCAN_INTERVAL = timedelta(seconds=20)

PRODUCT_ID = 'xxx'
DEVICE_ID = 'xxx'
APP_KEY = 'xxx'

SPEED_MAX = 'max'
def _get_speed_word(speed):
    if speed == 0:
        return SPEED_OFF
    elif 0 < speed <= 20:
        return SPEED_LOW
    elif 20 < speed <= 30:
        return SPEED_MEDIUM
    elif 30 < speed <= 40:
        return SPEED_HIGH
    elif 40 < speed:
        return SPEED_MAX

def _get_speed_number(speed_word):
    if speed_word == SPEED_OFF:
        return 0
    elif speed_word == SPEED_LOW:
        return 10
    elif speed_word == SPEED_MEDIUM:
        return 30
    elif speed_word == SPEED_HIGH:
        return 40
    elif speed_word == SPEED_MAX:
        return 50

def setup_platform(hass, config, add_entities_callback, discovery_info=None):
    add_entities_callback([DreamMaker(hass, config)])

class DreamMaker(FanEntity):
    def __init__(self, hass, config):
        self._hass = hass
        self._config = config

        self._name = 'Dream Maker Fresh Air System'

        self._speed = SPEED_OFF
        self._state_attrs = {
            'humidity' : None,
            'temperature' : None,
            'aqi' : None,
            'aqi_out' : None,
            'co2' : None,
        }

        self.async_update = Throttle(SCAN_INTERVAL)(self._async_update)

    @property
    def name(self):
        return self._name

    @property
    def speed(self):
        return self._speed

    @property
    def speed_list(self):
        return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, SPEED_MAX]
    
    @property
    def device_state_attributes(self):
        return self._state_attrs

    async def async_turn_on(self, speed: str=None, **kwargs) -> None:
        if(speed is None):
            speed = SPEED_MEDIUM

        await self.async_set_speed(speed)

    async def async_turn_off(self, **kwargs) -> None:
        await self.async_set_speed(SPEED_OFF)

    async def async_set_speed(self, speed: str) -> None:
        old_speed = self._speed
        self._speed = speed

        if speed == old_speed:
            return

        if speed == SPEED_OFF:
            # power off
            await self._request({"action":4,"data":{"power":0, "source":"manual"},"resource_id":9001,"version":"zeico_2.0.0"})
        else:
            if old_speed == SPEED_OFF:
                await self._request({"action":4,"data":{"power":1, "source":"manual"},"resource_id":9001,"version":"zeico_2.0.0"})

            speed_num = _get_speed_number(speed)
            await self._request({"action":4,"data":{"fanspeed":speed_num},"resource_id":9001,"version":"zeico_2.0.0"})

    async def _async_update(self):
        data = await self._request()
        self._speed = _get_speed_word(data['fanspeed'])

        self._state_attrs.update({
            "humidity" : data['rh'],
            "temperature" : data['temp'],
            "aqi" : data['pm25'],
            "aqi_out" : data['pm25_out'],
            "co2" : data['co2'],
        })

    async def _request(self, post_data=None):
        websession = async_get_clientsession(self._hass, False)

        method = 'get' if post_data is None else 'post'
        url = 'https://api.dream-maker.com:8444/APIServer/v1/control/product/%s/device/%s' % (PRODUCT_ID, DEVICE_ID)
       
        headers = {
            "app_key": APP_KEY
        }

        async with websession.request(method, url, json=post_data,headers=headers) as resp:
            resp_data = (await resp.json())['data']
            return resp_data

fan:
  - platform: dream_maker

sensor:
  - platform: template
    sensors:
      dream_maker_fresh_air_system_temperature:
        value_template: '{{ states.fan.dream_maker_fresh_air_system.attributes["temperature"]|round(2) }}'
        device_class: temperature
        unit_of_measurement: '°C'
      dream_maker_fresh_air_system_co2:
        value_template: '{{ states.fan.dream_maker_fresh_air_system.attributes["co2"] }}'
        unit_of_measurement: 'ppm'
      dream_maker_fresh_air_system_rh:
        value_template: '{{ states.fan.dream_maker_fresh_air_system.attributes["humidity"]|round(2) }}'
        device_class: humidity
        unit_of_measurement: '%'
      dream_maker_fresh_air_system_pm25:
        value_template: '{{ states.fan.dream_maker_fresh_air_system.attributes["aqi"] }}'
        unit_of_measurement: 'μg/m³'
      dream_maker_fresh_air_system_pm25_out:
        value_template: '{{ states.fan.dream_maker_fresh_air_system.attributes["aqi_out"] }}'
        unit_of_measurement: 'μg/m³'

input_select:
  dream_maker_set_speed:
    name: 风量
    icon: mdi:fan
    options:
      - "关闭"
      - "小"
      - "中"
      - "大"
      - "全速"
     
automation:
  - alias: dream_maker_get_speed
    initial_state: true
    trigger:
      platform: time_pattern
      seconds: /5
    action:
      service: input_select.select_option
      data_template:
        entity_id: input_select.dream_maker_set_speed
        option: >
          {% if states.fan.dream_maker_fresh_air_system.attributes.speed == "off" %}关闭
          {% elif states.fan.dream_maker_fresh_air_system.attributes.speed == "low" %}小
          {% elif states.fan.dream_maker_fresh_air_system.attributes.speed == "medium" %}中
          {% elif states.fan.dream_maker_fresh_air_system.attributes.speed == "high" %}大
          {% elif states.fan.dream_maker_fresh_air_system.attributes.speed == "max" %}全速
          {% endif %}

  - alias: dream_maker_set_speed
    initial_state: true
    trigger:
      platform: state
      entity_id: input_select.dream_maker_set_speed
    action:
      service: fan.set_speed
      data_template:
        entity_id: fan.dream_maker_fresh_air_system
        speed: >
          {% if states.input_select.dream_maker_set_speed.state == "关闭" %}off
          {% elif states.input_select.dream_maker_set_speed.state == "小" %}low
          {% elif states.input_select.dream_maker_set_speed.state == "中" %}medium
          {% elif states.input_select.dream_maker_set_speed.state == "大" %}high
          {% elif states.input_select.dream_maker_set_speed.state == "全速" %}max
          {% endif %}

homeassistant:
  customize:
    sensor.dream_maker_fresh_air_system_co2:
      friendly_name: CO₂
    sensor.dream_maker_fresh_air_system_pm25:
      icon: mdi:blur
      friendly_name: PM2.5
    sensor.dream_maker_fresh_air_system_pm25_out:
      icon: mdi:blur
      friendly_name: PM2.5_Outdoor
    sensor.dream_maker_fresh_air_system_temperature:
      friendly_name: Temperature
    sensor.dream_maker_fresh_air_system_rh:
      friendly_name: Humidity
    fan.dream_maker_fresh_air_system:
      friendly_name: '新风机'

评分

参与人数 1金钱 +20 HASS币 +20 收起 理由
+ 20 + 20 膜拜大神!

查看全部评分

回复

使用道具 举报

123

主题

4661

帖子

1万

积分

管理员

囧死

Rank: 9Rank: 9Rank: 9

积分
16410
金钱
11664
HASS币
45
发表于 2019-4-16 23:08:41 | 显示全部楼层
一贴就技达走起,就这么任性!
回复

使用道具 举报

123

主题

4661

帖子

1万

积分

管理员

囧死

Rank: 9Rank: 9Rank: 9

积分
16410
金钱
11664
HASS币
45
发表于 2019-4-16 23:14:17 | 显示全部楼层
不过还是希望代码能完整放出来,放部分不是技达该有的风格啊
回复

使用道具 举报

2

主题

17

帖子

130

积分

论坛技术达人

积分
130
金钱
108
HASS币
20
 楼主| 发表于 2019-4-16 23:28:38 | 显示全部楼层
Jones 发表于 2019-4-16 23:14
不过还是希望代码能完整放出来,放部分不是技达该有的风格啊

J 大,其实那就是全部代码,config 及参数校验部分我没写。因为给自己用,config 就硬编码在代码里,没抽取为配置。如果有人有需要,我再整理下。
回复

使用道具 举报

123

主题

4661

帖子

1万

积分

管理员

囧死

Rank: 9Rank: 9Rank: 9

积分
16410
金钱
11664
HASS币
45
发表于 2019-4-16 23:30:05 | 显示全部楼层
qgy18 发表于 2019-4-16 23:28
J 大,其实那就是全部代码,config 及参数校验部分我没写。因为给自己用,config 就硬编码在代码里,没抽 ...

建议整理下,方便小白使用,谢谢
回复

使用道具 举报

0

主题

71

帖子

432

积分

中级会员

Rank: 3Rank: 3

积分
432
金钱
361
HASS币
0
发表于 2019-4-17 07:51:21 | 显示全部楼层
膜拜大神。希望有完整的代码,这样小白可以看的更清楚  嘿嘿
https://tool.lu/netcard/
回复

使用道具 举报

12

主题

397

帖子

2289

积分

金牌会员

Rank: 6Rank: 6

积分
2289
金钱
1892
HASS币
10
发表于 2019-4-17 08:34:20 | 显示全部楼层
技术达人
回复

使用道具 举报

75

主题

1976

帖子

8179

积分

元老级技术达人

积分
8179
金钱
6153
HASS币
430

活跃会员教程狂人

发表于 2019-4-17 16:19:19 | 显示全部楼层
顶技术大佬,学习学习。

拜托帮忙修复一下M大当年做的asus组件呗。很需要。

https://bbs.hassbian.com/thread-3697-1-1.html
所有过往,皆为序章。
回复

使用道具 举报

15

主题

335

帖子

2454

积分

金牌会员

Rank: 6Rank: 6

积分
2454
金钱
2119
HASS币
10
发表于 2019-4-17 20:58:26 | 显示全部楼层
膜拜大神,买了大土豆新风也接入不了,希望大神可以分享一下详细教程学习一下
回复

使用道具 举报

0

主题

58

帖子

283

积分

中级会员

Rank: 3Rank: 3

积分
283
金钱
225
HASS币
0
发表于 2019-4-17 22:00:00 | 显示全部楼层
人才啊。。。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Hassbian

GMT+8, 2024-11-23 23:20 , Processed in 0.058104 second(s), 36 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表