PanelKit is a tablet-focused UI built on top of Home Assistant.
It's heavily inspired by tileboard, but built using a modern React stack.
Some scribbles about what (hopefully) still needs done.
-
Grid system needs work. It'd ideally automatically layout with a fixed width grid, and variable unit-sized tiles. Tiles can live in groups, which then cascades the grid. e.g. traditional CSS grid systems, but with a bit more control.
-
Implement mobile-specific styles and metadata. A bit of work is done here, but we may still want to rethink the grid. Also need to finish up support for a standard portrait display (e.g. iPhone).
-
Needs thorough testing on mobile devices and tablets. Haven't done this at all yet, though some of the behavior is already implemented (for e.g. touch controls).
-
Revisit a number of design elements (like the Slider) to optimize for touch devices and smaller form factors. Some controls are likely going to be too small to tap easily, or need to respond faster to human interactions. A bunch of work is already done to make it feel fast, but it's not complete yet.
-
Implement missing Tile features. Notes are generally inline in the Tile list below. To some degree I'm modeling feature parity with Tileboard, but there may be things that simply aren't worth porting (such as the device tracker) unless someone else opts to do it.
-
Consider migrating to
home-assistant-js-websocket
. I didn't notice it when I started development, and its got quite a lot of functionality/complexity that migration may or may not be worth it. -
Add validation for config so that it doesn't throw cryptic javascript errors when e.g. you pass an invalid
type
. -
Camera modal/door modal will need portrait-focused improvements.
Configuration is read from ./config.js
- relative to your index.html
file.
The config.js
file must export CONFIG
with the following baseline values:
/* global TILE, React */
window.CONFIG = {
url: "http://localhost:8123",
accessToken: "your.long-lived.access-token",
tiles: [],
};
Tiles are defined either as a nested tree of additional tiles (grouped together):
{
// ...
tiles: [
{
tiles: [
{
type: TILE.LIGHT,
entityID: "light.cool_light",
},
// ...
],
},
];
}
Oo individual tiles themselves (see docs below for more details):
{
// ...
tiles: [
{
type: TILE.LIGHT,
entityID: "light.cool_light",
},
// ...
];
}
Groups and individual tiles can be meshed together as needed to create your perfect layout.
Every tile is a React component. A few common attributes are shared within configuration:
type
- The type attribute is the component that will be rendered. The
TILE
constant is a registry of default components. title
- The title (or name) to make visible for the tile. Inferred from the entity when available.
subtitle
- An optional subtitle to make visible for the tile.
entityId
- Required by some tiles, this will couple the state of the tile to the given entity.
icons
- Optional mapping of icons to react to state changes.
icon
- Optional icon component to override the default (and the fallback when no other icon is available).
state
- Optional mapping of states for labels
An example basic tile configuration:
{
type: TILE.LIGHT,
entityId: "light.string_lights_front",
title: "Christmas Lights",
subtitle: "Front Yard",
icons: {
"on": "string-lights",
"off": "string-lights-off"
},
states: {
"on": "Really On",
"on": "Really Off",
}
}
And with that, you'll get a basic light tile control with custom icons:
A basic alarm control, showing the current state of the alarm and allowing disarm, armed_home, armed_away, and armed_night state changes.
{
type: TILE.ALARM,
// The `alarm_control_panel` entity ID.
entityId: "",
}
A tile which to trigger an automation
{
type: TILE.AUTOMATION,
// The `automation` entity ID.
entityId: "",
// (Optional) The action to perform: toggle (default), trigger, turn_on, and turn_off
action: "toggle"
}
A still capture of a camera, refreshed every few seconds. A single press goes into a full screen video, which will progressively load a streaming gif and video feed when possible. It also gives quick access to any other defined camera entities.
{
type: TILE.CAMERA,
// The `camera` entity ID.
entityId: "",
// (Optional) The refresh interval for the camera still (in milliseconds). Defaults to `3000` (3s).
refreshInterval: 3000,
// (Optional) List of cameras to show. Defaults to all cameras with active tiles.
cameraList: ["camera.name"]
}
The modal allows cycling through camera feeds:
NOT FINISHED
A tile designed for climate control. A single press opens climate configuration.
{
type: TILE.CLIMATE,
// The `climate` entity ID.
entityId: "",
}
TODO:
- Add modal for advanced climate controls
NOT FINISHED
A tile designed for a door control system, including a main camera feed as well as various actions. A single press opens the full screen camera feed with door controls.
{
type: TILE.DOOR_CONTROL,
// The `camera` entity ID.
camerra: "",
}
TODO:
- Add controls within panel.
- Add event-based prompt to load DoorControlModal.
A simple fan switch. A single press toggles the fan, a long press brings up a speed control.
{
type: TILE.FAN,
// The `fan` entity ID.
entityId: "",
}
TODO:
- Add speed control
An input selector. A single press activates the next option. A long press brings up a selector.
{
type: TILE.INPUT_SELECT,
// The `input_select` entity ID.
entityId: "",
}
TODO:
- Add long press widget
A simple light switch. A single press toggles the light, a long press brings up a brightness control.
{
type: TILE.LIGHT,
// The `light` entity ID.
entityId: "",
}
TODO:
- Add RGB controls
- Change portrait/small device display to be vertical sliders
A simple lock swtich. A single press toggles the lock.
{
type: TILE.LOCK,
// The `lock` entity ID.
entityId: "",
}
A tile which can be pressed to activate a scene.
{
type: TILE.SCENE,
// The `scene` entity ID.
entityId: "",
}
A tile which displays the result of a sensor.
{
type: TILE.SENSOR,
// The `sensor` entity ID.
entityId: "",
// (Optional) A function to format the value.
format: function(state, attributes, unitOfMeasurement) { return state + ' ' + unitOfMeasurement }
}
A tile which can be pressed to activate a script.
{
type: TILE.SCRIPT,
// The `script` entity ID.
entityId: "",
// (Optional) The payload to send along with the script.
data: {
// ...
}
}
An example using a custom Pi-hole script:
{
type: TILE.SCRIPT,
entityId: "switch.pi_hole",
icon: "pi-hole",
}
A simple switch. A single press toggles the switch.
{
type: TILE.SWITCH,
// The `switch` entity ID.
entityId: "",
}
YMMV. Enter at your own risk.
A tile is just a component. So you can use React's APIs if you want to build your own:
{
type: function (props) {
var e = React.createElement;
return e("div", null, "Example style-less widget");
},
},
Realistically if you go down this route, you're going to want to just spin up a simple application that can generate a CSS bundle with React components in it. You might want to look at create-react-app for this. From there you'll want to import PanelKit's Tile
component and use it as defined in our built-ins.
Note: The above is likely not possible today, but if this project gets use, we could easily abstract the tile components into their own module so folks could achieve this.
PanelKit is built using create-react-app
, and you'll find your standard helper scripts available.
Install dependencies:
yarn install
Run the development server:
yarn start
Create .env
to hold your credentials:
# .env
# see `.env.example` for more information
REACT_APP_HASS_URL=http://localhost:8123
REACT_APP_HASS_ACCESS_TOKEN=
In development we're currently reading config from public/config.js
, which is version controlled and not ideal.
You can use mydevice.info to determine device specs, which is useful for mobile device testing.
- Galaxy Tab A 10.5"
- 1280px x 800px, 80em, 1.5 pixel ratio
You may also want to disable context menu when testing against mobile profiles. This can be done in your Chrome console:
window.oncontextmenu = function () {
return false;
};