894 lines
31 KiB
Python
894 lines
31 KiB
Python
import logging
|
|
from datetime import timedelta
|
|
import time
|
|
|
|
# 此处引入了几个异步处理的库
|
|
import asyncio
|
|
import async_timeout
|
|
import aiohttp
|
|
|
|
import voluptuous as vol
|
|
|
|
# aiohttp_client将aiohttp的session与hass关联起来
|
|
# track_time_interval需要使用对应的异步的版本
|
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|
from homeassistant.helpers.event import async_track_time_interval
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
|
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
|
from homeassistant.const import (
|
|
ATTR_ATTRIBUTION, ATTR_FRIENDLY_NAME,
|
|
#TEMP_CELSIUS,
|
|
UnitOfTemperature,
|
|
PERCENTAGE,
|
|
#PRECIPITATION_MILLIMETERS_PER_HOUR,
|
|
UnitOfVolumetricFlux,
|
|
#SPEED_KILOMETERS_PER_HOUR,
|
|
UnitOfSpeed,
|
|
#PRESSURE_HPA,
|
|
UnitOfPressure,
|
|
#LENGTH_KILOMETERS
|
|
UnitOfLength
|
|
)
|
|
from homeassistant.helpers.entity import Entity, DeviceInfo
|
|
import homeassistant.helpers.config_validation as cv
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
from .heweather.const import (
|
|
DOMAIN,
|
|
CONF_AUTH_METHOD,
|
|
CONF_OPTIONS,
|
|
CONF_LONGITUDE,
|
|
CONF_LATITUDE,
|
|
CONF_HOST,
|
|
CONF_KEY,
|
|
CONF_JWT_SUB,
|
|
CONF_JWT_KID,
|
|
DEFAULT_HOST,
|
|
CONF_DISASTERLEVEL,
|
|
CONF_DISASTERMSG,
|
|
CONF_SENSOR_LIST,
|
|
DISASTER_LEVEL,
|
|
ATTR_UPDATE_TIME,
|
|
ATTR_SUGGESTION,
|
|
ATTRIBUTION
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
WEATHER_TIME_BETWEEN_UPDATES = timedelta(seconds=600)
|
|
LIFESUGGESTION_TIME_BETWEEN_UPDATES = timedelta(seconds=7200)
|
|
|
|
OPTIONS = {
|
|
"temprature": ["heweather_temperature", "室外温度", "mdi:thermometer", UnitOfTemperature.CELSIUS],
|
|
"humidity": ["heweather_humidity", "室外湿度", "mdi:water-percent", PERCENTAGE],
|
|
"feelsLike": ["heweather_feelslike", "体感温度", "mdi:thermometer", UnitOfTemperature.CELSIUS],
|
|
"text": ["heweather_text", "天气描述", "mdi:thermometer", ' '],
|
|
"precip": ["heweather_precip", "小时降水量", "mdi:weather-rainy", UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR],
|
|
"windDir": ["heweather_winddir", "风向", "mdi:windsock", ' '],
|
|
"windScale": ["heweather_windscale", "风力等级", "mdi:weather-windy", ' '],
|
|
"windSpeed": ["heweather_windspeed", "风速", "mdi:weather-windy", UnitOfSpeed.KILOMETERS_PER_HOUR],
|
|
"dew": ["heweather_dew", "露点温度", "mdi:thermometer-water", ' '],
|
|
"pressure": ["heweather_pressure", "大气压强", "mdi:thermometer", UnitOfPressure.HPA],
|
|
"vis": ["heweather_vis", "能见度", "mdi:thermometer", UnitOfLength.KILOMETERS],
|
|
"cloud": ["heweather_cloud", "云量", "mdi:cloud-percent", PERCENTAGE],
|
|
"primary": ["heweather_primary", "空气质量的主要污染物", "mdi:weather-dust", " "],
|
|
"category": ["heweather_category", "空气质量指数级别", "mdi:walk", " "],
|
|
"level": ["heweather_level", "空气质量指数等级", "mdi:walk", " "],
|
|
"pm2p5": ["heweather_pm25", "PM2.5", "mdi:walk", " "],
|
|
"pm10": ["heweather_pm10", "PM10", "mdi:walk", " "],
|
|
"no2": ["heweather_no2", "二氧化氮", "mdi:emoticon-dead", " "],
|
|
"so2": ["heweather_so2", "二氧化硫", "mdi:emoticon-dead", " "],
|
|
"co": ["heweather_co", "一氧化碳", "mdi:molecule-co", " "],
|
|
"o3": ["heweather_o3", "臭氧", "mdi:weather-cloudy", " "],
|
|
"no": ["heweather_no", "一氧化氮", "mdi:emoticon-dead", " "],
|
|
"nmhc": ["heweather_nmhc", "非甲烷总烃", "mdi:emoticon-dead", " "],
|
|
"qlty": ["heweather_qlty", "综合空气质量", "mdi:quality-high", " "],
|
|
"disaster_warn": ["heweather_disaster_warn", "灾害预警", "mdi:alert", " "],
|
|
|
|
"air": ["suggestion_air", "空气污染扩散条件指数", "mdi:air-conditioner", " "],
|
|
"comf": ["suggestion_comf", "舒适度指数", "mdi:human-greeting", " "],
|
|
"cw": ["suggestion_cw", "洗车指数", "mdi:car", " "],
|
|
"drsg": ["suggestion_drsg", "穿衣指数", "mdi:hanger", " "],
|
|
"flu": ["suggestion_flu", "感冒指数", "mdi:biohazard", " "],
|
|
"sport": ["suggestion_sport", "运动指数", "mdi:badminton", " "],
|
|
"trav": ["suggestion_trav", "旅行指数", "mdi:wallet-travel", " "],
|
|
"uv": ["suggestion_uv", "紫外线指数", "mdi:sun-wireless", " "],
|
|
"guomin": ["suggestion_guomin", "过敏指数", "mdi:sunglasses", " "],
|
|
"kongtiao": ["suggestion_kongtiao", "空调开启指数", "mdi:air-conditioner", " "],
|
|
"sunglass": ["suggestion_sunglass", "太阳镜指数", "mdi:sunglasses", " "],
|
|
"fangshai": ["suggestion_fangshai", "防晒指数", "mdi:shield-sun-outline", " "],
|
|
"liangshai": ["suggestion_liangshai", "晾晒指数", "mdi:tshirt-crew-outline", " "],
|
|
|
|
"jiaotong": ["suggestion_jiaotong", "交通指数", "mdi:train-car", " "],
|
|
|
|
}
|
|
|
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|
vol.Required(CONF_LONGITUDE): cv.string,
|
|
vol.Required(CONF_LATITUDE): cv.string,
|
|
vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string,
|
|
vol.Required(CONF_KEY): cv.string,
|
|
vol.Required(CONF_DISASTERLEVEL): cv.string,
|
|
vol.Required(CONF_DISASTERMSG): cv.string,
|
|
|
|
})
|
|
|
|
|
|
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback):
|
|
"""这个协程是程序的入口,其中add_devices函数也变成了异步版本."""
|
|
_LOGGER.info("setup platform sensor.Heweather...")
|
|
|
|
longitude = config_entry.data.get(CONF_LONGITUDE)
|
|
latitude = config_entry.data.get(CONF_LATITUDE)
|
|
host = config_entry.data.get(CONF_HOST)
|
|
key = config_entry.data.get(CONF_KEY)
|
|
disastermsg = config_entry.data.get(CONF_DISASTERMSG)
|
|
disasterlevel = config_entry.data.get(CONF_DISASTERLEVEL)
|
|
# 这里通过 data 实例化class weatherdata,并传入调用API所需信息
|
|
auth_method = config_entry.data.get(CONF_AUTH_METHOD)
|
|
if auth_method == "key":
|
|
key = config_entry.data.get(CONF_KEY)
|
|
suggestion_data = SuggestionData(hass, longitude, latitude, host, key=key)
|
|
weather_data = WeatherData(hass, longitude, latitude, host, disastermsg, disasterlevel, key=key)
|
|
else:
|
|
# HeWeather Certification
|
|
heweather_cert = hass.data[DOMAIN].get('heweather_cert', None)
|
|
jwt_sub = config_entry.data.get(CONF_JWT_SUB)
|
|
jwt_kid = config_entry.data.get(CONF_JWT_KID)
|
|
suggestion_data = SuggestionData(hass, longitude, latitude, host, heweather_cert=heweather_cert, jwt_sub=jwt_sub, jwt_kid=jwt_kid)
|
|
weather_data = WeatherData(hass, longitude, latitude, host, disastermsg, disasterlevel, heweather_cert=heweather_cert, jwt_sub=jwt_sub, jwt_kid=jwt_kid)
|
|
|
|
await weather_data.async_update(dt_util.now())
|
|
config_entry.async_on_unload(async_track_time_interval(hass, weather_data.async_update, WEATHER_TIME_BETWEEN_UPDATES, cancel_on_shutdown=True))
|
|
|
|
await suggestion_data.async_update(dt_util.now())
|
|
config_entry.async_on_unload(async_track_time_interval(hass, suggestion_data.async_update, LIFESUGGESTION_TIME_BETWEEN_UPDATES, cancel_on_shutdown=True))
|
|
|
|
dev = []
|
|
for option in CONF_SENSOR_LIST:
|
|
dev.append(HeweatherWeatherSensor(weather_data, suggestion_data, option, longitude, latitude))
|
|
async_add_entities(dev, True)
|
|
|
|
|
|
#@asyncio.coroutine
|
|
async def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
|
"""这个协程是程序的入口,其中add_devices函数也变成了异步版本."""
|
|
_LOGGER.info("setup platform sensor.Heweather...")
|
|
|
|
longitude = config.get(CONF_LONGITUDE)
|
|
latitude = config.get(CONF_LATITUDE)
|
|
host = config.get(CONF_HOST)
|
|
key = config.get(CONF_KEY)
|
|
disastermsg = config.get(CONF_DISASTERMSG)
|
|
disasterlevel = config.get(CONF_DISASTERLEVEL)
|
|
# 这里通过 data 实例化class weatherdata,并传入调用API所需信息
|
|
weather_data = WeatherData(hass, longitude, latitude, host, disastermsg, disasterlevel, key=key)
|
|
suggestion_data = SuggestionData(hass, longitude, latitude, host, key=key)
|
|
|
|
await weather_data.async_update(dt_util.now())
|
|
async_track_time_interval(hass, weather_data.async_update, WEATHER_TIME_BETWEEN_UPDATES, cancel_on_shutdown=True)
|
|
|
|
await suggestion_data.async_update(dt_util.now())
|
|
async_track_time_interval(hass, suggestion_data.async_update, LIFESUGGESTION_TIME_BETWEEN_UPDATES, cancel_on_shutdown=True)
|
|
|
|
dev = []
|
|
for option in CONF_SENSOR_LIST:
|
|
dev.append(HeweatherWeatherSensor(weather_data, suggestion_data, option, longitude, latitude))
|
|
async_add_devices(dev, True)
|
|
|
|
|
|
class HeweatherWeatherSensor(Entity):
|
|
"""定义一个温度传感器的类,继承自HomeAssistant的Entity类."""
|
|
_attr_has_entity_name = True
|
|
|
|
def __init__(self, weather_data, suggestion_data, option, longitude, latitude):
|
|
"""初始化."""
|
|
self._weather_data = weather_data
|
|
self._suggestion_data = suggestion_data
|
|
self._object_id = OPTIONS[option][0]
|
|
|
|
# 【修改重点】直接使用 OPTIONS 里的中文名称作为实体名
|
|
# OPTIONS[option][1] 对应代码最上面的字典里的 "舒适度指数"、"洗车指数" 等中文
|
|
self._attr_name = OPTIONS[option][0]
|
|
|
|
self._icon = OPTIONS[option][2]
|
|
self._unit_of_measurement = OPTIONS[option][3]
|
|
|
|
self._type = option
|
|
self._state = None
|
|
self._attributes = {"states":"null"}
|
|
self._updatetime = None
|
|
self._attr_unique_id = f"{OPTIONS[option][0]}_{longitude}_{latitude}"
|
|
|
|
@property
|
|
def device_info(self) -> DeviceInfo:
|
|
"""Return the device info."""
|
|
return DeviceInfo(
|
|
identifiers={(DOMAIN, f"{self._weather_data._params['location']}")},
|
|
name="和风天气",
|
|
manufacturer="QWeather",
|
|
model="API v7",
|
|
entry_type=None,
|
|
)
|
|
|
|
@property
|
|
def extra_state_attributes(self):
|
|
"""Return entity specific state attributes."""
|
|
return self._attributes
|
|
|
|
@property
|
|
def translation_key(self):
|
|
"""Return the translation key to translate the entity's name and states."""
|
|
return self._object_id
|
|
|
|
#@property
|
|
#def name(self):
|
|
# """返回实体的名字."""
|
|
# return self._name
|
|
|
|
#@property
|
|
#def registry_name(self):
|
|
# """返回实体的friendly_name属性."""
|
|
# return self._friendly_name
|
|
|
|
@property
|
|
def state(self):
|
|
"""返回当前的状态."""
|
|
return self._state
|
|
|
|
@property
|
|
def icon(self):
|
|
"""返回icon属性."""
|
|
return self._icon
|
|
|
|
@property
|
|
def unit_of_measurement(self):
|
|
"""返回unit_of_measuremeng属性."""
|
|
return self._unit_of_measurement
|
|
|
|
@property
|
|
def device_state_attributes(self):
|
|
"""设置其它一些属性值."""
|
|
if self._state is not None:
|
|
return {
|
|
ATTR_ATTRIBUTION: ATTRIBUTION,
|
|
ATTR_UPDATE_TIME: self._updatetime
|
|
}
|
|
|
|
#@asyncio.coroutine
|
|
async def async_update(self):
|
|
"""update函数变成了async_update."""
|
|
self._updatetime = self._weather_data.updatetime
|
|
|
|
if self._type == "temprature":
|
|
self._state = self._weather_data.temprature
|
|
elif self._type == "humidity":
|
|
self._state = self._weather_data.humidity
|
|
elif self._type == "feelsLike":
|
|
self._state = self._weather_data.feelsLike
|
|
elif self._type == "text":
|
|
self._state = self._weather_data.text
|
|
elif self._type == "windDir":
|
|
self._state = self._weather_data.windDir
|
|
elif self._type == "windScale":
|
|
self._state = self._weather_data.windScale
|
|
elif self._type == "windSpeed":
|
|
self._state = self._weather_data.windSpeed
|
|
elif self._type == "precip":
|
|
self._state = self._weather_data.precip
|
|
elif self._type == "pressure":
|
|
self._state = self._weather_data.pressure
|
|
elif self._type == "vis":
|
|
self._state = self._weather_data.vis
|
|
elif self._type == "dew":
|
|
self._state = self._weather_data.dew
|
|
elif self._type == "cloud":
|
|
self._state = self._weather_data.cloud
|
|
elif self._type == "category":
|
|
self._state = self._weather_data.category
|
|
elif self._type == "primary":
|
|
self._state = self._weather_data.primary
|
|
elif self._type == "level":
|
|
self._state = self._weather_data.level
|
|
elif self._type == "pm10":
|
|
self._state = self._weather_data.pm10
|
|
elif self._type == "pm2p5":
|
|
self._state = self._weather_data.pm2p5
|
|
elif self._type == "no2":
|
|
self._state = self._weather_data.no2
|
|
elif self._type == "so2":
|
|
self._state = self._weather_data.so2
|
|
elif self._type == "co":
|
|
self._state = self._weather_data.co
|
|
elif self._type == "o3":
|
|
self._state = self._weather_data.o3
|
|
elif self._type == "no":
|
|
self._state = self._weather_data.no
|
|
elif self._type == "nmhc":
|
|
self._state = self._weather_data.nmhc
|
|
elif self._type == "qlty":
|
|
self._state = self._weather_data.qlty
|
|
elif self._type == "disaster_warn":
|
|
if len(self._weather_data.disaster_warn) > 10:
|
|
self._state = 'on'
|
|
self._attributes["states"] = self._weather_data.disaster_warn
|
|
else:
|
|
self._state = 'off'
|
|
self._attributes["states"] = self._weather_data.disaster_warn
|
|
#lifesuggestion
|
|
elif self._type == "air":
|
|
self._state = self._suggestion_data.air[0]
|
|
self._attributes["states"] = self._suggestion_data.air[1]
|
|
elif self._type == "comf":
|
|
self._state = self._suggestion_data.comf[0]
|
|
self._attributes["states"] = self._suggestion_data.comf[1]
|
|
elif self._type == "cw":
|
|
self._state = self._suggestion_data.cw[0]
|
|
self._attributes["states"] = self._suggestion_data.cw[1]
|
|
elif self._type == "drsg":
|
|
self._state = self._suggestion_data.drsg[0]
|
|
self._attributes["states"] = self._suggestion_data.drsg[1]
|
|
elif self._type == "flu":
|
|
self._state = self._suggestion_data.flu[0]
|
|
self._attributes["states"] = self._suggestion_data.flu[1]
|
|
elif self._type == "sport":
|
|
self._state = self._suggestion_data.sport[0]
|
|
self._attributes["states"] = self._suggestion_data.sport[1]
|
|
elif self._type == "trav":
|
|
self._state = self._suggestion_data.trav[0]
|
|
self._attributes["states"] = self._suggestion_data.trav[1]
|
|
elif self._type == "uv":
|
|
self._state = self._suggestion_data.uv[0]
|
|
self._attributes["states"] = self._suggestion_data.uv[1]
|
|
elif self._type == "guomin":
|
|
self._state = self._suggestion_data.guomin[0]
|
|
self._attributes["states"] = self._suggestion_data.guomin[1]
|
|
elif self._type == "kongtiao":
|
|
self._state = self._suggestion_data.kongtiao[0]
|
|
self._attributes["states"] = self._suggestion_data.kongtiao[1]
|
|
elif self._type == "liangshai":
|
|
self._state = self._suggestion_data.liangshai[0]
|
|
self._attributes["states"] = self._suggestion_data.liangshai[1]
|
|
elif self._type == "fangshai":
|
|
self._state = self._suggestion_data.fangshai[0]
|
|
self._attributes["states"] = self._suggestion_data.fangshai[1]
|
|
elif self._type == "sunglass":
|
|
self._state = self._suggestion_data.uv[0]
|
|
self._attributes["states"] = self._suggestion_data.uv[1]
|
|
elif self._type == "jiaotong":
|
|
self._state = self._suggestion_data.jiaotong[0]
|
|
self._attributes["states"] = self._suggestion_data.jiaotong[1]
|
|
|
|
# 设置污染物单位
|
|
pollutant_types = {"pm10", "pm2p5", "no2", "so2", "co", "o3", "no", "nmhc"}
|
|
if self._type in pollutant_types:
|
|
unit = getattr(self._weather_data, f"{self._type}_unit", None)
|
|
if unit:
|
|
self._unit_of_measurement = unit
|
|
|
|
class WeatherData(object):
|
|
"""天气相关的数据,存储在这个类中."""
|
|
|
|
def __init__(self, hass, longitude, latitude, host, disastermsg, disasterlevel, key=None, heweather_cert=None, jwt_sub=None, jwt_kid=None):
|
|
"""初始化函数."""
|
|
self._hass = hass
|
|
location = f"{longitude},{latitude}"
|
|
self._disastermsg = disastermsg
|
|
self._disasterlevel = disasterlevel
|
|
#disastermsg, disasterlevel
|
|
|
|
self._temprature = None
|
|
self._humidity = None
|
|
|
|
if key is not None:
|
|
self._is_jwt = False
|
|
self._weather_now_url = "https://"+host+"/v7/weather/now?location="+location+"&key="+key
|
|
self._air_now_url = "https://"+host+"/airquality/v1/current/"+latitude+"/"+longitude+"?key="+key
|
|
self._disaster_warn_url = "https://"+host+"/weatheralert/v1/current/"+latitude+"/"+longitude+"?key="+key
|
|
self._params = {"location": location,
|
|
"key": key}
|
|
else:
|
|
self._is_jwt = True
|
|
self._weather_now_url = "https://"+host+"/v7/weather/now?location="+location
|
|
self._air_now_url = "https://"+host+"/airquality/v1/current/"+latitude+"/"+longitude
|
|
self._disaster_warn_url = "https://"+host+"/weatheralert/v1/current/"+latitude+"/"+longitude
|
|
self._params = {"location": location}
|
|
self._heweather_cert = heweather_cert
|
|
self._jwt_sub = jwt_sub
|
|
self._jwt_kid = jwt_kid
|
|
|
|
self._feelsLike = None
|
|
self._text = None
|
|
self._windDir = None
|
|
self._windScale = None
|
|
self._windSpeed = None
|
|
self._precip = None
|
|
self._pressure = None
|
|
self._vis = None
|
|
self._cloud = None
|
|
self._dew = None
|
|
self._updatetime = None
|
|
|
|
self._category = None
|
|
self._pm10 = None
|
|
self._primary = None
|
|
self._level = None
|
|
|
|
|
|
|
|
self._pm2p5 = None
|
|
self._no2 = None
|
|
self._so2 = None
|
|
self._co = None
|
|
self._o3 = None
|
|
self._no = None
|
|
self._nmhc = None
|
|
# 新 API 单位不固定,动态获取
|
|
self._pm10_unit = None
|
|
self._pm2p5_unit = None
|
|
self._no2_unit = None
|
|
self._so2_unit = None
|
|
self._co_unit = None
|
|
self._o3_unit = None
|
|
self._no_unit = None
|
|
self._nmhc_unit = None
|
|
self._qlty = None
|
|
self._disaster_warn = None
|
|
self._updatetime = None
|
|
|
|
@property
|
|
def temprature(self):
|
|
"""温度."""
|
|
return self._temprature
|
|
|
|
@property
|
|
def humidity(self):
|
|
"""湿度."""
|
|
return self._humidity
|
|
|
|
@property
|
|
def feelsLike(self):
|
|
"""体感温度"""
|
|
return self._feelsLike
|
|
|
|
@property
|
|
def text(self):
|
|
"""天气状况的文字描述,包括阴晴雨雪等天气状态的描述"""
|
|
return self._text
|
|
|
|
@property
|
|
def windDir(self):
|
|
"""风向"""
|
|
return self._windDir
|
|
|
|
@property
|
|
def category(self):
|
|
"""空气质量指数级别"""
|
|
return self._category
|
|
|
|
@property
|
|
def level(self):
|
|
"""空气质量指数等级"""
|
|
return self._level
|
|
|
|
@property
|
|
def primary(self):
|
|
"""空气质量的主要污染物,空气质量为优时,返回值为NA"""
|
|
return self._primary
|
|
|
|
@property
|
|
def windScale(self):
|
|
"""风力等级"""
|
|
return self._windScale
|
|
|
|
@property
|
|
def windSpeed(self):
|
|
"""风速,公里/小时"""
|
|
return self._windSpeed
|
|
|
|
@property
|
|
def precip(self):
|
|
"""当前小时累计降水量,默认单位:毫米"""
|
|
return self._precip
|
|
|
|
@property
|
|
def pressure(self):
|
|
"""大气压强,默认单位:百帕"""
|
|
return self._pressure
|
|
|
|
@property
|
|
def vis(self):
|
|
"""能见度,默认单位:公里"""
|
|
return self._vis
|
|
|
|
@property
|
|
def cloud(self):
|
|
"""云量,百分比数值。可能为空"""
|
|
return self._cloud
|
|
|
|
@property
|
|
def dew(self):
|
|
"""露点温度。可能为空"""
|
|
return self._dew
|
|
|
|
@property
|
|
def pm2p5(self):
|
|
"""pm2.5"""
|
|
return self._pm2p5
|
|
|
|
@property
|
|
def pm2p5_unit(self):
|
|
return self._pm2p5_unit
|
|
|
|
@property
|
|
def pm10(self):
|
|
"""pm10"""
|
|
return self._pm10
|
|
|
|
@property
|
|
def pm10_unit(self):
|
|
return self._pm10_unit
|
|
|
|
@property
|
|
def qlty(self):
|
|
"""(aqi)空气质量指数"""
|
|
return self._qlty
|
|
|
|
@property
|
|
def no2(self):
|
|
"""no2"""
|
|
return self._no2
|
|
|
|
@property
|
|
def no2_unit(self):
|
|
return self._no2_unit
|
|
|
|
@property
|
|
def co(self):
|
|
"""co"""
|
|
return self._co
|
|
|
|
@property
|
|
def co_unit(self):
|
|
return self._co_unit
|
|
|
|
@property
|
|
def so2(self):
|
|
"""so2"""
|
|
return self._so2
|
|
|
|
@property
|
|
def so2_unit(self):
|
|
return self._so2_unit
|
|
|
|
@property
|
|
def o3(self):
|
|
"""o3"""
|
|
return self._o3
|
|
|
|
@property
|
|
def o3_unit(self):
|
|
return self._o3_unit
|
|
|
|
@property
|
|
def no(self):
|
|
"""no"""
|
|
return self._no
|
|
|
|
@property
|
|
def no_unit(self):
|
|
return self._no_unit
|
|
|
|
@property
|
|
def nmhc(self):
|
|
"""nmhc"""
|
|
return self._nmhc
|
|
|
|
@property
|
|
def nmhc_unit(self):
|
|
return self._nmhc_unit
|
|
|
|
@property
|
|
def disaster_warn(self):
|
|
"""灾害预警"""
|
|
return self._disaster_warn
|
|
|
|
|
|
@property
|
|
def updatetime(self):
|
|
"""更新时间."""
|
|
return self._updatetime
|
|
|
|
#@asyncio.coroutine
|
|
async def async_update(self, now):
|
|
"""从远程更新信息."""
|
|
_LOGGER.info("Update from JingdongWangxiang's OpenAPI...")
|
|
|
|
# 通过HTTP访问,获取需要的信息
|
|
# 此处使用了基于aiohttp库的async_get_clientsession
|
|
try:
|
|
timeout = aiohttp.ClientTimeout(total=20)
|
|
connector = aiohttp.TCPConnector(limit=10)
|
|
headers = None
|
|
if self._is_jwt:
|
|
jwt_token = await self._heweather_cert.get_jwt_token_heweather_async(self._jwt_sub, self._jwt_kid, int(time.time()) - 30, int(time.time()) + 180)
|
|
headers = {'Authorization': f'Bearer {jwt_token}'}
|
|
async with aiohttp.ClientSession(connector=connector, timeout=timeout, headers=headers) as session:
|
|
async with session.get(self._weather_now_url) as response:
|
|
json_data = await response.json()
|
|
weather = json_data["now"]
|
|
async with session.get(self._air_now_url) as response:
|
|
json_data = await response.json()
|
|
# AQI (CN) code: cn-mee
|
|
air_index = next((item for item in json_data["indexes"] if item["code"] == "cn-mee"), None)
|
|
air_pollutants = json_data["pollutants"]
|
|
async with session.get(self._disaster_warn_url) as response:
|
|
json_data = await response.json()
|
|
disaster_warn = json_data["alerts"]
|
|
|
|
|
|
except(asyncio.TimeoutError, aiohttp.ClientError):
|
|
_LOGGER.error("Error while accessing: %s", self._weather_now_url)
|
|
return
|
|
|
|
# 根据http返回的结果,更新数据
|
|
self._temprature = weather["temp"]
|
|
self._humidity = weather["humidity"]
|
|
self._feelsLike = weather["feelsLike"]
|
|
self._text = weather["text"]
|
|
self._windDir = weather["windDir"]
|
|
self._windScale = weather["windScale"]
|
|
self._windSpeed = weather["windSpeed"]
|
|
self._precip = weather["precip"]
|
|
self._pressure = weather["pressure"]
|
|
self._vis = weather["vis"]
|
|
self._cloud = weather["cloud"]
|
|
self._dew = weather["dew"]
|
|
self._updatetime = weather["obsTime"]
|
|
self._qlty = air_index["aqiDisplay"]
|
|
self._level = air_index["level"]
|
|
self._category = air_index["category"]
|
|
primary_pollutant = air_index["primaryPollutant"]
|
|
if primary_pollutant is not None and "name" in primary_pollutant:
|
|
self._primary = primary_pollutant["name"]
|
|
|
|
supported_codes = {"pm10", "pm2p5", "co", "no", "no2", "so2", "o3", "nmhc"}
|
|
for pollutant in air_pollutants:
|
|
code = pollutant["code"]
|
|
if code in supported_codes:
|
|
setattr(self, f"_{code}", pollutant["concentration"]["value"])
|
|
setattr(self, f"_{code}_unit", pollutant["concentration"]["unit"])
|
|
|
|
|
|
allmsg=''
|
|
titlemsg=''
|
|
# Normalize disaster_warn into a list for safe iteration
|
|
if disaster_warn is None:
|
|
alerts = []
|
|
elif isinstance(disaster_warn, dict):
|
|
alerts = [disaster_warn]
|
|
elif isinstance(disaster_warn, list):
|
|
alerts = disaster_warn
|
|
else:
|
|
# Unexpected type: try to coerce to list if possible, else empty
|
|
try:
|
|
alerts = list(disaster_warn)
|
|
except Exception:
|
|
alerts = []
|
|
|
|
for i in alerts:
|
|
#if DISASTER_LEVEL[i["severity"]] >= 订阅等级:
|
|
severity = i.get("severity", "").lower()
|
|
if severity in DISASTER_LEVEL and (DISASTER_LEVEL[severity] >= int(self._disasterlevel)):
|
|
allmsg = allmsg +i["headline"] + ':' + i["description"] + '||'
|
|
titlemsg = titlemsg + i["headline"] + '||'
|
|
|
|
if(len(titlemsg)<5):
|
|
self._disaster_warn = '近日无'+ self._disasterlevel +'级及以上灾害'
|
|
#if(订阅标题)
|
|
elif(self._disastermsg=='title'):
|
|
self._disaster_warn = titlemsg
|
|
else:
|
|
self._disaster_warn = allmsg
|
|
|
|
class SuggestionData(object):
|
|
"""天气相关建议的数据,存储在这个类中."""
|
|
|
|
def __init__(self, hass, longitude, latitude, host, key=None, heweather_cert=None, jwt_sub=None, jwt_kid=None):
|
|
"""初始化函数."""
|
|
self._hass = hass
|
|
location = f"{longitude},{latitude}"
|
|
|
|
if key is not None:
|
|
self._url = "https://"+host+"/v7/indices/1d?location="+location+"&key="+key+"&type=0"
|
|
self._params = {"location": location,
|
|
"key": key,
|
|
"type": 0
|
|
}
|
|
self._is_jwt = False
|
|
else:
|
|
self._url = "https://"+host+"/v7/indices/1d?location="+location+"&type=0"
|
|
self._params = {"location": location,
|
|
"type": 0
|
|
}
|
|
self._is_jwt = True
|
|
self._heweather_cert = heweather_cert
|
|
self._jwt_sub = jwt_sub
|
|
self._jwt_kid = jwt_kid
|
|
|
|
self._updatetime = ["1","1"]
|
|
self._air = ["1","1"]
|
|
self._comf = ["1","1"]
|
|
self._cw = ["1","1"]
|
|
self._drsg = ["1","1"]
|
|
self._flu = ["1","1"]
|
|
self._sport = ["1","1"]
|
|
self._trav = ["1","1"]
|
|
self._uv = ["1","1"]
|
|
self._guomin = None
|
|
self._kongtiao = None
|
|
self._sunglass = None
|
|
self._liangshai = None
|
|
self._fangshai = None
|
|
self._jiaotong = None
|
|
|
|
@property
|
|
def updatetime(self):
|
|
"""更新时间."""
|
|
return self._updatetime
|
|
|
|
@property
|
|
def air(self):
|
|
"""空气污染扩散条件指数"""
|
|
return self._air
|
|
|
|
@property
|
|
def comf(self):
|
|
"""舒适度指数"""
|
|
return self._comf
|
|
|
|
@property
|
|
def cw(self):
|
|
"""洗车建议"""
|
|
return self._cw
|
|
|
|
@property
|
|
def drsg(self):
|
|
"""穿着建议"""
|
|
return self._drsg
|
|
|
|
@property
|
|
def flu(self):
|
|
"""流感提示"""
|
|
return self._flu
|
|
|
|
@property
|
|
def sport(self):
|
|
"""运动建议"""
|
|
return self._sport
|
|
|
|
@property
|
|
def trav(self):
|
|
"""旅游指南"""
|
|
return self._trav
|
|
|
|
@property
|
|
def uv(self):
|
|
"""紫外线"""
|
|
return self._uv
|
|
|
|
@property
|
|
def guomin(self):
|
|
"""过敏指数"""
|
|
return self._guomin
|
|
|
|
@property
|
|
def kongtiao(self):
|
|
"""空调指数"""
|
|
return self._kongtiao
|
|
|
|
@property
|
|
def sunglass(self):
|
|
"""太阳镜指数"""
|
|
return self._sunglass
|
|
|
|
@property
|
|
def liangshai(self):
|
|
"""晾晒指数"""
|
|
return self._liangshai
|
|
|
|
@property
|
|
def fangshai(self):
|
|
"""防晒指数"""
|
|
return self._fangshai
|
|
|
|
@property
|
|
def jiaotong(self):
|
|
"""交通指数"""
|
|
return self._jiaotong
|
|
|
|
|
|
#@asyncio.coroutine
|
|
async def async_update(self, now):
|
|
"""从远程更新信息."""
|
|
try:
|
|
session = async_get_clientsession(self._hass)
|
|
headers = None
|
|
if self._is_jwt:
|
|
jwt_token = await self._heweather_cert.get_jwt_token_heweather_async(self._jwt_sub, self._jwt_kid, int(time.time()) - 30, int(time.time()) + 180)
|
|
headers = {'Authorization': f'Bearer {jwt_token}'}
|
|
with async_timeout.timeout(15):
|
|
#with async_timeout.timeout(15, loop=self._hass.loop):
|
|
response = await session.get(
|
|
self._url, headers=headers)
|
|
|
|
except(asyncio.TimeoutError, aiohttp.ClientError):
|
|
_LOGGER.error("Error while accessing: %s", self._url)
|
|
return
|
|
|
|
if response.status != 200:
|
|
_LOGGER.error("Error while accessing: %s, status=%d",
|
|
self._url,
|
|
response.status)
|
|
return
|
|
|
|
result = await response.json()
|
|
|
|
if result is None:
|
|
_LOGGER.error("Request api Error")
|
|
return
|
|
elif result["code"] != "200":
|
|
_LOGGER.error("Error API return, code=%s,url=%s",
|
|
result["code"],self._url)
|
|
return
|
|
|
|
# 根据http返回的结果,更新数据
|
|
all_result = result["daily"]
|
|
self._updatetime = result["updateTime"]
|
|
for i in all_result:
|
|
if i["type"] == "1":
|
|
self._sport = [i["category"], i["text"]]
|
|
|
|
if i["type"] == "10":
|
|
self._air = [i["category"], i["text"]]
|
|
|
|
if i["type"] == "8":
|
|
self._comf = [i["category"], i["text"]]
|
|
|
|
if i["type"] == "2":
|
|
self._cw = [i["category"], i["text"]]
|
|
|
|
if i["type"] == "3":
|
|
self._drsg = [i["category"], i["text"]]
|
|
|
|
if i["type"] == "9":
|
|
self._flu = [i["category"], i["text"]]
|
|
|
|
if i["type"] == "6":
|
|
self._trav = [i["category"], i["text"]]
|
|
|
|
if i["type"] == "5":
|
|
self._uv = [i["category"], i["text"]]
|
|
|
|
if i["type"] == "7":
|
|
self._guomin = [i["category"], i["text"]]
|
|
|
|
if i["type"] == "11":
|
|
self._kongtiao = [i["category"], i["text"]]
|
|
|
|
if i["type"] == "12":
|
|
self._sunglass = [i["category"], i["text"]]
|
|
|
|
if i["type"] == "14":
|
|
self._liangshai = [i["category"], i["text"]]
|
|
|
|
if i["type"] == "15":
|
|
self._jiaotong = [i["category"], i["text"]]
|
|
|
|
if i["type"] == "16":
|
|
self._fangshai = [i["category"], i["text"]]
|