Big restructuring - repo is now a full home assistant config directory

This commit is contained in:
Martin Bauer
2019-06-30 19:37:32 +02:00
parent d35b9e132e
commit 808727ad92
79 changed files with 14378 additions and 27 deletions

View File

@@ -0,0 +1,7 @@
To install dependencies run:
npm install
To build:
npm run build
copy file from dist folder

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
{
"name": "state-card-custom-cover",
"version": "0.0.1",
"description": "Cover card from home assistant",
"main": "src/state-card-custom-cover.ts",
"scripts": {
"build": "webpack",
"test": "echo \"Error: no test specified\" && exit 1"
},
"private": true,
"author": "Martin Bauer",
"license": "GPLv3",
"devDependencies": {
"home-assistant-js-websocket": "^4.2.2",
"lit-element": "^2.1.0",
"ts-loader": "^6.0.2",
"typescript": "^3.5.1",
"webpack": "^4.33.0",
"webpack-cli": "^3.3.4"
}
}

View File

@@ -0,0 +1,26 @@
import {Connection, HassConfig, HassEntities, HassServices, MessageBase} from "home-assistant-js-websocket";
export interface HomeAssistant {
connection: Connection;
connected: boolean;
states: HassEntities;
services: HassServices;
config: HassConfig;
callService: (
domain: string,
service: string,
serviceData?: { [key: string]: any }
) => Promise<void>;
callApi: <T>(
method: "GET" | "POST" | "PUT" | "DELETE",
path: string,
parameters?: { [key: string]: any }
) => Promise<T>;
fetchWithAuth: (
path: string,
init?: { [key: string]: any }
) => Promise<Response>;
sendWS: (msg: MessageBase) => void;
callWS: <T>(msg: MessageBase) => Promise<T>;
}

View File

@@ -0,0 +1,208 @@
import {LitElement, html, css, customElement, property, TemplateResult, CSSResult} from "lit-element";
import {classMap} from "lit-html/directives/class-map";
import {HomeAssistant} from "./home-assistant-interface";
@customElement("room-glance-card")
export class RoomGlanceCard extends LitElement {
@property() public hass?: HomeAssistant;
@property() private _config?: any;
public setConfig(config: any) {
//if (!config || !config.entity) {
// throw new Error("Invalid configuration");
//}
console.log("Setting config", config);
this._config = config;
}
public static getCardSize(): number {
return 3;
}
protected render(): TemplateResult | void {
return html`
<ha-card>
<hui-image
class="${classMap({
clickable: true,
})}"
@ha-click="${this._handleTap}"
@ha-hold="${this._handleHold}"
.hass="${this.hass}"
image="${this._config.image}"
aspectRatio="${this._config.aspect_ratio}"
></hui-image>
<div class="box box-upper">
<div>${this._config.name}</div>
<div>
${this.renderCoverControl()}
</div>
</div>
<div class="box box-lower">
<div class="title"></div>
<div>
${this._config.scenes.map( (sceneButtonCfg) => this.renderSceneButton(sceneButtonCfg))}
<paper-icon-button
icon="mdi:close-circle"
@click=${this.serviceHandler("light", "turn_off")}
></paper-icon-button>
<paper-icon-button
icon="mdi:chevron-up"
title="Heller"
@click=${this.serviceHandler("dimmer", "dim", {offset: 30})}
></paper-icon-button>
<paper-icon-button
icon="mdi:chevron-down"
title="Dunkler"
@click=${this.serviceHandler("dimmer", "dim", {offset: -30})}
></paper-icon-button>
</div>
</div>
</ha-card>
`;
}
private renderSceneButton(buttonCfg: any) {
return html`
<paper-icon-button
icon="${buttonCfg.icon || "mdi:checkbox-blank"}"
style="color: ${buttonCfg.color || ""};"
title="${buttonCfg.name}"
@click=${this.serviceHandler("scene", "turn_on", {entity_id: buttonCfg.scene})}
></paper-icon-button>
`;
}
private renderCoverControl() {
return html`
<paper-icon-button
icon="hass:menu"
@click=${this.serviceHandler("cover_half", "set_half")}
></paper-icon-button>
<paper-icon-button
icon="hass:arrow-up"
@click=${this.serviceHandler("cover", "open_cover")}
></paper-icon-button>
<paper-icon-button
icon="hass:stop"
@click=${this.serviceHandler("cover", "stop_cover")}
></paper-icon-button>
<paper-icon-button
icon="hass:arrow-down"
@click=${this.serviceHandler("cover", "close_cover")}
></paper-icon-button>
`;
}
private serviceHandler(domain: string, service: string, serviceData: { [key: string]: any } = {})
{
const thisObj = this;
return function(ev) {
ev.stopPropagation();
thisObj.callServiceForAllEntities(domain, service, serviceData);
}
}
private callServiceForAllEntities(domain: string, service:string, serviceData: { [key: string]: any } = {}) {
if (!serviceData.hasOwnProperty('entity_id')) {
for (let entity_id of this._config!.entities) {
serviceData['entity_id'] = entity_id;
console.log("Calling service", domain, service, serviceData);
this.hass!.callService(domain, service, serviceData);
}
} else {
console.log("Calling service with given entity_id", domain, service, serviceData);
this.hass!.callService(domain, service, serviceData);
}
}
private _handleTap() {
console.log("Image tap");
}
private _handleHold() {
console.log("Image hold");
}
static get styles(): CSSResult {
return css`
ha-card {
position: relative;
min-height: 48px;
overflow: hidden;
}
hui-image.clickable {
cursor: pointer;
}
.box {
/* start paper-font-common-nowrap style */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
/* end paper-font-common-nowrap style */
position: absolute;
left: 0;
right: 0;
padding-left: 8px;
padding-right: 8px;
font-size: 16px;
line-height: 40px;
color: white;
display: flex;
justify-content: space-between;
background-color: rgba(0, 0, 0, 0.3);
}
.box-upper {
bottom: 45px;
padding-top: 4px;
padding-bottom: 0;
}
.box-lower {
bottom: 0;
padding-top: 0;
padding-bottom: 0px;
height:45px;
}
.box .title {
font-weight: 500;
margin-left: 8px;
}
ha-icon {
cursor: pointer;
padding: 8px;
color: #a9a9a9;
}
ha-icon.state-on {
color: white;
}
`;
}
}

