├── .gitattributes ├── pyscripts ├── icon.png ├── logo.png ├── Dockerfile ├── config.yaml └── run.sh ├── wenotify_tx ├── icon.png ├── Dockerfile ├── options.json ├── config.yaml ├── README.md └── wenotify.py ├── zhonghong_vrf ├── icon.png ├── logo.png ├── options.json ├── Dockerfile ├── config.yaml ├── README.md └── vrf.py ├── repository.json ├── README.md └── LICENSE /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /pyscripts/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xswxm/hassio-addons/HEAD/pyscripts/icon.png -------------------------------------------------------------------------------- /pyscripts/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xswxm/hassio-addons/HEAD/pyscripts/logo.png -------------------------------------------------------------------------------- /wenotify_tx/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xswxm/hassio-addons/HEAD/wenotify_tx/icon.png -------------------------------------------------------------------------------- /zhonghong_vrf/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xswxm/hassio-addons/HEAD/zhonghong_vrf/icon.png -------------------------------------------------------------------------------- /zhonghong_vrf/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xswxm/hassio-addons/HEAD/zhonghong_vrf/logo.png -------------------------------------------------------------------------------- /repository.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Adon's addons repository", 3 | "url": "https://github.com/xswxm/hassio-addons", 4 | "maintainer": "Adon Wang " 5 | } -------------------------------------------------------------------------------- /zhonghong_vrf/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "broker": "192.168.123.200", 3 | "port": 1883, 4 | "username": "mqtt", 5 | "password": "mqtt", 6 | "gateway": "192.168.123.251" 7 | } -------------------------------------------------------------------------------- /pyscripts/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7-alpine 2 | 3 | RUN apk update 4 | RUN apk add jq 5 | # RUN pip3 install requests paho-mqtt 6 | 7 | RUN mkdir /data 8 | ADD run.sh / 9 | RUN chmod a+x /run.sh 10 | 11 | CMD ["/run.sh"] -------------------------------------------------------------------------------- /wenotify_tx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7-alpine 2 | 3 | RUN mkdir /data 4 | #WORKDIR /data 5 | 6 | ADD wenotify.py / 7 | # ADD options.json /data 8 | 9 | RUN apk update 10 | RUN pip3 install requests 11 | 12 | CMD ["python3","./wenotify.py"] -------------------------------------------------------------------------------- /zhonghong_vrf/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7-alpine 2 | 3 | RUN mkdir /data 4 | #WORKDIR /data 5 | 6 | ADD vrf.py / 7 | #ADD options.json /data 8 | 9 | RUN apk update 10 | RUN pip3 install requests paho-mqtt 11 | 12 | CMD ["python3","./vrf.py"] -------------------------------------------------------------------------------- /wenotify_tx/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "api": "saylove", 3 | "key": "你的apikey", 4 | "push_time": "12:00:00", 5 | "corpid": "企业微信corpid", 6 | "corpsecret": "企业微信corpsecret", 7 | "agentid": "企业微信agentid", 8 | "touser": "@all", 9 | "http_proxy": "HTTP代理服务", 10 | "https_proxy": "HTTPS代理服务" 11 | } -------------------------------------------------------------------------------- /pyscripts/config.yaml: -------------------------------------------------------------------------------- 1 | name: "Python Scripts" 2 | version: "0.0.11" 3 | slug: "pyscripts" 4 | description: "基于python 3.7,运行位于/config/pyscripts/下的python脚本(文件夹可自定义)" 5 | url: "https://github.com/xswxm/hassio-addons/tree/main/pyscripts" 6 | arch: 7 | - aarch64 8 | - amd64 9 | - armhf 10 | - armv7 11 | - i386 12 | boot: auto 13 | options: 14 | apks: "" 15 | packages: "" 16 | path: "pyscripts" 17 | schema: 18 | apks: str 19 | packages: str 20 | path: str 21 | map: 22 | - config:rw -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hassio-addons 2 | 3 | addons for hassio 4 | 5 | - [pyscripts](https://github.com/xswxm/hassio-addons/tree/master/pyscripts): 6 | 7 | 基于python 3.7,运行位于/config/pyscripts/下的python脚本(文件夹可自定义) 8 | 9 | 10 | - [wenotify_tx](https://github.com/xswxm/hassio-addons/tree/master/wenotify_tx): 11 | 12 | 基于[天行数据](https://www.tianapi.com/) API的企业微信消息推送工具,可以实现每日情话、心灵鸡汤等等推送 13 | 14 | 15 | - [zhonghong_vrf](https://github.com/xswxm/hassio-addons/tree/master/zhonghong_vrf): 16 | 17 | 基于HTTP实现的中弘空调控制器 18 | 19 | -------------------------------------------------------------------------------- /zhonghong_vrf/config.yaml: -------------------------------------------------------------------------------- 1 | name: "Zhonghong VRF" 2 | version: "0.3.5" 3 | slug: "zhonghong_vrf" 4 | description: "HTTP based Zhonghong vrf controller" 5 | url: "https://github.com/xswxm/hassio-addons/tree/main/zhonghong_vrf" 6 | arch: 7 | - aarch64 8 | - amd64 9 | - armhf 10 | - armv7 11 | - i386 12 | boot: auto 13 | #image: xswxm/{arch}-addon-zhonghong_vrf 14 | options: 15 | broker: "192.168.123.10" 16 | port: "1883" 17 | username: "mqtt" 18 | password: "mqtt" 19 | gateway: "192.168.123.251" 20 | schema: 21 | broker: str 22 | port: str 23 | username: str 24 | password: str 25 | gateway: str 26 | -------------------------------------------------------------------------------- /zhonghong_vrf/README.md: -------------------------------------------------------------------------------- 1 | # Home Assistant Add-on: zhonghong_vrf 2 | 3 | ## 关于 4 | 5 | [zhonghong_vrf](https://github.com/xswxm/hassio-addons/blob/main/zhonghong_vrf/README.md)是一个基于HTTP控制中弘空调的应用。 6 | 7 | ## 版本更新 8 | 0.3.5: 9 | - 代码优化 10 | 0.3.4: 11 | - 代码优化 12 | 0.3.0: 13 | - 代码重构,MQTT服务可找到“ZhongHong VRF”的新设备 14 | - 去除configuration.yaml 添加提示,不用再手动编辑configuration.yaml了 15 | 0.2.0: 16 | - 增加configuration.yaml 添加提示 17 | - 日志增加时间标记 18 | 19 | 20 | ## 配置 21 | 22 | #### Option `broker` 23 | 24 | mqtt服务器 25 | 26 | #### Option `port` 27 | 28 | mqtt服务器端口 29 | 30 | #### Option `username` 31 | 32 | mqtt服务器用户名 33 | 34 | #### Option `password` 35 | 36 | mqtt服务器密码 37 | 38 | #### Option `gateway` 39 | 40 | 中弘网关IP地址 -------------------------------------------------------------------------------- /wenotify_tx/config.yaml: -------------------------------------------------------------------------------- 1 | name: "企业微信消息推送" 2 | version: "0.0.5" 3 | slug: "wenotify_tianxin" 4 | description: "使用企业号推送微信消息,基于天行数据api" 5 | url: "https://github.com/xswxm/hassio-addons/" 6 | arch: 7 | - aarch64 8 | - amd64 9 | - armhf 10 | - armv7 11 | - i386 12 | boot: auto 13 | options: 14 | api: "saylove" 15 | key: "你的apikey" 16 | push_time: "12:00:00" 17 | corpid: "企业微信corpid" 18 | corpsecret: "企业微信corpsecret" 19 | agentid: "企业微信agentid" 20 | touser: "@all" 21 | http_proxy: "HTTP代理服务" 22 | https_proxy: "HTTPS代理服务" 23 | schema: 24 | api: str 25 | key: str 26 | push_time: str 27 | corpid: str 28 | corpsecret: str 29 | agentid: str 30 | touser: str 31 | http_proxy: str 32 | https_proxy: str -------------------------------------------------------------------------------- /pyscripts/run.sh: -------------------------------------------------------------------------------- 1 | CONFIG_PATH=/data/options.json 2 | 3 | apks=$(jq --raw-output ".apks" $CONFIG_PATH) 4 | packages=$(jq --raw-output ".packages" $CONFIG_PATH) 5 | path=$(jq --raw-output ".path" $CONFIG_PATH) 6 | 7 | 8 | IFS=',' 9 | # install system packages 10 | for apk in ${apks} 11 | do 12 | echo 'RUN apk add '${apk//[[:blank:]]/} 13 | apk add ${apk//[[:blank:]]/} 14 | done 15 | 16 | # install python packages 17 | for package in ${packages} 18 | do 19 | echo 'RUN pip3 install '${package//[[:blank:]]/} 20 | pip3 install ${package//[[:blank:]]/} 21 | done 22 | 23 | # run python scripts 24 | IFS=$' \t\n' 25 | cd /config/${path} 26 | filenames=$(ls *.py) 27 | for file in ${filenames};do 28 | echo 'RUN python3 '${file} 29 | python3 ${file} & 30 | done 31 | 32 | while true 33 | do 34 | sleep 3600 35 | done -------------------------------------------------------------------------------- /wenotify_tx/README.md: -------------------------------------------------------------------------------- 1 | # Home Assistant Add-on: wenotify_tx 2 | 3 | ## 关于 4 | 5 | [wenotify_tx](https://github.com/xswxm/hassio-addons/blob/main/wenotify_tx/README.md)是一个基于[天行数据](https://www.tianapi.com/) API的企业微信消息推送工具,可以实现每日情话、心灵鸡汤等等推送。 6 | 7 | ## 版本更新 8 | 0.0.5: 9 | - 首版发布 10 | 11 | ## 配置 12 | 13 | #### Option `api` 14 | 15 | 接口名称,如果接口地址为 "https://apis.tianapi.com/pcterm/index " ,则此处为"pcterm" 16 | 17 | #### Option `key` 18 | 19 | 你的key 20 | 21 | #### Option `push_time` 22 | 23 | 每日推送时间,如"12:00:00",则每日中午12点推送消息 24 | 25 | #### Option `corpid` 26 | 27 | 企业微信corpid 28 | 29 | #### Option `corpsecret` 30 | 31 | 企业微信corpsecret 32 | 33 | #### Option `agentid` 34 | 35 | 企业微信agentid 36 | 37 | #### Option `touser` 38 | 39 | 发送对象,"@all" 则为所有对象 40 | 41 | #### Option `http_proxy` 42 | 43 | HTTP代理服务器地址,防止路由器ip变更重复添加企业微信应用的ip白名单 44 | 45 | #### Option `https_proxy` 46 | 47 | HTTPS代理服务器地址,防止路由器ip变更重复添加企业微信应用的ip白名单 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Adon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /wenotify_tx/wenotify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import http.client, urllib 5 | import time 6 | import requests 7 | import json 8 | import logging 9 | 10 | logging.basicConfig(level=logging.INFO) 11 | 12 | CONFIG = {} 13 | 14 | def get_config(): 15 | config = {} 16 | CONFIG_PATH = "/data/options.json" 17 | with open(CONFIG_PATH) as fp: 18 | config = json.load(fp) 19 | logging.info(" {0}: Config loaded: {1}".format(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())), config)) 20 | return config 21 | 22 | def getQinghua(): 23 | conn = http.client.HTTPSConnection('api.tianapi.com') #接口域名 24 | params = urllib.parse.urlencode({'key':CONFIG['key']}) 25 | headers = {'Content-type':'application/x-www-form-urlencoded'} 26 | conn.request('POST','/'+ CONFIG['api'] +'/index',params,headers) 27 | res = conn.getresponse() 28 | return json.loads(res.read().decode('utf-8')) 29 | 30 | def updateQinghua(): 31 | data = getQinghua() 32 | if data['code'] == 200: 33 | return data['newslist'][0]['content'] 34 | elif is_between_time(): # 如还在12:00 内吗,等待10s再获取一次 35 | time.sleep(10) 36 | updateQinghua() 37 | 38 | # 定时12:00~12:01之间发一次消息 39 | def is_between_time(): 40 | now = time.strftime('%Y-%m-%d %H:%M:%S') 41 | t = now[0:11] + CONFIG['push_time'] 42 | t1 = time.strptime(t, "%Y-%m-%d %H:%M:%S") 43 | t2 = time.mktime(t1) 44 | if t2 <= time.time() <= t2 + 60: 45 | return True 46 | else: 47 | return False 48 | 49 | # 微信消息提醒模块 50 | class WeWorkNotify(): 51 | def __init__(self, corpid, corpsecret, agentId, touser, http_proxy, https_proxy): 52 | self._corpid = corpid 53 | self._corpsecret = corpsecret 54 | self._agentid = agentId 55 | self._touser = touser 56 | self._proxies = { "http": http_proxy, 'https': https_proxy } 57 | self._token = "" 58 | self._token_expire_time = 0 59 | self._header = {} 60 | 61 | def _get_access_token(self): 62 | url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken' 63 | values = { 64 | "corpid": self._corpid, 65 | "corpsecret": self._corpsecret, 66 | } 67 | req = requests.post(url, params=values, headers=self._header, proxies=self._proxies) 68 | data = json.loads(req.text) 69 | if data["errcode"] != 0: 70 | logging.info(" {0}: 获取企业微信 Access token 失败 `{1}`".format(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())), data)) 71 | self._token_expire_time = time.time() + data["expires_in"] 72 | return data["access_token"] 73 | 74 | def get_access_token(self): 75 | if time.time() < self._token_expire_time: 76 | return self._token 77 | else: 78 | self._token = self._get_access_token() 79 | return self._token 80 | 81 | def send_msg(self, message): 82 | send_url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=' + self.get_access_token() 83 | send_values = { 84 | "touser": self._touser, 85 | "msgtype": "text", 86 | "agentid": self._agentid, 87 | "text": { 88 | "content": message 89 | }, 90 | "safe": "0" 91 | } 92 | send_msges=(bytes(json.dumps(send_values), 'utf-8')) 93 | response = requests.post(send_url, send_msges, proxies=self._proxies).json() 94 | if response["errcode"] != 0: 95 | logging.info(" {0}: 发送消息失败 `{1}`".format(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())), response)) 96 | else: 97 | logging.info(" {0}: 发送消息成功 `{1}`".format(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())), send_msges)) 98 | 99 | 100 | if __name__ == '__main__': 101 | CONFIG = get_config() 102 | wx = WeWorkNotify(CONFIG['corpid'], CONFIG['corpsecret'], CONFIG['agentid'], CONFIG['touser'], CONFIG['http_proxy'], CONFIG['https_proxy']) 103 | 104 | while True: 105 | if is_between_time(): 106 | try: 107 | qinghua = updateQinghua() 108 | wx.send_msg(qinghua) 109 | except Exception as e: 110 | logging.info(" {0}: Exception `{1}`".format(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())), e)) 111 | # 每一分钟检测一次,防止cpu过载 112 | time.sleep(60) 113 | -------------------------------------------------------------------------------- /zhonghong_vrf/vrf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import requests 4 | import json 5 | import logging 6 | import time 7 | from paho.mqtt import client as mqtt_client 8 | 9 | logging.basicConfig(level=logging.INFO) 10 | 11 | STATES = {'on':{0:'OFF', 1:'ON','OFF':0,'ON':1}, 12 | 'mode':{0:'off',1:'cool',2:'dry',4:'fan_only',8:'heat','off':0,'cool':1,'dry':2,'fan_only':4,'heat':8}, 13 | 'fan':{0:'auto',1:'high',2:'medium',4:'low',6:'silent','auto':0,'high':1,'medium':2,'low':4,'silent':8}} 14 | CONFIG = {} 15 | acs = [] 16 | 17 | def setConfig(): 18 | global CONFIG 19 | CONFIG = {} 20 | CONFIG['broker'] = "192.168.123.10" 21 | CONFIG['port'] = 1883 22 | CONFIG['gateway'] = "192.168.123.251" 23 | CONFIG['username'] = "mqtt" 24 | CONFIG['password'] = "mqtt" 25 | 26 | def loadConfig(CONFIG_PATH = '/data/options.json'): 27 | global CONFIG 28 | CONFIG = {} 29 | with open(CONFIG_PATH) as fp: 30 | CONFIG = json.load(fp) 31 | logging.info (" {0}: Config loaded: {1}".format(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())), CONFIG)) 32 | 33 | def connect_mqtt() -> mqtt_client: 34 | global CONFIG 35 | def on_connect(client, userdata, flags, rc): 36 | if rc == 0: 37 | logging.info(" {0}: Connected to MQTT Broker!".format(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())))) 38 | else: 39 | logging.info(" {0}: Failed to connect, return code {1}\n".format(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))), rc) 40 | client_id = time.strftime('%Y%m%d%H%M%S',time.localtime(time.time())) 41 | client = mqtt_client.Client(client_id) 42 | client.username_pw_set(username=CONFIG['username'], password=CONFIG['password']) 43 | client.on_connect = on_connect 44 | client.connect(CONFIG['broker'], int(CONFIG['port'])) 45 | return client 46 | 47 | # subscribe mqtt topic and receive msg 48 | def subscribe(client: mqtt_client): 49 | def on_message(client, userdata, msg): 50 | logging.info(" {0}: Received `{1}` from `{2}` topic".format(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())), msg.payload.decode(), msg.topic)) 51 | 52 | try: 53 | if "homeassistant/climate/zhonghong/" in msg.topic: 54 | object_id = msg.topic.split('/')[3] 55 | 56 | idx = int(object_id.split('_')[1]) 57 | global acs 58 | for i in range(len(acs)): 59 | if acs[i]['idx'] == idx: 60 | break 61 | ac = {} 62 | ac['idx'] = idx 63 | ac['on'] = acs[i]['on'] 64 | ac['mode'] = acs[i]['mode'] 65 | ac['tempSet'] = acs[i]['tempSet'] 66 | ac['fan'] = acs[i]['fan'] 67 | if msg.topic.split('/')[-2] == 'temp': 68 | ac['tempSet'] = int(float(msg.payload.decode())) 69 | else: 70 | global STATES 71 | ac[msg.topic.split('/')[-2]] = STATES[msg.topic.split('/')[-2]][msg.payload.decode()] 72 | if msg.topic.split('/')[-2] == 'mode' and msg.payload.decode() == 'off': 73 | ac['on'] = 0 74 | else: 75 | ac['on'] = 1 76 | setAC(ac) 77 | 78 | elif msg.topic == "homeassistant/zhonghong/initialize": 79 | initializeClimates(client) 80 | except Exception as e: 81 | logging.info(" {0}: Exception `{1}`".format(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())), e)) 82 | client.on_message = on_message 83 | 84 | # publish state for an air conditioner 85 | def publish(client, topic, msg): 86 | result = client.publish(topic, msg) 87 | status = result[0] 88 | if status == 0: 89 | logging.info(" {0}: Publish `{1} to `{2}` topic".format(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())), msg, topic)) 90 | else: 91 | logging.info(" {0}: Failed to send message to topic {1}".format(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())), topic)) 92 | 93 | # get states for all air conditioners 94 | def getACList(): 95 | # global acs_sample 96 | # return acs_sample['unit'] 97 | 98 | global CONFIG 99 | api = 'http://{0}/cgi-bin/api.html?f=17&p={1}' 100 | i = 0 101 | acs_temp = [] 102 | while True: 103 | try: 104 | r = requests.get(api.format(CONFIG['gateway'],i), auth=('admin',''), proxies = {'http': None, 'https': None}) 105 | msg = json.loads(r.text) 106 | if msg['err'] == 0 and len(msg['unit']) > 0: 107 | for m in msg['unit']: 108 | acs_temp.append(m) 109 | else: 110 | break 111 | except Exception as e: 112 | r = str(e) 113 | if "('Connection aborted.', BadStatusLine('" in r: 114 | msg = json.loads(r[39:-3]) 115 | if msg['err'] == 0 and len(msg['unit']) > 0: 116 | for m in msg['unit']: 117 | acs_temp.append(m) 118 | else: 119 | break 120 | i = i + 1 121 | return acs_temp 122 | 123 | # set values for a air conditioner 124 | def setAC(ac): 125 | global CONFIG 126 | api = "http://{0}/cgi-bin/api.html?f=18&idx={1}&on={2}&mode={3}&tempSet={4}&fan={5}".format(CONFIG['gateway'], ac['idx'], ac['on'], ac['mode'], ac['tempSet'], ac['fan']) 127 | logging.info(" {0}: Set: {1}".format(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())), api)) 128 | try: 129 | r = requests.get(api, auth=('admin',''), proxies = {'http': None, 'https': None}) 130 | except Exception as e: 131 | msg = str(e) 132 | if "('Connection aborted.', BadStatusLine('" in msg: 133 | msg = '' 134 | else: 135 | msg = '' 136 | 137 | def initializeClimates(client, node_id = "zhonghong", component = "climate", discovery_prefix = "homeassistant"): 138 | global acs 139 | acs = getACList() 140 | for ac in acs: 141 | object_id = "ac_{0}".format(ac['idx']) 142 | # Create Climate 143 | # removeClimate(object_id) 144 | createClimate(object_id) 145 | 146 | # Update Values 147 | topic = "{0}/{1}/{2}/{3}/{4}/state".format(discovery_prefix, component, node_id, object_id, 'mode') 148 | msg = STATES['mode'][0] if ac['on'] == 0 else STATES['mode'][ac['mode']] 149 | publish(client, topic, msg) 150 | topic = "{0}/{1}/{2}/{3}/{4}/state".format(discovery_prefix, component, node_id, object_id, 'temp') 151 | msg = int(ac['tempSet']) 152 | publish(client, topic, msg) 153 | topic = "{0}/{1}/{2}/{3}/{4}/state".format(discovery_prefix, component, node_id, object_id, 'cur_temp') 154 | msg = int(ac['tempIn']) 155 | publish(client, topic, msg) 156 | topic = "{0}/{1}/{2}/{3}/{4}/state".format(discovery_prefix, component, node_id, object_id, 'fan') 157 | msg = STATES['fan'][ac['fan']] 158 | publish(client, topic, msg) 159 | 160 | def createClimate(object_id, name = None, device_class = None, icon = None, temperature_unit = "C", node_id = "zhonghong", component = "climate", discovery_prefix = "homeassistant"): 161 | device = {} 162 | device["identifiers"] = ["ZhongHong VRF"] 163 | device["name"] = "ZhongHong VRF" 164 | device["manufacturer"] = "xswxm" 165 | device["model"] = "V100" 166 | device["sw_version"] = "0.3.0" 167 | 168 | topic = "{0}/{1}/{2}/{3}/config".format(discovery_prefix, component, node_id, object_id) 169 | 170 | payload = {} 171 | payload["device"] = device 172 | payload["temp_unit"] = temperature_unit 173 | payload["force_update"] = True 174 | 175 | payload["unique_id"] = "{0}_{1}".format(node_id, object_id) 176 | payload["object_id"] = "{0}_{1}".format(node_id, object_id) 177 | if icon != None: 178 | payload["icon"] = icon 179 | if device_class != None: 180 | payload["device_class"] = device_class 181 | payload["name"] = "zhonghong_{0}".format(object_id) 182 | payload["modes"] = [ 183 | "heat", 184 | "cool", 185 | "dry", 186 | "fan_only", 187 | "off" 188 | ] 189 | payload["fan_modes"] = [ 190 | "low", 191 | "medium", 192 | "high" 193 | ] 194 | # payload["min_temp"] = 16.0 195 | payload["max_temp"] = 29.0 196 | payload["power_command_topic"] = "{0}/{1}/{2}/{3}/{4}/set".format(discovery_prefix, component, node_id, object_id, 'on') 197 | payload["mode_command_topic"] = "{0}/{1}/{2}/{3}/{4}/set".format(discovery_prefix, component, node_id, object_id, 'mode') 198 | payload["temperature_command_topic"] = "{0}/{1}/{2}/{3}/{4}/set".format(discovery_prefix, component, node_id, object_id, 'temp') 199 | payload["fan_mode_command_topic"] = "{0}/{1}/{2}/{3}/{4}/set".format(discovery_prefix, component, node_id, object_id, 'fan') 200 | 201 | payload["mode_state_topic"] = "{0}/{1}/{2}/{3}/{4}/state".format(discovery_prefix, component, node_id, object_id, 'mode') 202 | payload["temperature_state_topic"] = "{0}/{1}/{2}/{3}/{4}/state".format(discovery_prefix, component, node_id, object_id, 'temp') 203 | payload["current_temperature_topic"] = "{0}/{1}/{2}/{3}/{4}/state".format(discovery_prefix, component, node_id, object_id, 'cur_temp') 204 | payload["fan_mode_state_topic"] = "{0}/{1}/{2}/{3}/{4}/state".format(discovery_prefix, component, node_id, object_id, 'fan') 205 | payload["temp_step"] = 1.0 206 | publish(client, topic, json.dumps(payload)) 207 | client.subscribe("{0}/{1}/{2}/{3}/{4}/set".format(discovery_prefix, component, node_id, object_id, 'on')) 208 | client.subscribe("{0}/{1}/{2}/{3}/{4}/set".format(discovery_prefix, component, node_id, object_id, 'mode')) 209 | client.subscribe("{0}/{1}/{2}/{3}/{4}/set".format(discovery_prefix, component, node_id, object_id, 'temp')) 210 | client.subscribe("{0}/{1}/{2}/{3}/{4}/set".format(discovery_prefix, component, node_id, object_id, 'fan')) 211 | 212 | def removeClimate(object_id, node_id = "zhonghong", component = "climate", discovery_prefix = "homeassistant"): 213 | topic = "{0}/{1}/{2}/{3}/config".format(discovery_prefix, component, node_id, object_id) 214 | publish(client, topic, json.dumps('')) 215 | 216 | def syncACList(client, node_id = "zhonghong", component = "climate", discovery_prefix = "homeassistant"): 217 | global STATES, acs 218 | acs_temp = getACList() 219 | 220 | for i in range(len(acs)): 221 | object_id = "ac_{0}".format(acs[i]['idx']) 222 | if acs[i]['on'] != acs_temp[i]['on'] or acs[i]['mode'] != acs_temp[i]['mode']: 223 | acs[i]['on'] = acs_temp[i]['on'] 224 | acs[i]['mode'] = acs_temp[i]['mode'] 225 | topic = "{0}/{1}/{2}/{3}/{4}/state".format(discovery_prefix, component, node_id, object_id, 'mode') 226 | msg = STATES['mode'][0] if acs[i]['on'] == 0 else STATES['mode'][acs[i]['mode']] 227 | publish(client, topic, msg) 228 | if acs[i]['tempSet'] != acs_temp[i]['tempSet']: 229 | acs[i]['tempSet'] = acs_temp[i]['tempSet'] 230 | topic = "{0}/{1}/{2}/{3}/{4}/state".format(discovery_prefix, component, node_id, object_id, 'temp') 231 | msg = int(acs[i]['tempSet']) 232 | publish(client, topic, msg) 233 | if acs[i]['tempIn'] != acs_temp[i]['tempIn']: 234 | acs[i]['tempIn'] = acs_temp[i]['tempIn'] 235 | topic = "{0}/{1}/{2}/{3}/{4}/state".format(discovery_prefix, component, node_id, object_id, 'cur_temp') 236 | msg = int(acs[i]['tempIn']) 237 | publish(client, topic, msg) 238 | if acs[i]['fan'] != acs_temp[i]['fan']: 239 | acs[i]['fan'] = acs_temp[i]['fan'] 240 | topic = "{0}/{1}/{2}/{3}/{4}/state".format(discovery_prefix, component, node_id, object_id, 'fan') 241 | msg = STATES['fan'][acs[i]['fan']] 242 | publish(client, topic, msg) 243 | 244 | if __name__ == '__main__': 245 | # setConfig() 246 | loadConfig() 247 | client = connect_mqtt() 248 | subscribe(client) 249 | client.subscribe("homeassistant/zhonghong/initialize") 250 | client.loop_start() 251 | 252 | count = 2 253 | while count > 0: 254 | initializeClimates(client) 255 | time.sleep(1) 256 | count = count - 1 257 | 258 | while True: 259 | syncACList(client) 260 | time.sleep(1) --------------------------------------------------------------------------------