* Tweaks

* Lint

* No need for copy plugin

* Allow handling more complex service calls

* Missed a state

* Add locks

* Lint

* Add cover entity

* Make generic entity constructor

* Light to handle homeassistant.X services

* Lint

* Fix translations

* final tweaks
This commit is contained in:
Paulus Schoutsen 2018-07-22 11:41:56 +02:00 committed by GitHub
parent 3b2d4de313
commit bdf26bbccd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 286 additions and 24 deletions

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -48,6 +48,10 @@ class DemoCard extends PolymerElement {
static get properties() {
return {
hass: {
type: Object,
observer: '_hassChanged',
},
config: {
type: Object,
observer: '_configChanged'
@ -62,17 +66,27 @@ class DemoCard extends PolymerElement {
card.removeChild(card.lastChild);
}
const hass = new HomeAssistant();
hass.config = demoConfig;
hass.resources = demoResources;
hass.language = 'en';
hass.states = demoStates;
const el = createCardElement(JsYaml.safeLoad(config.config)[0]);
el.hass = hass;
if (this.hass) {
el.hass = this.hass;
} else {
const hass = new HomeAssistant(demoStates);
hass.config = demoConfig;
hass.resources = demoResources;
hass.language = 'en';
hass.states = demoStates;
el.hass = hass;
}
card.appendChild(el);
}
_hassChanged(hass) {
const card = this.$.card.lastChild;
if (card) card.hass = hass;
}
_trim(config) {
return config.trim();
}

View File

@ -36,6 +36,7 @@ class DemoCards extends PolymerElement {
<demo-card
config='[[item]]'
show-config='[[showConfig]]'
hass='[[hass]]'
></demo-card>
</template>
</div>
@ -45,6 +46,7 @@ class DemoCards extends PolymerElement {
static get properties() {
return {
configs: Object,
hass: Object,
showConfig: {
type: Boolean,
value: false,

116
gallery/src/data/entity.js Normal file
View File

@ -0,0 +1,116 @@
const now = () => new Date().toISOString();
const randomTime = () =>
new Date(new Date().getTime() - (Math.random() * 80 * 60 * 1000)).toISOString();
/* eslint-disable no-unused-vars */
export class Entity {
constructor(domain, objectId, state, baseAttributes) {
this.domain = domain;
this.objectId = objectId;
this.entityId = `${domain}.${objectId}`;
this.lastChanged = randomTime();
this.lastUpdated = randomTime();
this.state = state;
// These are the attributes that we always write to the state machine
this.baseAttributes = baseAttributes;
this.attributes = baseAttributes;
}
async handleService(domain, service, data) {
console.log(`Unmocked service for ${this.entityId}: ${domain}/${service}`, data);
}
update(state, attributes = {}) {
this.state = state;
this.lastUpdated = now();
this.lastChanged = state === this.state ? this.lastChanged : this.lastUpdated;
this.attributes = Object.assign({}, this.baseAttributes, attributes);
console.log('update', this.entityId, this);
this.hass.updateStates({
[this.entityId]: this.toState()
});
}
toState() {
return {
entity_id: this.entityId,
state: this.state,
attributes: this.attributes,
last_changed: this.lastChanged,
last_updated: this.lastUpdated,
};
}
}
export class LightEntity extends Entity {
async handleService(domain, service, data) {
if (!['homeassistant', this.domain].includes(domain)) return;
if (service === 'turn_on') {
// eslint-disable-next-line
const { brightness, hs_color } = data;
this.update('on', Object.assign(this.attributes, {
brightness,
hs_color,
}));
} else if (service === 'turn_off') {
this.update('off');
} else if (service === 'toggle') {
if (this.state === 'on') {
this.handleService(domain, 'turn_off', data);
} else {
this.handleService(domain, 'turn_on', data);
}
}
}
}
export class LockEntity extends Entity {
async handleService(domain, service, data) {
if (domain !== this.domain) return;
if (service === 'lock') {
this.update('locked');
} else if (service === 'unlock') {
this.update('unlocked');
}
}
}
export class CoverEntity extends Entity {
async handleService(domain, service, data) {
if (domain !== this.domain) return;
if (service === 'open_cover') {
this.update('open');
} else if (service === 'close_cover') {
this.update('closing');
}
}
}
export class GroupEntity extends Entity {
async handleService(domain, service, data) {
if (!['homeassistant', this.domain].includes(domain)) return;
await Promise.all(this.attributes.entity_id.map((ent) => {
const entity = this.hass.mockEntities[ent];
return entity.handleService(entity.domain, service, data);
}));
this.update(service === 'turn_on' ? 'on' : 'off');
}
}
const TYPES = {
light: LightEntity,
lock: LockEntity,
cover: CoverEntity,
group: GroupEntity,
};
export default (domain, objectId, state, baseAttributes = {}) =>
new (TYPES[domain] || Entity)(domain, objectId, state, baseAttributes);

View File

@ -1,6 +1,6 @@
export default class FakeHass {
constructor() {
this.states = {};
constructor(states = {}) {
this.states = states;
this._wsCommands = {};
}

View File

@ -0,0 +1,79 @@
import fireEvent from '../../../src/common/dom/fire_event.js';
import demoConfig from './demo_config.js';
import demoResources from './demo_resources.js';
const ensureArray = val => (Array.isArray(val) ? val : [val]);
export default (elements, { initialStates = {} } = {}) => {
elements = ensureArray(elements);
const wsCommands = {};
let hass;
const entities = {};
function updateHass(obj) {
hass = Object.assign({}, hass, obj);
elements.forEach((el) => { el.hass = hass; });
}
updateHass({
// Home Assistant properties
config: demoConfig,
language: 'en',
resources: demoResources,
states: initialStates,
// Mock properties
mockEntities: entities,
// Home Assistant functions
async callService(domain, service, data) {
fireEvent(elements[0], 'show-notification', { message: `Called service ${domain}/${service}` });
if (data.entity_id) {
await Promise.all(ensureArray(data.entity_id).map(ent =>
entities[ent].handleService(domain, service, data)));
} else {
console.log('unmocked callService', domain, service, data);
}
},
async callWS(msg) {
const callback = wsCommands[msg.type];
return callback ? callback(msg) : Promise.reject({
code: 'command_not_mocked',
message: 'This command is not implemented in the gallery.',
});
},
async sendWS(msg) {
const callback = wsCommands[msg.type];
if (callback) {
callback(msg);
} else {
console.error(`Unknown command: ${msg.type}`);
}
console.log('sendWS', msg);
},
// Mock functions
updateHass,
updateStates(newStates) {
updateHass({
states: Object.assign({}, hass.states, newStates),
});
},
addEntities(newEntities) {
const states = {};
ensureArray(newEntities).forEach((ent) => {
ent.hass = hass;
entities[ent.entityId] = ent;
states[ent.entityId] = ent.toState();
});
this.updateStates(states);
}
});
return hass;
};

View File

@ -1,8 +1,45 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import getEntity from '../data/entity.js';
import provideHass from '../data/provide_hass.js';
import '../components/demo-cards.js';
const ENTITIES = [
getEntity('light', 'bed_light', 'on', {
friendly_name: 'Bed Light'
}),
getEntity('group', 'kitchen', 'on', {
entity_id: [
'light.bed_light',
],
order: 8,
friendly_name: 'Kitchen'
}),
getEntity('lock', 'kitchen_door', 'locked', {
friendly_name: 'Kitchen Door'
}),
getEntity('cover', 'kitchen_window', 'open', {
friendly_name: 'Kitchen Window',
supported_features: 11
}),
getEntity('scene', 'romantic_lights', 'scening', {
entity_id: [
'light.bed_light',
'light.ceiling_lights'
],
friendly_name: 'Romantic lights'
}),
getEntity('device_tracker', 'demo_paulus', 'home', {
source_type: 'gps',
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: 'Paulus'
}),
];
const CONFIGS = [
{
heading: 'Basic',
@ -48,7 +85,7 @@ const CONFIGS = [
`
},
{
heading: 'With title, cant\'t toggle',
heading: 'With title, can\'t toggle',
config: `
- type: entities
entities:
@ -80,7 +117,11 @@ const CONFIGS = [
class DemoEntities extends PolymerElement {
static get template() {
return html`
<demo-cards configs="[[_configs]]"></demo-cards>
<demo-cards
id='demos'
hass='[[hass]]'
configs="[[_configs]]"
></demo-cards>
`;
}
@ -89,9 +130,16 @@ class DemoEntities extends PolymerElement {
_configs: {
type: Object,
value: CONFIGS
}
},
hass: Object,
};
}
ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
customElements.define('demo-hui-entities-card', DemoEntities);

View File

@ -9,6 +9,8 @@ import '@polymer/paper-icon-button/paper-icon-button.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import '../../src/managers/notification-manager.js';
const demos = require.context('./demos', true, /^(.*\.(js$))[^.]*$/im);
const fixPath = path => path.substr(2, path.length - 5);
@ -91,6 +93,7 @@ class HaGallery extends PolymerElement {
</template>
</div>
</app-header-layout>
<notification-manager id='notifications'></notification-manager>
`;
}
@ -111,8 +114,15 @@ class HaGallery extends PolymerElement {
ready() {
super.ready();
this.addEventListener(
'show-notification',
ev => this.$.notifications.showNotification(ev.detail.message)
);
this.addEventListener('hass-more-info', (ev) => {
if (ev.detail.entityId) alert(`Showing more info for ${ev.detail.entityId}`);
if (ev.detail.entityId) {
this.$.notifications.showNotification(`Showing more info for ${ev.detail.entityId}`);
}
});
window.addEventListener('hashchange', () => { this._demo = document.location.hash.substr(1); });

View File

@ -49,12 +49,11 @@ module.exports = {
plugins: [
new CopyWebpackPlugin([
'public',
{ from: '../public', to: 'static' },
{ from: '../build-translations/output', to: 'static/translations' },
{ from: '../node_modules/leaflet/dist/leaflet.css', to: 'static/images/leaflet/' },
{ from: '../node_modules/@polymer/font-roboto-local/fonts', to: 'static/fonts' },
{ from: '../node_modules/leaflet/dist/images', to: 'static/images/leaflet/' },
{ from: './src/data/media_player_images/media_player.bedroom.jpg', to: 'api/media_player_proxy/media_player.bedroom' },
{ from: './src/data/media_player_images/media_player.living_room.jpg', to: 'api/media_player_proxy/media_player.living_room' },
{ from: './src/data/media_player_images/media_player.walkman.jpg', to: 'api/media_player_proxy/media_player.walkman' },
]),
isProd && new UglifyJsPlugin({
extractComments: true,

View File

@ -13,7 +13,7 @@ class NotificationManager extends LocalizeMixin(PolymerElement) {
}
</style>
<paper-toast id="toast" text="[[_text]]" no-cancel-on-outside-click="[[_cancelOnOutsideClick]]"></paper-toast>
<paper-toast id="toast" no-cancel-on-outside-click="[[_cancelOnOutsideClick]]"></paper-toast>
<paper-toast id="connToast" duration="0" text="[[localize('ui.notification_toast.connection_lost')]]" opened="[[connectionLost]]"></paper-toast>
`;
}
@ -40,11 +40,6 @@ class NotificationManager extends LocalizeMixin(PolymerElement) {
value: false,
},
_text: {
type: String,
readOnly: true,
},
toastClass: {
type: String,
value: '',
@ -90,8 +85,7 @@ class NotificationManager extends LocalizeMixin(PolymerElement) {
}
showNotification(message) {
this._set_text(message);
this.$.toast.show();
this.$.toast.show(message);
}
}