篇五:几个界面优化插件
都是一直用的默认hass的界面是不是很不爽,特别用在pad或者手机上,来我们看一下几个第三方界面的插件。
注意:因为已经将过一版专门插件如何安装,以下每个插件我不再细讲,我一直觉得插件安装配置、问题查找是最重要的能力,否则弃坑的可能性非常大,一步一坎,所以大家还是要有自己研究探索能力,毕竟这都是软件层面上的,再出问题能有多大,大不了删了重装嘛,如果怕树莓派有问题,就装我蓝牙网关那篇的树莓派sd卡备份以下,出了问题恢复一份。
floorplan
这个是我玩hass最开始最喜欢的插件,特别是在https://community.home-assistant.io/t/share-your-floorplan/看到各路大神写的样式以后,发现简直不搞这个等于白玩hass啊。
最后一顿操作搞出来我现在的版本:
floorplan其实整体结构比较简单,就是首先一个html,然后我们自己画一个svg图,把图上的某某实体都绑定上数据,就可以了,其实这种图还挺方便的特别是看数据特别直观,哪个房间的温度,哪个设备运行了,在加上一个天气简直完美。
但是要承载这个图可能就需要一个设备了,放在入户,我在share里最喜欢的两个。
这个一看就是即实用又美观,首先数据又展示内容有,右面一排的情景控制解决了图上设备比较小不好控制的问题。
这个就是看着舒爽,用圆圈的颜色来区分等的开关状态。
做一个floorplan
注意虽然作者已经有2年没更新了,不过最近看到大神准备继续更新这个插件了。https://github.com/pkozul/ha-floorplan
大家还是参照大神写好的教程,我想想我来写也不会比这个更详细。
https://post.smzdm.com/p/597918/
我只能给大家几个我做过程中遇到的问题。
- 首先svg没什么比较好的编辑器,免费的Inkscape只能说凑合,我是用mac的一个画流程图一类的软件omni graffle来画,不过这个不是专门画svg的所有有个问题,就是不能设置实体id,为此我写了python脚本来替换,如果有需求可以找我单要,如果没有这个软件功底的不推荐实用。
- 画图尽量拆分好图层,比如第一层墙体,第二层地板,第三层家居,第四层设备。好处很多,特别是需要往复修改一个图纸的时候。
- 需要画户型图就比较耗时了大家有心里准备,我花了差不多3-4个小时,特别是我有详细尺寸的前提下。
- 作者推荐的酷家乐很好用,主要是有很多同小区户型该起来比较方便,但是只能导出原型图,墙体一类的还是要自己画。
最后给一下我的svg方便大家参考。
floorplan&lovelace
https://github.com/pkozul/lovelace-floorplan
最近也支持lovelace环境了,有需要的可以参照上面的说明。
Home Panel
最接近官方的插件,我用的也不多更多的是测试。
这个插件首先是按照所谓的区域方式拆分的,并且这个方式还不能取消。
看到图上的Scenes、Central Heatting、Weather、LivingRoot
这个插件比较简单,我是直接通过hass.io插件直接安装,安装好以后,需要配置映射端口地址。
配置好了直接启动,通过http://ip:8124就可以访问了,这里有个坑。
- home panle是独立的账号密码,需要先注册,然后在登陆。
- 登陆成功以后需要绑定到hass上然后再绑定。
Home Panel配置
hp的配置算是很简单的,非常有官方的风格,基本上可以不手写,完全通过界面配置,比如:
点击 edit config
就可以直接通过这个+号新增,数据绑定也非常简单,直接选择对应的实体就可以了。
可以配置icon、宽度高度等,甚至支持直接配置group,group的每一个插件独立拆分成一个。
而且支持,hass、link、camera、iframe四种模式的配置,简单粗暴。
当然也支持多个page,并且由于这个针对手机做过优化,所以手机、平板展示的也不难看,更适合移动设备操作。
tileboard
这个就更简单了,因为只是一个前端插件,所以只要配置几个前端相关的就可以,甚至都不需要重启hass。
https://github.com/resoai/TileBoard
去看作者写的readme.md其实已经写的比较清楚了。
我是觉得这个好看很多人想法不一样。
这个配置就一个超大的config.js通过很多js配置方式,把页面切割成多少个方块,然后指定如何填充,这个其实很简单,参考以下我的样式,以及我的config.js就可以配置。
/*
This is an example configuration file.
COPY OR RENAME THIS FILE TO config.js.
Make sure you use real IDs from your HA entities.
*/
var CONFIG = {
/* customTheme: specify a custom theme for your dashboard
* Valid options: null, CUSTOM_THEMES.TRANSPARENT, CUSTOM_THEMES.MATERIAL, CUSTOM_THEMES.MOBILE, CUSTOM_THEMES.COMPACT, CUSTOM_THEMES.HOMEKIT, CUSTOM_THEMES.WINPHONE, CUSTOM_THEMES.WIN95 or a custom theme you have created
* Default: null. Array supported
*/
customTheme: CUSTOM_THEMES.COMPACT,
/* transition: The transition effect used between Pages
* Valid options: TRANSITIONS.ANIMATED, TRANSITIONS.ANIMATED_GPU, TRANSITIONS.SIMPLE
*/
transition: TRANSITIONS.ANIMATED_GPU,
/* tileSize: The default size (in pixels) of a tile */
tileSize: 120,
/* tileMargin: The default margin (in pixels) between tiles */
tileMargin: 5,
/* entitySize: Enum size of tile's content (SMALL, NORMAL, BIG)*/
entitySize: ENTITY_SIZES.SMALL,
/* groupMarginCss: CSS margin statement to override the default margin for groups */
groupMarginCss: '10px 6px',
/* serverUrl: The URL to your HomeAssistant server */
serverUrl: "http://xxxx:8123",
/* wsUrl: The URL to your HomeAssistant Websocket connection.
* If HomeAssistant is behind SSL, replace ws:// with wss://
*/
wsUrl: "ws://xxxx:8123/api/websocket",
/* authToken: Optional Long live token that you can create in your HomeAssistant
*/
authToken: null,
/* pingConnection: Set to false disable pinging of the websocket connection.
* Otherwise, a ping will be sent every five seconds, and if a response is not received in 3 seconds,
* a reconnect will be attempted. If not included in the config file, setting defaults to true.
*/
pingConnection: true,
/* debug: Toggle for extra debugging information.
* If enabled, will print info about state changes and entities to console.
*/
debug: true,
/* timeFormat: 12 for AM/PM marker, 24 for 24 hour time (default) */
timeFormat: 24,
/* googleApiKey: Google API key is required if you are using device tracker tiles along with Google Maps.
* More info here: https://developers.google.com/maps/documentation/maps-static/usage-and-billing
*/
googleApiKey: null,
/* A Mapbox token is required if you are using device tracker tiles along with Mapbox.
* More info here: https://www.mapbox.com/maps/
*/
mapboxToken: null,
/* mapboxStyle: Enter a style URL to change the mapbox style for device tracker tiles.
* The format of the url is: mapbox://styles/username/style-id
* If no style URL is entered, the style will default to mapbox/streets-v11.
*/
mapboxStyle: null,
/* menuPosition: LEFT (default) or BOTTOM */
menuPosition: MENU_POSITIONS.LEFT,
/* hideScrollbar: Hiding horizontal scrollbar */
hideScrollbar: false,
/* groupsAlign: Align groups HORIZONTALLY (default) or VERTICALLY */
groupsAlign: GROUP_ALIGNS.HORIZONTALLY,
/* events: A list of events. See documentation on Events below */
events: [],
/* screensaver: A digital picture frame with a clock. Appears when
* the dashboard has been idle
* https://github.com/resoai/TileBoard/wiki/Screensaver-configuration
* (optional)
*/
screensaver: { // optional. https://github.com/resoai/TileBoard/wiki/Screensaver-configuration
timeout: 300,
// after 5 mins of inactive
slidesTimeout: 10,
// 10s for one slide
styles: {
fontSize: '40px'
},
leftBottom: [{
type: SCREENSAVER_ITEMS.DATETIME
}],
// put datetime to the left-bottom of screensaver
slides: [{
bg: 'images/bg1.jpeg'
},
{
bg: 'images/bg2.png',
rightTop: [ // put text to the 2nd slide
{
type: SCREENSAVER_ITEMS.CUSTOM_HTML,
html: 'Welcome to the <b>TileBoard</b>',
styles: {
fontSize: '40px'
}
}]
},
{
bg: 'images/bg3.jpg'
}]
},
header: { // https://github.com/resoai/TileBoard/wiki/Header-configuration
styles: {
padding: '10px 80px 0',
fontSize: '26px'
},
left: [{
type: HEADER_ITEMS.DATETIME,
dateFormat: 'EEEE, LLLL dd',
//https://docs.angularjs.org/api/ng/filter/date
}],
right: [
{
type: HEADER_ITEMS.CUSTOM_HTML,
html: 'Welcome to the <b>TileBoard</b>',
styles: {
margin: '0 0 0'
}
},
{
type: HEADER_ITEMS.WEATHER,
styles: {
margin: '0 0 0'
},
icon: '&weather.dieyuan.state',
icons: {
'sunny': 'clear',
'clear': 'clear',
'clear-night': 'nt-clear',
'cloudy': 'cloudy',
'rainy': 'rain',
'hail': 'rain',
'pouring': 'rain',
'lightning': 'thunder',
'lightning-rainy': 'thunder',
'sleet': 'sleet',
'snowy': 'snow',
'snowy-rainy': 'snowy-rainy',
'windy': 'hazy',
'windy-variant': 'hazy',
'fog': 'fog',
'partlycloudy': 'partlycloudy',
'partly-cloudy-night': 'nt-partlycloudy'
},
fields: {
// summary: 'summary',
temperature: '&weather.dieyuan.attributes.temperature',
temperatureUnit: '℃',
}
}]
},
pages: [{
title: 'Main page',
bg: 'images/bg1.jpeg',
icon: 'mdi-home-outline',
// home icon
groups: [{
title: '天气',
width: 2,
height: 4,
items: [{
// please read README.md for more information
// this is just an example
position: [0, 0],
height: 2,
// 1 for compact
width: 2,
classes: ['-compact'],
type: TYPES.WEATHER,
id: 'weather.dieyuan',
title: '西溪蝶园',
state: '&weather.dieyuan.state',
// label with weather summary (e.g. Sunny)
icon: '&weather.dieyuan.state',
// 天气状态定义
icons: {
'sunny': 'clear',
'clear': 'clear',
'clear-night': 'nt-clear',
'cloudy': 'cloudy',
'rainy': 'rain',
'hail': 'rain',
'pouring': 'rain',
'lightning': 'thunder',
'lightning-rainy': 'thunder',
'sleet': 'sleet',
'snowy': 'snow',
'snowy-rainy': 'snowy-rainy',
'windy': 'hazy',
'windy-variant': 'hazy',
'fog': 'fog',
'partlycloudy': 'partlycloudy',
'partly-cloudy-night': 'nt-partlycloudy'
},
fields: { // most of that fields are optional
summary: 'summary',
temperature: '&weather.dieyuan.attributes.temperature',
temperatureUnit: '℃',
windSpeed: '&weather.dieyuan.attributes.wind_speed',
windSpeedUnit: '千米/小时',
humidity: '&weather.dieyuan.attributes.humidity',
humidityUnit: '%',
list: ['紫外线强度(0-11) ' + '&sensor.ultraviolet.state', '下雨概率 ' + '&sensor.weather_current_rain.state %', '今天气温 ' + '&sensor.weather_today_min_temp.state-&sensor.weather_today_max_temp.state ℃']
}
},
{
position: [0, 2],
type: TYPES.WEATHER_LIST,
width: 2,
height: 2,
title: '',
id: {},
icons: {
'sunny': 'clear',
'clear': 'clear',
'clear-night': 'nt-clear',
'cloudy': 'cloudy',
'rainy': 'rain',
'hail': 'rain',
'pouring': 'rain',
'lightning': 'thunder',
'lightning-rainy': 'thunder',
'sleet': 'sleet',
'snowy': 'snow',
'snowy-rainy': 'snowy-rainy',
'windy': 'hazy',
'windy-variant': 'hazy',
'fog': 'fog',
'partlycloudy': 'partlycloudy',
'partly-cloudy-night': 'nt-partlycloudy'
},
hideHeader: false,
secondaryTitle: '降雨概率',
list: [0, 1, 2, 3, 4, 5, 6].map(function(id) {
var forecast = "&weather.dieyuan.attributes.hourly_forecast." + id + ".temperature";
forecast += "℃";
var datetime = "&weather.dieyuan.attributes.hourly_forecast." + id + ".datetime";
var probable_precipitation = "&weather.dieyuan.attributes.hourly_forecast." + id + ".probable_precipitation";
probable_precipitation += "%";
return {
date: ((((Math.round(new Date(Date.now()).getHours() / 3)) + id) * 3) + 1) % 24 + ":00",
icon: "&weather.dieyuan.attributes.hourly_forecast." + id + ".condition",
//iconImage: null, replace icon with image
primary: forecast,
secondary: probable_precipitation
}
}),
filter: function(value) { // optional
return 111;
}
},
{
position: [0,4],
type: TYPES.WEATHER_LIST,
width: 2,
height: 1,
title: '',
id: {},
icons: {
'sunny': 'clear',
'clear': 'clear',
'clear-night': 'nt-clear',
'cloudy': 'cloudy',
'rainy': 'rain',
'hail': 'rain',
'pouring': 'rain',
'lightning': 'thunder',
'lightning-rainy': 'thunder',
'sleet': 'sleet',
'snowy': 'snow',
'snowy-rainy': 'snowy-rainy',
'windy': 'hazy',
'windy-variant': 'hazy',
'fog': 'fog',
'partlycloudy': 'partlycloudy',
'partly-cloudy-night': 'nt-partlycloudy'
},
hideHeader: false,
secondaryTitle: '降雨概率',
list: [1, 2, 3, 4].map(function(id) {
var forecast = "&weather.dieyuan.attributes.forecast." + id + ".templow";
forecast += " - &weather.dieyuan.attributes.forecast." + id + ".temperature";
forecast += "℃";
// var datetime = "&weather.dieyuan.attributes.forecast." + id + ".datetime";
var probable_precipitation = "&weather.dieyuan.attributes.forecast." + id + ".probable_precipitation";
probable_precipitation += "%";
return {
date: function() {
var d = new Date(Date.now() + id * 24 * 60 * 60 * 1000);
// var d = new Date(Date.parse(datetime.replace(/-/g, '/')));
return d.toLocaleDateString('zh-Hans', {
weekday: 'short'
});
},
icon: "&weather.dieyuan.attributes.forecast." + id + ".condition",
//iconImage: null, replace icon with image
primary: forecast,
secondary: probable_precipitation
}
})
}
]
},
{
title: '基础信息',
width: 3,
height: 4,
items: [{
position: [0, 0],
width: 1,
type: TYPES.DEVICE_TRACKER,
id: 'device_tracker.xxx',
// using empty object for an unknown id
states: {
home: "在家",
not_home: "离开",
},
// bg: '/local/images/xxx.png'
},{
position: [1, 0],
width: 1,
type: TYPES.DEVICE_TRACKER,
id: 'device_tracker.xxx',
// using empty object for an unknown id
states: {
home: "在家",
not_home: "离开",
},
// bg: '/local/images/xxx.png'
},{
position: [2, 0],
width: 1,
type: TYPES.DEVICE_TRACKER,
id: 'device_tracker.xxx',
// using empty object for an unknown id
states: {
home: "在家",
not_home: "离开",
},
// bg: '/local/images/xxx.png'
},
{
position: [0, 1],
type: TYPES.DEVICE_TRACKER,
title: '洗衣机',
id: 'switch.plug_158d0002836ad3',
// unit: '℃',
// override default entity unit
state: function (item, entity) {
var load_power = parseFloat(entity.attributes.load_power);
if (load_power > 1)
return "运行";
else
return "关闭";
},
// hidding state
filter: function(value, item, entity) { // optional
return "";
},
bg: "/local/images/washer.png",
},
{
position: [1, 1],
type: TYPES.DEVICE_TRACKER,
title: '烘干机',
id: 'switch.plug_158d0002ecece4',
state: function (item, entity) {
var load_power = parseFloat(entity.attributes.load_power);
if (load_power > 1)
return "运行";
else
return "关闭";
},
filter: function(value, item, entity) { // optional
return "";
},
bg: "/local/images/dryer.png",
},
{
position: [2, 1],
type: TYPES.DEVICE_TRACKER,
title: '电视机',
id: 'device_tracker.letv_2',
states: {
home: "打开",
not_home: "关闭",
},
bg: "/local/images/tv.png",
},
{
position: [0, 2],
type: TYPES.SENSOR,
title: '主卧温湿度',
id: 'sensor.mithermometer_master_room_temperature',
state: '湿度 ' + "&sensor.mithermometer_master_room_humidity.state" + '%',
filter: function(value, item, entity) { // optional
var num = parseFloat(value);
return num && !isNaN(num) ? num.toFixed(1) : value;
}
},
{
position: [1, 2],
type: TYPES.SENSOR,
title: '机柜温湿度',
id: 'sensor.temperature_158d00033e78c1',
state: '湿度 ' + "&sensor.humidity_158d00033e78c1.state" + '%',
filter: function(value, item, entity) { // optional
var num = parseFloat(value);
return num && !isNaN(num) ? num.toFixed(1) : value;
}
},
{
position: [2, 2],
type: TYPES.SENSOR,
title: '主卫温湿度',
id: 'sensor.temperature_158d00036311c9',
state: '湿度 ' + "&sensor.humidity_158d00036311c9.state" + '%',
filter: function(value, item, entity) { // optional
var num = parseFloat(value);
return num && !isNaN(num) ? num.toFixed(1) : value;
}
},
{
position: [0, 3],
type: TYPES.SENSOR,
title: '父母房温湿度',
id: 'sensor.temperature_158d0003a404db',
state: '湿度 ' + "&sensor.humidity_158d0003a404db.state" + '%',
filter: function(value, item, entity) { // optional
var num = parseFloat(value);
return num && !isNaN(num) ? num.toFixed(1) : value;
}
},
{
position: [1, 3],
type: TYPES.SENSOR,
title: '儿童房温湿度',
id: 'sensor.temperature_158d00034f6314',
state: '湿度 ' + "&sensor.humidity_158d00034f6314.state" + '%',
filter: function(value, item, entity) { // optional
var num = parseFloat(value);
return num && !isNaN(num) ? num.toFixed(1) : value;
}
},
{
position: [2, 3],
type: TYPES.SENSOR,
title: '厨房温湿度',
id: 'sensor.temperature_158d0003a404c1',
state: '湿度 ' + "&sensor.humidity_158d0003a404c1.state" + '%',
filter: function(value, item, entity) { // optional
var num = parseFloat(value);
return num && !isNaN(num) ? num.toFixed(1) : value;
}
},
{
position: [0, 4],
type: TYPES.SENSOR,
title: '客厅温湿度',
id: 'sensor.siements_dock_temperature',
state: '湿度 ' + "&sensor.siements_dock_humidity.state" + '%',
filter: function(value, item, entity) { // optional
var num = parseFloat(value);
return num && !isNaN(num) ? num.toFixed(1) : value;
}
},
{
position: [1, 4],
type: TYPES.SENSOR,
title: '客厅PM2.5',
id: 'sensor.siements_dock_pm2_5',
state: 'PM10 ' + "&sensor.siements_dock_pm10.state" +
"&sensor.siements_dock_pm10.attributes.unit_of_measurement",
filter: function(value, item, entity) { // optional
var num = parseFloat(value);
return num && !isNaN(num) ? num.toFixed(1) : value;
}
},
{
position: [2, 4],
type: TYPES.SENSOR,
title: '客厅甲醛',
id: 'sensor.siements_dock_hcho',
// state: '湿度 ' + "&sensor.siements_dock_humidity.state" + '%',
filter: function(value, item, entity) { // optional
var num = parseFloat(value);
return num && !isNaN(num) ? num.toFixed(1) : value;
}
}]
},
{
title: '设备控制',
width: 2,
height: 4,
items: [{
position: [0, 0],
width: 1,
type: TYPES.FAN,
id: "fan.airx",
// replace it with real string id
state: false,
title: '空气净化器',
},
{
position: [1, 0],
width: 1,
type: TYPES.SWITCH,
id: "switch.switch_door",
// replace it with real string id (e.g. "switch.lights")
state: true,
title: '衣柜灯',
subtitle: '主卧',
states: {
on: "On",
off: "Off"
},
icons: {
on: "mdi-lightbulb-on",
off: "mdi-lightbulb",
},
},
{
position: [0, 1],
type: TYPES.ALARM,
//id: "alarm_control_panel.home_alarm",
id: {
state: 'disarmed'
},
// replace it with real string id
title: 'Home Alarm',
icons: {
disarmed: 'mdi-bell-off',
pending: 'mdi-bell',
armed_home: 'mdi-bell-plus',
armed_away: 'mdi-bell',
triggered: 'mdi-bell-ring'
},
states: {
disarmed: 'Disarmed',
pending: 'Pending',
armed_home: 'Armed home',
armed_away: 'Armed away',
triggered: 'Triggered'
}
},
{
position: [1, 1],
type: TYPES.SENSOR,
title: '主卧温度',
id: 'sensor.mithermometer_master_room_temperature',
// unit: '℃',
// override default entity unit
state: true,
// hidding state
filter: function(value) { // optional
var num = parseFloat(value);
return num && !isNaN(num) ? num.toFixed(1) : value;
}
}]
},
]
},
{
title: 'Second page',
bg: 'images/bg2.png',
icon: 'mdi-numeric-2-box-outline',
groups: [{
title: '',
width: 2,
height: 3,
items: [{
position: [0, 0],
width: 2,
title: 'Short instruction',
type: TYPES.TEXT_LIST,
id: {},
// using empty object for an unknown id
state: false,
// disable state element
list: [{
title: 'Read',
icon: 'mdi-numeric-1-box-outline',
value: 'README.md'
},
{
title: 'Ask on forum',
icon: 'mdi-numeric-2-box-outline',
value: 'home-assistant.io'
},
{
title: 'Open an issue',
icon: 'mdi-numeric-3-box-outline',
value: 'github.com'
}]
}]
},
]
}],
}
如果了解js那写起来轻松愉快。
这里有一个注意点:
- state、filter这两个即可以返回一个function也可以是一个值,只有是值的时候才处理&符号数据替换
- 不要照抄我每一行,这个不复杂一行一行看一下配置,既然我是测试所以几种常见的情况我都配置了。
由于没找到合适的PAD所以这三种我都是实验,都不太深入,主要是没使用场景,设备太少了。