View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es2017",
"module": "esnext",
"moduleResolution": "node",
"lib": [
"es2017",
"dom",
"dom.iterable"
],
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"strict": true,
"noImplicitAny": false,
"skipLibCheck": true,
"resolveJsonModule": true,
"experimentalDecorators": true
}
}

View File

@@ -0,0 +1,19 @@
const path = require('path');
module.exports = {
entry: "./src/room-glance-card.ts",
output: {
filename: 'room-glance-card.js',
path: path.resolve(__dirname, 'dist')
},
resolve: {
extensions: [".tsx", ".ts", ".js", ".json"]
},
module: {
rules: [
// all files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'
{test: /\.tsx?$/, use: ["ts-loader"], exclude: /node_modules/}
]
},
mode: "production"
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
{
"name": "state-card-custom-cover",
"version": "0.0.1",
"description": "Cover card from home assistant",
"main": "src/state-card-custom-cover.ts",
"scripts": {
"build": "webpack",
"test": "echo \"Error: no test specified\" && exit 1"
},
"private": true,
"author": "Martin Bauer",
"license": "GPLv3",
"devDependencies": {
"home-assistant-js-websocket": "^4.2.2",
"lit-element": "^2.1.0",
"ts-loader": "^6.0.2",
"typescript": "^3.5.1",
"webpack": "^4.33.0",
"webpack-cli": "^3.3.4"
}
}

View File

@@ -0,0 +1,168 @@
import {
HassEntity,
} from "home-assistant-js-websocket";
import {HomeAssistant} from "./home-assistant-interface";
const supportsFeature = (
stateObj: HassEntity,
feature: number
): boolean => {
// tslint:disable-next-line:no-bitwise
return (stateObj.attributes.supported_features! & feature) !== 0;
};
/* eslint-enable no-bitwise */
export class CoverEntity {
public hass: HomeAssistant;
public stateObj: HassEntity;
private _attr: { [key: string]: any; };
private _feat: any;
constructor(hass : HomeAssistant, stateObj) {
this.hass = hass;
this.stateObj = stateObj;
this._attr = stateObj.attributes;
this._feat = this._attr.supported_features;
}
get isFullyOpen() {
if (this._attr.current_position !== undefined) {
return this._attr.current_position === 100;
}
return this.stateObj.state === "open";
}
get isFullyClosed() {
if (this._attr.current_position !== undefined) {
return this._attr.current_position === 0;
}
return this.stateObj.state === "closed";
}
get isFullyOpenTilt() {
return this._attr.current_tilt_position === 100;
}
get isFullyClosedTilt() {
return this._attr.current_tilt_position === 0;
}
get isOpening() {
return this.stateObj.state === "opening";
}
get isClosing() {
return this.stateObj.state === "closing";
}
get supportsOpen() {
return supportsFeature(this.stateObj, 1);
}
get supportsClose() {
return supportsFeature(this.stateObj, 2);
}
get supportsSetPosition() {
return supportsFeature(this.stateObj, 4);
}
get supportsStop() {
return supportsFeature(this.stateObj, 8);
}
get supportsOpenTilt() {
return supportsFeature(this.stateObj, 16);
}
get supportsCloseTilt() {
return supportsFeature(this.stateObj, 32);
}
get supportsStopTilt() {
return supportsFeature(this.stateObj, 64);
}
get supportsSetTiltPosition() {
return supportsFeature(this.stateObj, 128);
}
get isTiltOnly() {
const supportsCover =
this.supportsOpen || this.supportsClose || this.supportsStop;
const supportsTilt =
this.supportsOpenTilt || this.supportsCloseTilt || this.supportsStopTilt;
return supportsTilt && !supportsCover;
}
openCover() {
this.callService("open_cover");
}
closeCover() {
this.callService("close_cover");
}
stopCover() {
this.callService("stop_cover");
}
openCoverTilt() {
this.callService("open_cover_tilt");
}
closeCoverTilt() {
this.callService("close_cover_tilt");
}
stopCoverTilt() {
this.callService("stop_cover_tilt");
}
setCoverPosition(position) {
this.callService("set_cover_position", { position });
}
setCoverTiltPosition(tiltPosition) {
this.callService("set_cover_tilt_position", {
tilt_position: tiltPosition,
});
}
// helper method
callService(service, data = {}) {
data['entity_id'] = this.stateObj.entity_id;
this.hass.callService("cover", service, data);
}
}
export const supportsOpen = (stateObj) => supportsFeature(stateObj, 1);
export const supportsClose = (stateObj) => supportsFeature(stateObj, 2);
export const supportsSetPosition = (stateObj) => supportsFeature(stateObj, 4);
export const supportsStop = (stateObj) => supportsFeature(stateObj, 8);
export const supportsOpenTilt = (stateObj) => supportsFeature(stateObj, 16);
export const supportsCloseTilt = (stateObj) => supportsFeature(stateObj, 32);
export const supportsStopTilt = (stateObj) => supportsFeature(stateObj, 64);
export const supportsSetTiltPosition = (stateObj) =>
supportsFeature(stateObj, 128);
export function isTiltOnly(stateObj) {
const supportsCover =
supportsOpen(stateObj) || supportsClose(stateObj) || supportsStop(stateObj);
const supportsTilt =
supportsOpenTilt(stateObj) ||
supportsCloseTilt(stateObj) ||
supportsStopTilt(stateObj);
return supportsTilt && !supportsCover;
}

View File

@@ -0,0 +1,26 @@
import {Connection, HassConfig, HassEntities, HassServices, MessageBase} from "home-assistant-js-websocket";
export interface HomeAssistant {
connection: Connection;
connected: boolean;
states: HassEntities;
services: HassServices;
config: HassConfig;
callService: (
domain: string,
service: string,
serviceData?: { [key: string]: any }
) => Promise<void>;
callApi: <T>(
method: "GET" | "POST" | "PUT" | "DELETE",
path: string,
parameters?: { [key: string]: any }
) => Promise<T>;
fetchWithAuth: (
path: string,
init?: { [key: string]: any }
) => Promise<Response>;
sendWS: (msg: MessageBase) => void;
callWS: <T>(msg: MessageBase) => Promise<T>;
}

View File

@@ -0,0 +1,140 @@
import {LitElement, html, customElement, property, TemplateResult} from "lit-element";
import {CoverEntity} from "./cover-model"
import {HomeAssistant} from "./home-assistant-interface";
export interface CustomCoverStateCardConfig {
type: string;
entity: string;
name?: string;
}
@customElement("state-card-custom-cover")
export class StateCardCustomCover extends LitElement {
@property() public hass?: HomeAssistant;
@property() private _config?: CustomCoverStateCardConfig;
@property()
public get stateObj() {
if (this.hass && this._config)
return this.hass.states[this._config.entity];
else
return null;
}
@property() public inDialog: boolean = false;
public setConfig(config: CustomCoverStateCardConfig | undefined) {
if (!config || !config.entity) {
throw new Error("Invalid configuration");
}
this._config = config;
}
get entityObj(): CoverEntity {
if (this.hass && this.stateObj)
return new CoverEntity(this.hass, this.stateObj);
else
throw new Error("Trying to get entityObj before setting hass & config");
}
public static getCardSize(): number {
return 1;
}
protected stateInfoTemplate() {
return html`
State info template
`;
}
protected render(): TemplateResult | void {
if (!this._config || !this.hass) {
return html``;
}
const entityObj = this.entityObj;
const stateObj = this.hass.states[this._config.entity];
if (!stateObj) {
return html`
<hui-warning
>Does not work :(</hui-warning
>
`;
}
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
:host {
line-height: 1.5;
}
.state {
white-space: nowrap;
}
[invisible] {
visibility: hidden !important;
}
</style>
<hui-generic-entity-row .config="${this._config}" .hass="${this.hass}">
<div class="state">
<paper-icon-button
icon="hass:menu"
@click=${this.onHalfOpenTap}
></paper-icon-button>
<paper-icon-button
icon="hass:arrow-up"
@click=${this.onOpenTap}
.disabled="${this.computeOpenDisabled(stateObj, this.entityObj)}"
></paper-icon-button>
<paper-icon-button
icon="hass:stop"
@click=${this.onStopTap}
></paper-icon-button>
<paper-icon-button
icon="hass:arrow-down"
@click=${this.onCloseTap}
.disabled="${this.computeClosedDisabled(stateObj, entityObj)}"
></paper-icon-button>
</div>
</hui-generic-entity-row>
`;
}
computeOpenDisabled(stateObj, entityObj) {
const assumedState = stateObj.attributes.assumed_state === true;
return (entityObj.isFullyOpen || entityObj.isOpening) && !assumedState;
}
computeClosedDisabled(stateObj, entityObj) {
const assumedState = stateObj.attributes.assumed_state === true;
return (entityObj.isFullyClosed || entityObj.isClosing) && !assumedState;
}
private onHalfOpenTap(ev) {
const stateObj = this.hass!.states[this._config!.entity];
ev.stopPropagation();
this.entityObj.setCoverPosition(stateObj.attributes['half_position']);
}
private onOpenTap(ev) {
ev.stopPropagation();
this.entityObj.openCover();
}
private onCloseTap(ev) {
ev.stopPropagation();
this.entityObj.closeCover();
}
private onStopTap(ev) {
ev.stopPropagation();
this.entityObj.stopCover();
}
}

View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es2017",
"module": "esnext",
"moduleResolution": "node",
"lib": [
"es2017",
"dom",
"dom.iterable"
],
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"strict": true,
"noImplicitAny": false,
"skipLibCheck": true,
"resolveJsonModule": true,
"experimentalDecorators": true
}
}

View File

@@ -0,0 +1,19 @@
const path = require('path');
module.exports = {
entry: "./src/state-card-custom-cover.ts",
output: {
filename: 'state-card-custom-cover.js',
path: path.resolve(__dirname, 'dist')
},
resolve: {
extensions: [".tsx", ".ts", ".js", ".json"]
},
module: {
rules: [
// all files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'
{test: /\.tsx?$/, use: ["ts-loader"], exclude: /node_modules/}
]
},
mode: "production"
}