Skip to content

Commit

Permalink
Implement and document new card layout
Browse files Browse the repository at this point in the history
  • Loading branch information
Magnus Larsson committed Jan 6, 2020
1 parent 209ed1b commit 4c8b065
Show file tree
Hide file tree
Showing 7 changed files with 408 additions and 108 deletions.
64 changes: 35 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,55 @@
vasttrafik-card
========================

Styled entities using the Västtrafik theme in a lovelace entities card. All trams and buses are styled using the colours used in Göteborg, so if you are living in Västra Götaland but outside of Göteborg you have to change the css manually.
Styled entities using the Västtrafik theme in a lovelace entities card. All trams and buses are styled using the colours used in Göteborg, so if you are living in Västra Götaland but outside of Göteborg you have to change the css manually. This card also displays:
* When the next vehicle is leaving
* Departing station (which requires [this version](https://github.com/Miicroo/ha-vasttrafik) of the Västtrafik sensor)
* When you have to leave home in order to catch the vehicle (or the amount of minutes until the vehicle leaves if no delay is set).

> ![v1.0.0](resources/info.svg)
>
> If you are looking for the Västtrafik-card where you can *group* sensors based on departure or destination, you want the (discontinued) [v1.0.0](https://github.com/Miicroo/lovelace-vasttrafik-card/releases/tag/v1.0.0)
## Options
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| entities | list | **Required** | See [entity format](https://github.com/Miicroo/lovelace-vasttrafik-card#entity-format)
| title | string | Västtrafik | The title of the card

## Entity format
Entities can be defined in 3 different ways:

1) As a simple string containing the id
```yaml
- sensor.ekedal_till_brunnsparken
- sensor.godhemsgatan_till_brunnsparken
```
2) As an object containing the id and the delay in minutes (e.g. how long it would take you to walk to the departing station)
```yaml
- id: sensor.ekedal_till_brunnsparken
delay: 3
- sensor.godhemsgatan_till_brunnsparken
delay: 2
```
3) As a combination of 1) and 2), the entities without specified delay will get delay = 0.
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| entities | list | **Required** | Entity ids of the Västtrafik sensors
| title | string | Västtrafik | The title of the card
| groupBy | string | null | Groups sensors based on `departure` or `destination`
## Examples
```yaml
type: 'custom:vasttrafik-card'
title: 'Valand <-> Hjalmar Brantingsplatsen'
title: 'To Valand'
entities:
- sensor.fran_valand
- sensor.fran_hjalmar
- id: sensor.ekedal_till_brunnsparken
delay: 3
- sensor.godhemsgatan_till_brunnsparken
```
![Example 1](https://raw.githubusercontent.com/Miicroo/ha-lovelace-vasttrafik_card/master/resources/1.png)
![Example 2](https://raw.githubusercontent.com/Miicroo/ha-lovelace-vasttrafik_card/master/resources/2.png)
![Example 3](https://raw.githubusercontent.com/Miicroo/ha-lovelace-vasttrafik_card/master/resources/3.png)
## Tram and bus styles
![Colours for each tram or bus line](https://raw.githubusercontent.com/Miicroo/ha-lovelace-vasttrafik_card/master/resources/colours.png)
## Grouping
It is possible to group the sensors based on departure or destination stop. Use the config `groupBy` with either `from` or `to` as value. Incorrect values will be interpreted as `to`.

For this to work, the sensors must expose the configured `from` and `to` as attributes which [this custom vasttrafik sensor does](https://github.com/Miicroo/ha-vasttrafik). If you use the built-in sensor from HomeAssistant, all sensors will be grouped in the same group named 'Västtrafik'.

```yaml
type: 'custom:vasttrafik-card'
# title has no effect here
entities:
- sensor.fran_valand_to_hjalmar
- sensor.fran_valand_to_vasa
- sensor.fran_hjalmar_to_valand
groupBy: from
```
![Example of grouped sensors](https://raw.githubusercontent.com/Miicroo/ha-lovelace-vasttrafik_card/master/resources/4.png)

## In case of errors
1. A warning will be printed to the console if any entity id you provide is not attributed to Västtrafik
2. The default sensor does not expose from/to, see Grouping section for full set up
3. The sensor updates every 2 minutes, so you will sometimes get `-1 minutes` until departure
2. The default sensor does not expose from/to, use [this one](https://github.com/Miicroo/ha-vasttrafik) instead
3. The sensor updates every 2 minutes, so you will sometimes get `-2 minutes` until departure
127 changes: 48 additions & 79 deletions dist/vasttrafik-card.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
const LitElement = Object.getPrototypeOf(customElements.get("hui-view"));
const html = LitElement.prototype.html;

class VasttrafikCard extends LitElement {
class VasttrafikLeaveCard extends LitElement {
static get properties() {
return {
_config: {
cardTemplates: []
entities: []
},
_hass: {
states: []
Expand All @@ -17,6 +14,13 @@ class VasttrafikCard extends LitElement {
if (!config.entities || config.entities.length === 0) {
throw new Error("Specify at least one entity!");
}

for(let i = 0; i<config.entities.length; i++) {
if (typeof config.entities[i] === 'string') {
config.entities[i] = {'id': config.entities[i], 'delay': 0};
}
}

this._config = config;
}

Expand All @@ -32,103 +36,68 @@ class VasttrafikCard extends LitElement {
}

verifyEntities() {
this._config.entities.forEach(entityId => {
const attribution = this._hass.states[entityId].attributes.attribution;
this._config.entities
.filter(entity => !!this._hass.states[entity.id])
.forEach(entity => {
const attribution = this._hass.states[entity.id].attributes.attribution;

if (!attribution || !attribution.toLowerCase().includes('västtrafik')) {
console.warn(`WARNING: ${entityId} does not seem to be a Västtrafik-sensor. Instead it is attributed to ${attribution}`);
}
});
if (!attribution || !attribution.toLowerCase().includes('västtrafik')) {
console.warn(`WARNING: ${entity.id} does not seem to be a Västtrafik-sensor. Instead it is attributed to ${attribution}`);
}
});
}

createCardTemplates() {
if (this._config.groupBy) {
const titleFunction = this._config.groupBy === 'from' ?
entityId => this._hass.states[entityId].attributes.from :
entityId => this._hass.states[entityId].attributes.to;

const sortedEntityObject = this.getEntityIdsByTitle(titleFunction);
this._config.cardTemplates = Object.keys(sortedEntityObject)
.map(title => {
return {
'title': title,
'entityIds': sortedEntityObject[title],
};
});
} else {
this._config.cardTemplates = [{
title: this.getValidTitle(null),
entityIds: this._config.entities,
}];
}
}

getEntityIdsByTitle(titleFunction) {
return this._config.entities.reduce((obj, entityId) => {
const title = this.getValidTitle(titleFunction(entityId));
if (!obj.hasOwnProperty(title)) {
obj[title] = []
}
obj[title].push(entityId);
return obj;
}, {});
}
this._config.entities
.filter(entity => !!this._hass.states[entity.id])
.forEach(entity => entity['departureTime'] = this._hass.states[entity.id].state);

getValidTitle(wantedTitle) {
return wantedTitle || this._config.title || 'Västtrafik';
this._config.entities.sort((a,b) => this.getTimeUntil(a) - this.getTimeUntil(b));
}

render() {
const cardTemplates = this._config.cardTemplates.map(cardTemplate => this.renderCardTemplate(cardTemplate));
const title = this._config.title || 'Västtrafik';
const renderedEntities = this._config.entities.map(entity => this.renderEntity(entity));

return html`
<link type="text/css" rel="stylesheet" href="/community_plugin/lovelace-vasttrafik-card/vasttrafik-card.css"></link>
<ha-card>
<div class="card-header">
${title}
</div>
<div>
${cardTemplates}
<table>
<tr>
<th align="left"></th>
<th align="left">Time</th>
<th align="left">From</th>
<th align="left">Leave home</th>
</tr>
${renderedEntities}
</table>
</div>
</ha-card>`;
}

renderCardTemplate(cardTemplate) {
return html`
<div class="card-header">
${cardTemplate.title}
</div>
<table>
${cardTemplate.entityIds.map(entityId => this.renderEntity(entityId))}
</table>`;
}

renderEntity(entityId) {
if (!(entityId in this._hass.states)) {
renderEntity(entity) {
if (!(entity.id in this._hass.states)) {
return;
}

const entity = this._hass.states[entityId];
const attributes = entity.attributes;
const hassEntity = this._hass.states[entity.id];
const attributes = hassEntity.attributes;

const line = attributes.line;
const lineClass = this.getLineClass(line);
const direction = attributes.direction;
const track = attributes.track;
const departureTime = entity.state;
const accessibilityIcon = attributes.accessibility === 'wheelChair' ? 'mdi:wheelchair-accessibility' : '';
const timeUntilDeparture = this.getTimeUntil(departureTime);
const departureTime = hassEntity.state;
const timeUntilLeave = this.getTimeUntil(entity);
const from = attributes.from || '';
const to = attributes.to || '';

const shouldDisplayFrom = this._config.groupBy && this._config.groupBy !== 'from';
const shouldDisplayTo = this._config.groupBy && this._config.groupBy !== 'to';

return html`<tr>
<td class="${lineClass} line">${line}</td>
<td>${direction}</td>
<td>${timeUntilDeparture} minutes</td>
<td>${departureTime}</td>
<td>${track}</td>
<td><ha-icon icon="${accessibilityIcon}"></ha-icon></td>
${shouldDisplayFrom ? html`<td>${from}</td>`: html``}
${shouldDisplayTo ? html`<td>${to}</td>`: html``}
<td>${from}</td>
<td>${timeUntilLeave} minutes</td>
</tr>`;
}

Expand All @@ -145,11 +114,11 @@ class VasttrafikCard extends LitElement {
}
}

getTimeUntil(hhmm) {
getTimeUntil(entity) {
const now = new Date();
const nowHour = now.getHours();
const nowMinute = now.getMinutes();
const expectedTime = hhmm.split(':');
const expectedTime = entity.departureTime.split(':');
const expectedHour = parseInt(expectedTime[0]);
const expectedMinute = parseInt(expectedTime[1]);
let hourDiff = expectedHour < nowHour ? 24+(expectedHour-nowHour) : expectedHour-nowHour;
Expand All @@ -159,7 +128,7 @@ class VasttrafikCard extends LitElement {
hourDiff--;
}

return hourDiff*60 + minuteDiff;
return hourDiff*60 + minuteDiff - entity.delay;
}
}
customElements.define('vasttrafik-card', VasttrafikCard);
customElements.define('vasttrafik-leave-card', VasttrafikLeaveCard);
Binary file modified resources/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed resources/2.png
Binary file not shown.
Binary file removed resources/3.png
Binary file not shown.
Binary file removed resources/4.png
Binary file not shown.
Loading

0 comments on commit 4c8b065

Please sign in to comment.