Browse Source

scripting system, hue fixes and such

beelzebub
Gisle Aune 9 months ago
parent
commit
8776ed6df3
  1. 10
      cmd/lucifer4-server/main.go
  2. 1
      device/interfaces.go
  3. 7
      device/pointer.go
  4. 1
      frontend/.gitignore
  5. BIN
      frontend/icons.blend
  6. 70
      frontend/src/lib/components/Icon.svelte
  7. 59
      frontend/src/lib/components/Lamp.svelte
  8. 9
      frontend/src/lib/components/icons/generic_ball.svg
  9. 10
      frontend/src/lib/components/icons/generic_boob.svg
  10. 11
      frontend/src/lib/components/icons/generic_lamp.svg
  11. 9
      frontend/src/lib/components/icons/generic_strip.svg
  12. 11
      frontend/src/lib/components/icons/hue_adore_tube.svg
  13. 11
      frontend/src/lib/components/icons/hue_dimmerswitch.svg
  14. 12
      frontend/src/lib/components/icons/hue_lightbulb_e14.svg
  15. 10
      frontend/src/lib/components/icons/hue_lightbulb_e27.svg
  16. 15
      frontend/src/lib/components/icons/hue_lightbulb_gu10.svg
  17. 9
      frontend/src/lib/components/icons/hue_motionsensor.svg
  18. 9
      frontend/src/lib/components/icons/hue_playbar.svg
  19. 10
      frontend/src/lib/components/icons/hue_signe.svg
  20. 2
      frontend/src/lib/models/device.ts
  21. 8
      frontend/src/lib/utils/color.ts
  22. 19
      frontend/src/routes/+page.svelte
  23. 7
      go.mod
  24. 13
      go.sum
  25. 39
      internal/formattools/compactidlist.go
  26. 39
      internal/formattools/compactlist_test.go
  27. 19
      services/effectenforcer/service.go
  28. 10
      services/httpapiv1/service.go
  29. 23
      services/hue/bridge.go
  30. 37
      services/hue/data.go
  31. 12
      services/mysqldb/migrations/20230828234420_script.sql
  32. 15
      services/mysqldb/migrations/20230828235352_script_variable.sql
  33. 40
      services/mysqldb/mysqlgen/db.go
  34. 11
      services/mysqldb/mysqlgen/models.go
  35. 94
      services/mysqldb/mysqlgen/script.sql.go
  36. 11
      services/mysqldb/queries/script.sql
  37. 99
      services/mysqldb/service.go
  38. 7
      services/resolver.go
  39. 42
      services/script/commands.go
  40. 56
      services/script/condition.go
  41. 66
      services/script/condition_test.go
  42. 33
      services/script/script.go
  43. 118
      services/script/service.go
  44. 73
      services/script/variables.go
  45. 5
      services/tradfri/bridge.go
  46. 7
      services/uistate/data.go
  47. 9
      services/uistate/patch.go
  48. 14
      services/uistate/service.go

10
cmd/lucifer4-server/main.go

@ -9,6 +9,8 @@ import (
"git.aiterp.net/lucifer3/server/services/hue"
"git.aiterp.net/lucifer3/server/services/mysqldb"
"git.aiterp.net/lucifer3/server/services/nanoleaf"
"git.aiterp.net/lucifer3/server/services/script"
"git.aiterp.net/lucifer3/server/services/tradfri"
"git.aiterp.net/lucifer3/server/services/uistate"
"log"
"os"
@ -21,7 +23,7 @@ func main() {
bus := lucifer3.EventBus{}
resolver := services.NewResolver()
sceneMap := services.NewSceneMap(resolver)
variables := script.NewVariables()
database, err := mysqldb.Connect(
env("LUCIFER4_DB_HOST"),
@ -40,13 +42,15 @@ func main() {
}
bus.JoinPrivileged(resolver)
bus.JoinPrivileged(sceneMap)
bus.Join(effectenforcer.NewService(resolver, sceneMap))
bus.Join(effectenforcer.NewService(resolver))
bus.Join(nanoleaf.NewService())
bus.Join(hue.NewService())
bus.Join(tradfri.NewService())
bus.Join(uistate.NewService())
bus.Join(script.NewService(resolver, variables))
bus.Join(database)
bus.Join(httpAPI)
bus.Join(variables)
bus.RunEvent(events.Started{})

1
device/interfaces.go

@ -2,6 +2,7 @@ package device
type Resolver interface {
Resolve(pattern string) []Pointer
CompileMatcher(pattern string) PointerMatcher
}
type SceneMap interface {

7
device/pointer.go

@ -2,6 +2,7 @@ package device
import (
"git.aiterp.net/lucifer3/server/internal/gentools"
"log"
"strings"
)
@ -69,6 +70,12 @@ func (p *Pointer) Name() string {
}
func (p *Pointer) Matches(matcher PointerMatcher) bool {
defer func() {
if err := recover(); err != nil {
log.Println("[PANIC]", err)
}
}()
if matcher.Match(p.ID) {
return true
}

1
frontend/.gitignore

@ -8,3 +8,4 @@ node_modules
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
/*.blend1

BIN
frontend/icons.blend

70
frontend/src/lib/components/Icon.svelte

@ -0,0 +1,70 @@
<script lang="ts" context="module">
import generic_lamp from "./icons/generic_lamp.svg?raw";
import generic_boob from "./icons/generic_boob.svg?raw";
import generic_ball from "./icons/generic_ball.svg?raw";
import generic_strip from "./icons/generic_strip.svg?raw";
import hue_signe from "./icons/hue_signe.svg?raw";
import hue_dimmerswitch from "./icons/hue_dimmerswitch.svg?raw";
import hue_motionsensor from "./icons/hue_motionsensor.svg?raw";
import hue_lightbulb_e27 from "./icons/hue_lightbulb_e27.svg?raw";
import hue_lightbulb_e14 from "./icons/hue_lightbulb_e14.svg?raw";
import hue_lightbulb_gu10 from "./icons/hue_lightbulb_gu10.svg?raw";
import hue_adore_tube from "./icons/hue_adore_tube.svg?raw";
import hue_playbar from "./icons/hue_playbar.svg?raw";
export const iconMap = Object.seal({
generic_lamp,
generic_boob,
generic_ball,
generic_strip,
hue_signe,
hue_dimmerswitch,
hue_motionsensor,
hue_lightbulb_e27,
hue_lightbulb_e14,
hue_lightbulb_gu10,
hue_adore_tube,
hue_playbar,
});
export type IconName = keyof typeof iconMap;
</script>
<script lang="ts">
import type { ColorRGB } from "$lib/models/color";
import { rgbToHex } from "$lib/utils/color";
export let brightColor: ColorRGB;
export let darkColor: ColorRGB;
export let name: IconName;
let gandalf: string;
let voldemort: string;
$: gandalf = rgbToHex(brightColor);
$: voldemort = rgbToHex(darkColor);
let icon: string;
$: icon = iconMap[name];
let dataUrl: string;
$: {
const data = encodeURIComponent(
icon
.replaceAll("fill:#ff0000", `fill:${gandalf}`)
.replaceAll("fill:#0000ff", `fill:${voldemort}`)
);
dataUrl = `data:image/svg+xml;url,${data}`
}
</script>
<div class="icon">
<img alt="icon" src={dataUrl} />
<slot></slot>
</div>
<style>
div.icon > img {
width: 1em;
height: 1em;
}
</style>

59
frontend/src/lib/components/Lamp.svelte

@ -1,8 +1,14 @@
<script lang="ts" context="module">
export const DARK_COLOR = rgb(0.5176470588235295/2, 0.5333333333333333/2, 0.5607843137254902/1.75);
export const DARK_COLOR_SELECTED = rgb(0.5176470588235295/1.5, 0.5333333333333333/1.5, 0.5607843137254902/1.25);
</script>
<script lang="ts">
import { getSelectedContext } from "$lib/contexts/SelectContext.svelte";
import type Device from "$lib/models/device";
import { SupportFlags } from "$lib/models/device";
import { rgb, type ColorRGB } from "../models/color";
import Icon from "./Icon.svelte";
export let device: Device;
@ -85,21 +91,25 @@
displayTitle = deviceTitle;
}
}
let darkColor: ColorRGB;
$: darkColor = $selectedMap[device.id] ? DARK_COLOR_SELECTED : DARK_COLOR
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="lamp" class:selected={$selectedMap[device.id]} on:click={onSelect}>
<div class="row">
{#if iconColor != null}
<div style="background-color: rgb({iconColor.red*255},{iconColor.green*255},{iconColor.blue*255})" class="roundboi"></div>
{:else}
<div style="background-color: #223)" class="roundboi">
{#if !!roundboiText}
<div class="roundboi-text">{roundboiText}</div>
{/if}
</div>
{/if}
<div class="row-icon">
{#if iconColor != null}
<Icon name={device.hwMetadata?.icon||"generic_ball"} brightColor={iconColor} darkColor={darkColor} />
{:else}
<Icon name={device.hwMetadata?.icon||"generic_ball"} brightColor={darkColor} darkColor={darkColor}>
{#if !!roundboiText}
<div class="roundboi-text">{roundboiText}</div>
{/if}
</Icon>
{/if}
</div>
<div class="title">{displayTitle}</div>
</div>
<div class="flatboi">
@ -132,25 +142,26 @@
> div.row
display: flex
> div.roundboi
width: 1.25em
height: 1.25em
margin-left: 0.25em
margin-top: 0.30em
border-radius: 2em
box-sizing: border-box
border: 0.5px solid #000
> div.roundboi-text
color: #abc
margin-top: 0.2em
font-size: 0.8em
> div.row-icon
font-size: 1.8em
position: absolute
width: 0
height: 0
div.roundboi-text
width: 0
height: 0
left: 1.4ch
top: 0.9em
text-align: center
position: absolute
font-size: 0.333em
> div.title
font-size: 0.9em
text-align: left
margin-top: 0.4em
margin-left: 0.75ch
margin-left: 2em
margin-right: 1ch
height: 1.6em

9
frontend/src/lib/components/icons/generic_ball.svg

@ -0,0 +1,9 @@
<?xml version='1.0' encoding='ascii'?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320">
<g id="View Layer_LineSet" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="View Layer_LineSet">
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes">
<path style="fill:#ff0000;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 151.535, 21.348 142.812, 22.208 134.442, 23.520 126.207, 25.510 117.819, 28.055 109.866, 30.975 102.178, 34.534 94.447, 38.666 87.217, 43.082 80.371, 48.071 73.595, 53.632 67.365, 59.374 61.624, 65.603 56.063, 72.380 51.073, 79.226 46.657, 86.456 42.525, 94.187 38.967, 101.875 36.046, 109.828 33.502, 118.216 31.512, 126.451 30.199, 134.820 29.340, 143.544 28.994, 152.009 29.340, 160.474 30.199, 169.197 31.512, 177.567 33.502, 185.801 36.046, 194.190 38.967, 202.142 42.525, 209.831 47.239, 218.650 50.667, 225.063 57.011, 232.793 63.355, 240.523 67.020, 244.989 74.750, 251.333 79.739, 255.427 87.243, 260.897 95.169, 265.737 101.457, 269.098 109.884, 272.999 118.602, 276.200 125.425, 278.270 134.451, 280.451 143.625, 281.890 150.721, 282.589 160.000, 282.967 169.279, 282.589 176.375, 281.890 185.549, 280.451 194.575, 278.270 201.398, 276.200 210.116, 272.999 218.543, 269.098 224.831, 265.737 232.757, 260.897 240.261, 255.427 247.991, 249.083 252.980, 244.989 259.324, 237.259 263.937, 231.638 268.927, 224.792 273.343, 217.561 277.475, 209.831 281.033, 202.142 283.954, 194.190 286.498, 185.801 288.488, 177.567 289.801, 169.197 290.660, 160.474 291.006, 152.009 290.660, 143.544 289.801, 134.820 288.488, 126.451 286.498, 118.216 283.954, 109.828 281.033, 101.875 277.475, 94.187 273.343, 86.456 268.927, 79.226 263.937, 72.380 258.376, 65.603 252.635, 59.374 246.405, 53.632 239.629, 48.071 232.783, 43.082 225.553, 38.666 217.822, 34.534 210.134, 30.975 202.181, 28.055 193.793, 25.510 185.558, 23.520 177.188, 22.208 168.465, 21.348 160.000, 21.003 151.535, 21.348 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 240.261, 255.427 247.991, 249.083 252.980, 244.989 252.980, 252.242 252.440, 256.200 250.901, 260.442 248.598, 264.323 245.881, 267.252 240.117, 271.982 232.763, 277.342 224.996, 282.086 218.379, 285.623 210.120, 289.445 201.577, 292.582 194.397, 294.760 185.551, 296.898 176.560, 298.308 169.093, 299.044 160.000, 299.415 150.907, 299.044 143.440, 298.308 134.449, 296.898 125.603, 294.760 118.423, 292.582 109.880, 289.445 101.621, 285.623 95.004, 282.086 87.237, 277.342 79.883, 271.982 74.119, 267.252 71.402, 264.323 69.099, 260.442 67.560, 256.200 67.020, 252.242 67.020, 244.989 74.750, 251.333 79.739, 255.427 87.243, 260.897 95.169, 265.737 101.457, 269.098 109.884, 272.999 118.602, 276.200 125.425, 278.270 134.451, 280.451 143.625, 281.890 150.721, 282.589 160.000, 282.967 169.279, 282.589 176.375, 281.890 185.549, 280.451 194.575, 278.270 201.398, 276.200 210.116, 272.999 218.543, 269.098 224.831, 265.737 232.757, 260.897 240.261, 255.427 " />
</g>
</g>
</svg>

10
frontend/src/lib/components/icons/generic_boob.svg

@ -0,0 +1,10 @@
<?xml version='1.0' encoding='ascii'?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320">
<g id="View Layer_LineSet" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="View Layer_LineSet">
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes">
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 160.000, 62.462 150.025, 63.169 140.050, 63.876 132.687, 64.398 122.918, 66.531 113.148, 68.665 106.424, 70.133 97.091, 73.724 87.758, 77.315 82.220, 79.446 73.610, 84.532 65.000, 89.618 61.005, 91.978 53.487, 98.573 45.969, 105.167 43.594, 107.250 37.633, 115.278 31.671, 123.307 30.657, 124.673 26.773, 133.888 22.890, 143.103 22.690, 143.578 21.334, 153.485 20.000, 163.238 21.356, 173.146 22.690, 182.899 26.573, 192.114 30.457, 201.329 30.657, 201.804 36.618, 209.832 42.580, 217.861 43.594, 219.227 51.112, 225.821 58.630, 232.415 61.005, 234.498 69.615, 239.584 78.225, 244.671 82.220, 247.031 91.553, 250.622 100.886, 254.213 106.424, 256.344 116.194, 258.477 125.964, 260.610 132.687, 262.078 142.662, 262.786 152.637, 263.493 160.000, 264.015 169.975, 263.308 179.950, 262.600 187.313, 262.078 197.082, 259.945 206.852, 257.812 213.576, 256.344 222.909, 252.753 232.242, 249.162 237.780, 247.031 246.390, 241.945 255.000, 236.858 258.995, 234.498 266.513, 227.904 274.031, 221.310 276.406, 219.227 282.367, 211.198 288.329, 203.170 289.343, 201.804 293.227, 192.589 297.110, 183.373 297.310, 182.899 298.666, 172.991 300.000, 163.238 298.644, 153.331 297.310, 143.578 293.427, 134.363 289.543, 125.147 289.343, 124.673 283.382, 116.644 277.420, 108.615 276.406, 107.250 268.888, 100.656 261.370, 94.062 258.995, 91.978 250.385, 86.892 241.775, 81.806 237.780, 79.446 228.447, 75.855 219.114, 72.264 213.576, 70.133 203.806, 68.000 194.036, 65.866 187.313, 64.398 177.338, 63.691 167.363, 62.984 160.000, 62.462 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 237.780, 62.906 228.447, 59.316 219.114, 55.725 213.576, 53.594 203.806, 51.460 194.036, 49.327 187.313, 47.859 177.338, 47.152 167.363, 46.445 160.000, 45.923 150.025, 46.630 140.050, 47.337 132.687, 47.859 122.918, 49.992 113.148, 52.126 106.424, 53.594 97.091, 57.185 87.758, 60.776 82.220, 62.906 73.610, 67.993 65.000, 73.079 61.005, 75.439 53.487, 82.033 45.969, 88.628 43.594, 90.711 37.633, 98.739 31.671, 106.768 30.657, 108.134 26.773, 117.349 22.890, 126.564 22.690, 127.039 21.334, 136.946 20.000, 146.699 20.000, 156.699 20.000, 163.238 21.356, 153.331 22.690, 143.578 26.573, 134.363 30.457, 125.147 30.657, 124.673 36.618, 116.644 42.580, 108.615 43.594, 107.250 51.112, 100.656 58.630, 94.062 61.005, 91.978 69.615, 86.892 78.225, 81.806 82.220, 79.446 91.553, 75.855 100.886, 72.264 106.424, 70.133 116.194, 67.999 125.964, 65.866 132.687, 64.398 142.662, 63.691 152.637, 62.984 160.000, 62.462 169.975, 63.169 179.950, 63.876 187.313, 64.398 197.082, 66.531 206.852, 68.665 213.576, 70.133 222.909, 73.724 232.242, 77.315 237.780, 79.446 246.390, 84.532 255.000, 89.618 258.995, 91.978 266.513, 98.573 274.031, 105.167 276.406, 107.250 282.367, 115.278 288.329, 123.307 289.343, 124.673 293.227, 133.888 297.110, 143.103 297.310, 143.578 298.666, 153.485 300.000, 163.238 300.000, 153.238 300.000, 146.699 298.644, 136.792 297.310, 127.039 293.427, 117.823 289.543, 108.608 289.343, 108.134 283.382, 100.105 277.420, 92.076 276.406, 90.711 268.888, 84.117 261.370, 77.523 258.995, 75.439 250.385, 70.353 241.775, 65.267 237.780, 62.906 " />
<path style="fill:#ff0000;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 136.608, 78.586 126.838, 80.720 117.068, 82.853 114.115, 83.498 104.782, 87.089 95.449, 90.680 93.385, 91.474 84.775, 96.560 76.166, 101.646 75.216, 102.208 67.698, 108.802 60.304, 115.287 54.342, 123.315 49.224, 130.209 45.340, 139.424 42.401, 146.400 41.045, 156.308 40.097, 163.238 40.131, 166.336 40.376, 169.310 41.731, 179.218 42.674, 186.109 43.327, 188.961 44.385, 191.698 48.269, 200.913 51.093, 207.616 52.464, 210.011 58.425, 218.040 63.220, 224.497 64.777, 226.439 66.643, 228.096 74.161, 234.690 80.607, 240.343 89.217, 245.430 97.621, 250.395 99.062, 251.234 108.396, 254.824 117.729, 258.415 118.026, 258.530 119.143, 258.795 128.912, 260.928 138.682, 263.061 139.171, 263.168 149.146, 263.875 159.121, 264.582 160.000, 264.645 169.975, 263.937 179.950, 263.230 180.829, 263.168 190.599, 261.035 200.368, 258.901 200.857, 258.795 201.974, 258.530 211.307, 254.939 220.641, 251.348 220.938, 251.234 222.379, 250.395 230.989, 245.308 239.393, 240.343 246.911, 233.749 253.357, 228.096 255.223, 226.439 256.780, 224.497 262.742, 216.468 267.536, 210.011 268.907, 207.616 272.790, 198.401 275.615, 191.698 276.673, 188.961 277.326, 186.109 278.681, 176.201 279.624, 169.310 279.869, 166.336 279.903, 163.238 278.548, 153.331 277.599, 146.400 273.716, 137.185 270.776, 130.209 264.815, 122.180 259.696, 115.287 252.178, 108.693 244.784, 102.208 236.175, 97.121 227.565, 92.035 226.615, 91.474 217.282, 87.883 207.949, 84.292 205.885, 83.498 196.115, 81.365 186.345, 79.231 183.392, 78.586 173.417, 77.879 163.442, 77.172 160.000, 76.928 150.025, 77.635 140.050, 78.342 136.608, 78.586 " />
</g>
</g>
</svg>

11
frontend/src/lib/components/icons/generic_lamp.svg

@ -0,0 +1,11 @@
<?xml version='1.0' encoding='ascii'?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320">
<g id="View Layer_LineSet" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="View Layer_LineSet">
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes">
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 55.961, 155.000 65.961, 155.000 75.961, 155.000 85.961, 155.000 95.961, 155.000 105.961, 155.000 115.961, 155.000 125.961, 155.000 135.961, 155.000 145.961, 155.000 155.961, 155.000 165.961, 155.000 175.961, 155.000 185.961, 155.000 195.961, 155.000 205.961, 155.000 215.961, 155.000 225.961, 155.000 235.961, 155.000 245.961, 155.000 255.961, 155.000 264.039, 155.000 260.274, 145.736 256.926, 137.500 246.926, 137.500 236.926, 137.500 226.926, 137.500 216.926, 137.500 206.926, 137.500 196.926, 137.500 186.926, 137.500 176.926, 137.500 166.926, 137.500 156.926, 137.500 146.926, 137.500 136.926, 137.500 126.926, 137.500 116.926, 137.500 106.926, 137.500 96.926, 137.500 86.926, 137.500 76.926, 137.500 66.926, 137.500 63.074, 137.500 59.308, 146.764 55.961, 155.000 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 103.715, 37.500 113.715, 37.500 123.715, 37.500 133.715, 37.500 143.715, 37.500 153.715, 37.500 163.715, 37.500 173.715, 37.500 183.715, 37.500 193.715, 37.500 203.715, 37.500 213.715, 37.500 216.285, 37.500 212.520, 28.236 209.172, 20.000 199.172, 20.000 189.172, 20.000 179.172, 20.000 169.172, 20.000 159.172, 20.000 149.172, 20.000 139.172, 20.000 129.172, 20.000 119.172, 20.000 110.828, 20.000 107.063, 29.264 103.715, 37.500 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 168.549, 165.000 158.549, 165.000 148.549, 165.000 138.549, 165.000 131.451, 165.000 131.451, 175.000 131.451, 185.000 131.451, 195.000 131.451, 205.000 131.451, 215.000 131.451, 225.000 131.451, 235.000 131.451, 245.000 131.451, 255.000 131.451, 265.000 131.451, 275.000 131.451, 280.000 131.835, 283.902 132.973, 287.654 134.821, 291.111 137.309, 294.142 140.339, 296.629 143.797, 298.478 147.549, 299.616 151.451, 300.000 161.451, 300.000 171.451, 300.000 181.451, 300.000 188.549, 300.000 188.549, 290.000 188.549, 280.000 188.549, 270.000 188.549, 260.000 188.549, 250.000 188.549, 240.000 188.549, 230.000 188.549, 220.000 188.549, 210.000 188.549, 200.000 188.549, 190.000 188.549, 185.000 188.165, 181.098 187.027, 177.346 185.179, 173.889 182.691, 170.858 179.661, 168.371 176.203, 166.522 172.451, 165.384 168.549, 165.000 " />
<path style="fill:#ff0000;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 103.715, 37.500 99.950, 46.764 96.185, 56.028 92.420, 65.292 88.655, 74.556 84.890, 83.821 81.125, 93.085 77.360, 102.349 73.595, 111.613 69.829, 120.877 66.064, 130.141 63.074, 137.500 73.074, 137.500 83.074, 137.500 93.074, 137.500 103.074, 137.500 113.074, 137.500 123.074, 137.500 133.074, 137.500 143.074, 137.500 153.074, 137.500 163.074, 137.500 173.074, 137.500 183.074, 137.500 193.074, 137.500 203.074, 137.500 213.074, 137.500 223.074, 137.500 233.074, 137.500 243.074, 137.500 253.074, 137.500 256.926, 137.500 253.161, 128.236 249.396, 118.972 245.631, 109.708 241.866, 100.444 238.101, 91.179 234.336, 81.915 230.571, 72.651 226.806, 63.387 223.040, 54.123 219.275, 44.859 216.285, 37.500 206.285, 37.500 196.285, 37.500 186.285, 37.500 176.285, 37.500 166.285, 37.500 156.285, 37.500 146.285, 37.500 136.285, 37.500 126.285, 37.500 116.285, 37.500 106.285, 37.500 103.715, 37.500 " />
</g>
</g>
</svg>

9
frontend/src/lib/components/icons/generic_strip.svg

@ -0,0 +1,9 @@
<?xml version='1.0' encoding='ascii'?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320">
<g id="View Layer_LineSet" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="View Layer_LineSet">
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes">
<path style="fill:#ff0000;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 19.563, 190.646 20.193, 195.399 21.151, 200.213 27.776, 198.679 34.402, 197.144 41.027, 195.610 40.320, 192.062 39.866, 188.648 39.655, 185.368 39.676, 182.220 39.919, 179.197 40.374, 176.295 41.036, 173.505 41.897, 170.821 42.955, 168.235 44.206, 165.742 45.651, 163.335 47.288, 161.013 49.117, 158.774 51.138, 156.618 53.350, 154.545 55.750, 152.559 58.336, 150.663 61.105, 148.858 64.051, 147.150 67.169, 145.542 70.455, 144.037 73.902, 142.637 77.504, 141.347 81.255, 140.169 85.146, 139.105 89.173, 138.157 93.327, 137.329 97.603, 136.620 101.992, 136.035 106.489, 135.573 111.085, 135.236 115.775, 135.026 120.551, 134.944 125.406, 134.991 130.335, 135.168 135.342, 135.478 143.028, 136.118 150.282, 136.856 157.113, 137.684 163.543, 138.593 169.596, 139.578 175.294, 140.631 180.661, 141.746 185.721, 142.916 190.498, 144.133 195.016, 145.393 199.299, 146.689 203.374, 148.015 207.264, 149.367 210.995, 150.738 214.592, 152.125 218.080, 153.522 221.483, 154.923 224.827, 156.325 228.134, 157.720 231.429, 159.102 234.734, 160.466 238.071, 161.802 241.462, 163.103 244.928, 164.360 248.488, 165.563 252.161, 166.704 255.967, 167.773 259.923, 168.760 264.048, 169.659 268.359, 170.459 272.876, 171.154 277.618, 171.738 282.605, 172.202 287.856, 172.541 293.393, 172.749 299.237, 172.820 299.237, 166.020 299.237, 159.219 299.237, 152.419 293.899, 152.354 288.896, 152.166 284.208, 151.864 279.809, 151.454 275.674, 150.946 271.774, 150.345 268.081, 149.660 264.565, 148.894 261.197, 148.053 257.946, 147.140 254.781, 146.157 251.673, 145.106 248.595, 143.989 245.517, 142.808 242.416, 141.566 239.266, 140.267 236.046, 138.915 232.734, 137.518 229.310, 136.083 225.756, 134.619 222.053, 133.137 218.184, 131.645 214.131, 130.156 209.879, 128.679 205.411, 127.224 200.709, 125.802 195.758, 124.421 190.538, 123.091 185.033, 121.819 179.223, 120.612 173.089, 119.478 166.611, 118.424 159.769, 117.456 152.541, 116.580 144.907, 115.803 136.817, 115.130 131.330, 114.791 125.872, 114.595 120.474, 114.543 115.143, 114.634 109.883, 114.870 104.700, 115.250 99.600, 115.774 94.586, 116.443 89.664, 117.259 84.840, 118.221 80.119, 119.332 75.505, 120.594 71.005, 122.009 66.623, 123.578 62.367, 125.307 58.241, 127.198 54.254, 129.255 50.414, 131.483 46.728, 133.886 43.207, 136.469 39.864, 139.237 36.711, 142.193 33.764, 145.339 31.040, 148.676 28.556, 152.201 26.330, 155.910 24.382, 159.792 22.728, 163.838 21.382, 168.031 20.357, 172.356 19.659, 176.795 19.294, 181.333 19.262, 185.954 19.563, 190.646 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 47.029, 190.950 46.634, 187.982 46.453, 185.173 46.470, 182.515 46.672, 179.998 47.047, 177.608 47.587, 175.330 48.287, 173.149 49.145, 171.050 50.165, 169.019 51.349, 167.047 52.704, 165.126 54.235, 163.253 55.947, 161.426 57.845, 159.648 59.931, 157.923 62.206, 156.255 64.668, 154.650 67.316, 153.116 70.145, 151.657 73.151, 150.280 76.329, 148.990 79.671, 147.793 83.171, 146.694 86.822, 145.695 90.617, 144.803 94.548, 144.019 98.608, 143.346 102.790, 142.788 107.085, 142.347 111.486, 142.025 115.985, 141.823 120.576, 141.744 125.251, 141.790 130.003, 141.961 134.850, 142.260 142.401, 142.890 149.529, 143.615 156.228, 144.426 162.521, 145.316 168.432, 146.278 173.985, 147.305 179.204, 148.389 184.115, 149.524 188.744, 150.704 193.118, 151.923 197.262, 153.177 201.205, 154.461 204.975, 155.771 208.599, 157.103 212.105, 158.454 215.521, 159.822 218.874, 161.203 222.191, 162.593 225.496, 163.988 228.816, 165.381 232.173, 166.766 235.589, 168.133 239.085, 169.474 242.680, 170.778 246.390, 172.032 250.233, 173.226 254.224, 174.346 258.376, 175.383 262.703, 176.325 267.221, 177.164 271.944, 177.891 276.888, 178.499 282.070, 178.981 287.509, 179.333 293.224, 179.548 299.237, 179.621 299.237, 172.820 293.393, 172.749 287.856, 172.541 282.605, 172.202 277.618, 171.738 272.876, 171.154 268.359, 170.459 264.048, 169.659 259.923, 168.760 255.967, 167.773 252.161, 166.704 248.488, 165.563 244.928, 164.360 241.462, 163.103 238.071, 161.802 234.734, 160.466 231.429, 159.102 228.134, 157.720 224.827, 156.325 221.483, 154.923 218.080, 153.522 214.592, 152.125 210.995, 150.738 207.264, 149.367 203.374, 148.015 199.299, 146.689 195.016, 145.393 190.498, 144.133 185.721, 142.916 180.661, 141.746 175.294, 140.631 169.596, 139.578 163.543, 138.593 157.113, 137.684 150.282, 136.856 143.028, 136.118 135.342, 135.478 130.335, 135.168 125.406, 134.991 120.551, 134.944 115.775, 135.026 111.085, 135.236 106.489, 135.573 101.992, 136.035 97.603, 136.620 93.327, 137.329 89.173, 138.157 85.146, 139.105 81.255, 140.169 77.504, 141.347 73.902, 142.637 70.455, 144.037 67.169, 145.542 64.051, 147.150 61.105, 148.858 58.336, 150.663 55.750, 152.559 53.350, 154.545 51.138, 156.618 49.117, 158.774 47.288, 161.013 45.651, 163.335 44.206, 165.742 42.955, 168.235 41.897, 170.821 41.036, 173.505 40.374, 176.295 39.919, 179.197 39.676, 182.220 39.655, 185.368 39.866, 188.648 40.320, 192.062 41.027, 195.610 47.652, 194.076 47.029, 190.950 " />
</g>
</g>
</svg>

11
frontend/src/lib/components/icons/hue_adore_tube.svg

@ -0,0 +1,11 @@
<?xml version='1.0' encoding='ascii'?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320">
<g id="View Layer_LineSet" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="View Layer_LineSet">
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes">
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 131.519, 170.000 131.519, 180.000 131.642, 181.564 132.008, 183.090 132.609, 184.540 133.429, 185.878 134.448, 187.071 135.641, 188.090 136.979, 188.910 138.429, 189.511 139.954, 189.877 141.519, 190.000 151.519, 190.000 161.519, 190.000 171.519, 190.000 181.519, 190.000 188.481, 190.000 188.481, 180.000 188.358, 178.436 187.992, 176.910 187.391, 175.460 186.571, 174.122 185.552, 172.929 184.359, 171.910 183.021, 171.090 181.571, 170.489 180.046, 170.123 178.481, 170.000 168.481, 170.000 158.481, 170.000 148.481, 170.000 138.481, 170.000 131.519, 170.000 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 290.000, 160.000 300.000, 160.000 300.000, 150.000 300.000, 140.000 300.000, 130.000 290.000, 130.000 290.000, 140.000 290.000, 150.000 290.000, 160.000 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 20.000, 160.000 30.000, 160.000 30.000, 150.000 30.000, 140.000 30.000, 130.000 20.000, 130.000 20.000, 140.000 20.000, 150.000 20.000, 160.000 " />
<path style="fill:#ff0000;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 290.000, 160.000 290.000, 150.000 290.000, 140.000 290.000, 130.000 280.000, 130.000 270.000, 130.000 260.000, 130.000 250.000, 130.000 240.000, 130.000 230.000, 130.000 220.000, 130.000 210.000, 130.000 200.000, 130.000 190.000, 130.000 180.000, 130.000 170.000, 130.000 160.000, 130.000 150.000, 130.000 140.000, 130.000 130.000, 130.000 120.000, 130.000 110.000, 130.000 100.000, 130.000 90.000, 130.000 80.000, 130.000 70.000, 130.000 60.000, 130.000 50.000, 130.000 40.000, 130.000 30.000, 130.000 30.000, 140.000 30.000, 150.000 30.000, 160.000 40.000, 160.000 50.000, 160.000 60.000, 160.000 70.000, 160.000 80.000, 160.000 90.000, 160.000 100.000, 160.000 110.000, 160.000 120.000, 160.000 130.000, 160.000 140.000, 160.000 150.000, 160.000 160.000, 160.000 170.000, 160.000 180.000, 160.000 190.000, 160.000 200.000, 160.000 210.000, 160.000 220.000, 160.000 230.000, 160.000 240.000, 160.000 250.000, 160.000 260.000, 160.000 270.000, 160.000 280.000, 160.000 290.000, 160.000 " />
</g>
</g>
</svg>

11
frontend/src/lib/components/icons/hue_dimmerswitch.svg

@ -0,0 +1,11 @@
<?xml version='1.0' encoding='ascii'?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320">
<g id="View Layer_LineSet" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="View Layer_LineSet">
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes">
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 105.413, 186.990 100.413, 195.651 95.413, 204.311 90.413, 212.971 86.413, 219.900 85.623, 221.526 85.073, 223.247 84.773, 225.029 84.730, 226.836 84.945, 228.631 85.412, 230.376 86.124, 232.038 87.064, 233.581 88.215, 234.975 89.552, 236.190 91.049, 237.203 99.709, 242.203 108.370, 247.203 117.030, 252.203 125.690, 257.203 127.615, 258.315 129.241, 259.105 130.963, 259.655 132.745, 259.955 134.552, 259.998 136.346, 259.783 138.092, 259.315 139.753, 258.604 141.296, 257.663 142.690, 256.513 143.906, 255.175 144.919, 253.679 149.919, 245.018 154.919, 236.358 159.919, 227.698 163.919, 220.769 155.259, 215.769 146.599, 210.769 137.938, 205.769 129.278, 200.769 120.618, 195.769 111.958, 190.769 105.413, 186.990 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 175.081, 66.321 170.081, 74.982 165.081, 83.642 160.081, 92.302 156.081, 99.231 164.741, 104.231 173.401, 109.231 182.062, 114.231 190.722, 119.231 199.382, 124.231 208.042, 129.231 214.587, 133.010 219.587, 124.349 224.587, 115.689 229.587, 107.029 233.587, 100.100 234.377, 98.474 234.927, 96.753 235.227, 94.971 235.270, 93.164 235.055, 91.369 234.588, 89.624 233.876, 87.962 232.936, 86.419 231.785, 85.025 230.448, 83.810 228.951, 82.797 220.291, 77.797 211.630, 72.797 202.970, 67.797 194.310, 62.797 192.385, 61.685 190.759, 60.895 189.037, 60.345 187.255, 60.045 185.448, 60.002 183.654, 60.217 181.908, 60.685 180.247, 61.396 178.704, 62.337 177.310, 63.487 176.094, 64.825 175.081, 66.321 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 105.413, 186.990 114.073, 191.990 122.734, 196.990 131.394, 201.990 140.054, 206.990 148.714, 211.990 157.375, 216.990 163.919, 220.769 168.919, 212.109 173.919, 203.448 178.919, 194.788 183.919, 186.128 188.919, 177.468 189.253, 176.889 180.593, 171.889 171.933, 166.889 163.272, 161.889 154.612, 156.889 145.952, 151.889 137.292, 146.889 130.747, 143.111 125.747, 151.771 120.747, 160.431 115.747, 169.092 110.747, 177.752 105.747, 186.412 105.413, 186.990 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 130.747, 143.111 139.407, 148.111 148.067, 153.111 156.728, 158.111 165.388, 163.111 174.048, 168.111 182.708, 173.111 189.253, 176.889 194.253, 168.229 199.253, 159.569 204.253, 150.908 209.253, 142.248 214.253, 133.588 214.587, 133.010 205.927, 128.010 197.266, 123.010 188.606, 118.010 179.946, 113.010 171.286, 108.010 162.625, 103.010 156.081, 99.231 151.081, 107.891 146.081, 116.552 141.081, 125.212 136.081, 133.872 131.081, 142.532 130.747, 143.111 " />
</g>
</g>
</svg>

12
frontend/src/lib/components/icons/hue_lightbulb_e14.svg

@ -0,0 +1,12 @@
<?xml version='1.0' encoding='ascii'?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320">
<g id="View Layer_LineSet" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="View Layer_LineSet">
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes">
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 169.178, 251.497 166.652, 253.304 166.044, 253.780 165.431, 254.331 164.979, 254.807 163.612, 256.633 163.400, 256.858 163.119, 257.060 162.797, 257.221 162.466, 257.324 162.157, 257.359 160.000, 257.359 157.843, 257.359 157.534, 257.324 157.203, 257.221 156.881, 257.060 156.600, 256.858 156.388, 256.633 155.021, 254.807 154.569, 254.331 153.956, 253.780 153.348, 253.304 150.822, 251.497 150.286, 251.020 149.872, 250.470 149.692, 249.994 149.157, 240.008 148.649, 230.521 153.262, 231.514 154.360, 231.728 155.582, 231.901 156.846, 232.037 158.057, 232.101 160.000, 232.154 161.943, 232.101 163.154, 232.037 164.418, 231.901 165.640, 231.728 166.738, 231.514 171.351, 230.521 170.816, 240.506 170.308, 249.994 170.128, 250.470 169.714, 251.020 169.178, 251.497 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 136.024, 158.261 132.797, 157.704 126.260, 155.940 127.741, 165.767 129.256, 172.976 131.568, 181.450 134.450, 190.361 137.622, 198.836 140.771, 206.044 144.402, 213.615 144.709, 214.426 144.934, 215.364 145.016, 216.176 145.016, 217.333 145.113, 218.093 145.397, 218.832 145.849, 219.500 146.438, 220.050 147.124, 220.446 147.860, 220.660 152.699, 221.422 153.775, 221.636 155.020, 221.820 156.323, 221.968 157.559, 222.041 158.630, 222.098 160.000, 222.083 161.370, 222.098 162.441, 222.041 163.677, 221.968 164.980, 221.820 166.225, 221.636 167.301, 221.422 172.140, 220.660 172.876, 220.446 173.562, 220.050 174.151, 219.500 174.603, 218.832 174.887, 218.093 174.984, 217.333 174.984, 216.176 175.066, 215.364 175.291, 214.426 175.598, 213.615 179.229, 206.044 182.378, 198.836 185.550, 190.361 188.432, 181.450 190.744, 172.976 192.259, 165.767 193.740, 155.940 187.203, 157.704 183.976, 158.261 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 183.976, 158.261 183.426, 158.356 176.872, 158.845 168.439, 159.172 160.000, 159.327 151.561, 159.172 143.128, 158.845 136.574, 158.356 136.024, 158.261 " />
<path style="fill:#ff0000;fill-opacity:0.666" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 165.524, 59.271 164.408, 58.717 163.268, 58.369 162.182, 58.250 160.000, 58.250 157.818, 58.250 156.732, 58.369 155.592, 58.717 154.476, 59.271 153.461, 59.992 152.616, 60.833 151.998, 61.735 149.009, 67.164 146.179, 72.594 143.935, 77.048 141.526, 82.302 139.319, 87.556 137.649, 92.010 136.060, 96.646 134.650, 101.100 133.185, 106.354 131.886, 111.608 130.951, 116.062 130.101, 120.698 129.357, 125.152 128.599, 130.406 127.943, 135.660 127.489, 140.114 126.715, 150.084 126.260, 155.940 132.797, 157.704 136.024, 158.260 136.024, 158.259 136.338, 148.264 136.431, 145.301 136.933, 140.114 137.364, 135.660 137.798, 131.167 137.928, 130.406 138.824, 125.152 139.583, 120.698 140.019, 118.141 140.563, 116.062 141.729, 111.608 143.008, 106.723 143.151, 106.354 145.193, 101.100 146.649, 97.353 147.071, 96.646 149.838, 92.010 150.804, 90.391 153.784, 87.556 155.312, 86.103 160.000, 84.655 164.688, 86.103 166.216, 87.556 169.196, 90.391 170.162, 92.010 172.929, 96.646 173.351, 97.353 174.807, 101.100 176.849, 106.354 176.992, 106.723 178.271, 111.608 179.437, 116.062 179.981, 118.141 180.417, 120.698 181.176, 125.152 182.072, 130.406 182.202, 131.167 182.636, 135.660 183.067, 140.114 183.569, 145.301 183.884, 155.296 183.977, 158.261 183.978, 158.260 187.203, 157.704 193.740, 155.940 192.966, 145.970 192.511, 140.114 192.057, 135.660 191.401, 130.406 190.643, 125.152 189.899, 120.698 189.049, 116.062 188.114, 111.608 186.815, 106.354 185.350, 101.100 183.940, 96.646 182.351, 92.010 180.681, 87.556 178.474, 82.302 176.065, 77.048 173.821, 72.594 170.991, 67.164 168.002, 61.735 167.384, 60.833 166.539, 59.992 165.524, 59.271 " />
<path style="fill:#ff0000;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 164.688, 86.103 160.000, 84.655 155.312, 86.103 153.784, 87.556 150.804, 90.391 149.838, 92.010 147.071, 96.646 146.649, 97.353 145.193, 101.100 143.151, 106.354 143.008, 106.723 141.729, 111.608 140.563, 116.062 140.019, 118.141 139.583, 120.698 138.824, 125.152 137.928, 130.406 137.798, 131.167 137.364, 135.660 136.933, 140.114 136.431, 145.301 136.118, 155.296 136.025, 158.261 136.026, 158.261 136.574, 158.356 143.128, 158.845 151.561, 159.172 160.000, 159.327 168.439, 159.172 176.872, 158.845 183.426, 158.356 183.976, 158.260 183.976, 158.259 183.662, 148.264 183.569, 145.301 183.067, 140.114 182.636, 135.660 182.202, 131.167 182.072, 130.406 181.176, 125.152 180.417, 120.698 179.981, 118.141 179.437, 116.062 178.271, 111.608 176.992, 106.723 176.849, 106.354 174.807, 101.100 173.351, 97.353 172.929, 96.646 170.162, 92.010 169.196, 90.391 166.216, 87.556 164.688, 86.103 " />
</g>
</g>
</svg>

10
frontend/src/lib/components/icons/hue_lightbulb_e27.svg

@ -0,0 +1,10 @@
<?xml version='1.0' encoding='ascii'?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320">
<g id="View Layer_LineSet" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="View Layer_LineSet">
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes">
<path style="fill:#ff0000;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 105.776, 88.534 108.781, 98.072 111.786, 107.609 114.791, 117.147 117.115, 124.523 127.092, 125.202 137.069, 125.881 139.426, 126.042 141.671, 126.174 144.307, 126.293 147.074, 126.388 149.703, 126.449 151.935, 126.469 161.935, 126.469 168.065, 126.469 170.297, 126.449 172.926, 126.388 175.693, 126.293 178.329, 126.174 180.574, 126.042 190.551, 125.363 200.527, 124.683 202.885, 124.523 205.890, 114.985 208.895, 105.447 211.900, 95.909 214.224, 88.534 214.400, 83.212 213.511, 77.890 211.327, 73.391 205.522, 68.892 196.391, 64.814 195.448, 64.393 185.689, 62.213 185.376, 62.143 175.893, 60.852 172.658, 60.485 168.835, 60.174 165.004, 59.966 161.749, 59.893 158.251, 59.893 154.996, 59.966 151.165, 60.174 147.342, 60.485 144.107, 60.852 134.624, 62.143 124.865, 64.323 124.552, 64.393 115.421, 68.471 114.478, 68.892 108.673, 73.391 106.489, 77.890 105.600, 83.212 105.776, 88.534 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 182.407, 226.829 181.225, 236.759 181.206, 236.918 180.513, 242.746 180.189, 243.597 179.436, 244.601 178.367, 245.605 177.146, 246.456 175.202, 247.603 173.855, 248.476 172.489, 249.506 171.311, 250.535 170.500, 251.408 166.395, 256.699 166.135, 256.952 165.788, 257.166 165.406, 257.309 165.047, 257.359 160.000, 257.359 154.953, 257.359 154.594, 257.309 154.212, 257.166 153.865, 256.952 153.605, 256.699 149.500, 251.408 148.689, 250.535 147.511, 249.506 146.145, 248.476 144.798, 247.603 142.854, 246.456 141.633, 245.605 140.564, 244.601 139.811, 243.597 139.487, 242.746 139.164, 240.032 137.985, 230.119 136.828, 220.396 146.248, 221.390 148.492, 221.604 150.986, 221.777 153.557, 221.891 156.042, 221.978 158.257, 222.008 159.989, 222.019 161.721, 222.029 163.958, 221.976 166.431, 221.912 169.014, 221.776 171.508, 221.603 173.752, 221.389 183.172, 220.395 182.407, 226.829 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 129.157, 183.080 129.895, 193.053 130.633, 203.026 130.839, 205.808 133.541, 210.084 143.487, 211.125 145.099, 211.294 147.296, 211.508 149.839, 211.692 152.492, 211.816 155.025, 211.910 157.196, 211.942 162.789, 211.973 164.974, 211.916 167.499, 211.843 170.160, 211.695 172.703, 211.511 174.901, 211.297 184.846, 210.255 186.458, 210.087 189.161, 205.808 189.899, 195.835 190.637, 185.862 190.843, 183.080 191.941, 173.141 192.012, 172.503 193.608, 162.632 194.038, 159.975 196.044, 150.179 196.613, 147.404 199.086, 137.715 199.344, 136.703 202.136, 127.100 202.885, 124.523 192.908, 125.202 182.931, 125.881 180.574, 126.042 178.329, 126.174 175.693, 126.293 172.926, 126.388 170.297, 126.449 168.065, 126.469 158.065, 126.469 151.935, 126.469 149.703, 126.449 147.074, 126.388 144.307, 126.293 141.671, 126.174 139.426, 126.042 129.449, 125.363 119.473, 124.683 117.115, 124.523 119.906, 134.125 120.656, 136.703 123.129, 146.392 123.387, 147.404 125.394, 157.200 125.962, 159.975 127.559, 169.847 127.988, 172.503 129.086, 182.443 129.157, 183.080 " />
</g>
</g>
</svg>

15
frontend/src/lib/components/icons/hue_lightbulb_gu10.svg

@ -0,0 +1,15 @@
<?xml version='1.0' encoding='ascii'?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320">
<g id="View Layer_LineSet" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="View Layer_LineSet">
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes">
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 188.922, 250.354 188.970, 250.498 188.970, 254.785 188.300, 256.814 186.392, 258.534 183.538, 259.683 180.171, 260.087 176.803, 259.683 173.949, 258.534 172.041, 256.814 171.742, 255.906 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 148.258, 255.906 147.959, 256.814 146.051, 258.534 143.197, 259.683 139.829, 260.087 136.462, 259.683 133.608, 258.534 131.700, 256.814 131.030, 254.785 131.030, 250.498 131.078, 250.354 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 218.164, 180.475 221.584, 176.011 225.378, 170.558 228.968, 164.945 231.806, 160.026 236.526, 151.210 238.462, 147.594 228.462, 147.594 218.462, 147.594 208.462, 147.594 198.462, 147.594 188.462, 147.594 178.462, 147.594 168.462, 147.594 158.462, 147.594 148.462, 147.594 138.462, 147.594 128.462, 147.594 118.462, 147.594 108.462, 147.594 98.462, 147.594 88.462, 147.594 81.538, 147.594 86.258, 156.410 88.194, 160.026 91.032, 164.945 94.622, 170.558 98.416, 176.011 101.836, 180.475 106.401, 186.044 108.554, 189.303 110.081, 192.228 111.387, 195.891 112.318, 200.393 112.803, 205.134 112.803, 215.134 112.803, 222.979 114.007, 229.321 116.406, 235.654 118.811, 239.693 122.163, 243.560 125.848, 246.958 129.227, 249.439 131.078, 250.354 133.146, 251.377 138.664, 253.616 143.346, 255.124 147.159, 255.767 148.258, 255.906 152.905, 256.493 160.000, 257.120 167.095, 256.493 171.742, 255.906 172.841, 255.767 176.654, 255.124 181.336, 253.616 186.854, 251.377 188.922, 250.354 190.773, 249.439 194.152, 246.958 197.837, 243.560 201.189, 239.693 203.594, 235.654 205.993, 229.321 207.197, 222.979 207.197, 212.979 207.197, 205.134 207.682, 200.393 208.613, 195.891 209.919, 192.228 211.446, 189.303 213.599, 186.044 218.164, 180.475 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 72.589, 149.749 79.225, 157.230 81.332, 159.606 89.392, 165.524 93.098, 168.245 102.062, 172.678 107.436, 175.336 116.954, 178.402 123.793, 180.604 133.630, 182.403 141.542, 183.849 151.524, 184.441 160.000, 184.944 169.982, 184.352 178.458, 183.849 188.295, 182.051 196.207, 180.604 205.725, 177.538 212.564, 175.336 221.528, 170.903 226.902, 168.245 234.962, 162.327 238.668, 159.606 245.304, 152.125 247.411, 149.749 251.908, 140.817 252.795, 139.053 254.408, 129.184 254.613, 127.930 254.613, 117.930 254.613, 117.477 253.000, 107.608 252.795, 106.354 248.299, 97.422 247.411, 95.658 240.775, 88.177 238.668, 85.801 230.608, 79.883 226.902, 77.162 217.938, 72.729 212.564, 70.071 203.046, 67.006 196.207, 64.803 186.370, 63.005 178.458, 61.558 168.476, 60.966 160.000, 60.463 150.018, 61.055 141.542, 61.558 131.705, 63.356 123.793, 64.803 114.275, 67.869 107.436, 70.071 98.472, 74.504 93.098, 77.162 85.038, 83.080 81.332, 85.801 74.696, 93.283 72.589, 95.658 68.092, 104.591 67.205, 106.354 65.592, 116.223 65.387, 117.477 65.387, 127.477 65.387, 127.930 67.000, 137.799 67.205, 139.053 71.701, 147.985 72.589, 149.749 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 254.613, 117.477 253.000, 127.346 252.795, 128.600 248.299, 137.532 247.411, 139.295 240.775, 146.776 238.668, 149.152 230.608, 155.070 226.902, 157.792 217.938, 162.225 212.564, 164.882 203.046, 167.948 196.207, 170.151 186.370, 171.949 178.458, 173.395 168.476, 173.988 160.000, 174.491 150.018, 173.898 141.542, 173.395 131.705, 171.597 123.793, 170.151 114.275, 167.085 107.436, 164.882 98.472, 160.449 93.098, 157.792 85.038, 151.873 81.332, 149.152 74.696, 141.671 72.589, 139.295 68.092, 130.363 67.205, 128.600 65.592, 118.731 65.387, 117.477 " />
<path style="fill:#ff0000;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 89.099, 88.866 89.043, 88.906 82.408, 96.388 81.157, 97.797 76.661, 106.729 76.301, 107.444 74.688, 117.313 74.661, 117.477 74.780, 118.203 74.939, 119.178 76.301, 127.509 80.797, 136.441 81.157, 137.156 87.793, 144.637 89.043, 146.047 97.104, 151.965 99.656, 153.840 108.620, 158.273 112.588, 160.235 122.107, 163.301 127.342, 164.987 137.179, 166.785 143.351, 167.914 153.334, 168.506 160.000, 168.902 169.982, 168.309 176.649, 167.914 186.486, 166.115 192.658, 164.987 202.176, 161.921 207.412, 160.235 216.375, 155.802 220.344, 153.840 228.404, 147.921 230.957, 146.047 237.592, 138.566 238.843, 137.156 243.339, 128.224 243.699, 127.509 245.061, 119.178 245.220, 118.203 245.339, 117.477 " />
<path style="fill:#ff0000;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 230.901, 88.866 230.957, 88.906 237.592, 96.388 238.843, 97.797 243.339, 106.729 243.699, 107.444 245.312, 117.313 245.339, 117.477 " />
<path style="fill:#ff0000;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 245.220, 118.203 243.699, 108.897 239.203, 99.965 238.843, 99.250 237.001, 95.742 235.191, 93.702 230.901, 88.866 229.299, 87.059 221.238, 81.141 218.934, 79.449 209.970, 75.016 206.304, 73.203 196.785, 70.137 191.895, 68.561 182.058, 66.763 176.260, 65.703 166.277, 65.111 160.000, 64.738 150.018, 65.331 143.740, 65.703 133.903, 67.502 128.105, 68.561 118.587, 71.627 113.696, 73.203 104.732, 77.636 101.066, 79.449 93.006, 85.367 90.701, 87.059 89.099, 88.866 84.809, 93.702 82.999, 95.742 81.157, 99.250 76.661, 108.182 76.301, 108.897 74.780, 118.203 " />
</g>
</g>
</svg>

9
frontend/src/lib/components/icons/hue_motionsensor.svg

@ -0,0 +1,9 @@
<?xml version='1.0' encoding='ascii'?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320">
<g id="View Layer_LineSet" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="View Layer_LineSet">
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes">
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 148.145, 100.400 158.097, 99.419 160.000, 99.232 169.952, 100.212 171.855, 100.400 181.425, 103.302 183.255, 103.858 192.074, 108.572 193.761, 109.473 201.491, 115.817 202.969, 117.031 209.313, 124.761 210.527, 126.239 215.241, 135.058 216.142, 136.745 219.045, 146.314 219.600, 148.145 220.581, 158.097 220.768, 160.000 219.788, 169.952 219.600, 171.855 216.698, 181.425 216.142, 183.255 211.428, 192.074 210.527, 193.761 204.183, 201.491 202.969, 202.969 195.239, 209.313 193.761, 210.527 184.942, 215.241 183.255, 216.142 173.686, 219.045 171.855, 219.600 165.000, 220.276 165.000, 230.276 165.000, 240.276 165.000, 250.276 165.000, 260.000 175.000, 260.000 185.000, 260.000 195.000, 260.000 205.000, 260.000 215.000, 260.000 220.000, 260.000 229.877, 258.436 232.361, 258.042 241.271, 253.502 243.511, 252.361 250.582, 245.290 252.361, 243.511 256.901, 234.601 258.042, 232.361 259.607, 222.484 260.000, 220.000 260.000, 210.000 260.000, 200.000 260.000, 190.000 260.000, 180.000 260.000, 170.000 260.000, 160.000 260.000, 150.000 260.000, 140.000 260.000, 130.000 260.000, 120.000 260.000, 110.000 260.000, 100.000 258.436, 90.123 258.042, 87.639 253.502, 78.729 252.361, 76.489 245.290, 69.418 243.511, 67.639 234.601, 63.099 232.361, 61.958 222.484, 60.393 220.000, 60.000 210.000, 60.000 200.000, 60.000 190.000, 60.000 180.000, 60.000 170.000, 60.000 160.000, 60.000 150.000, 60.000 140.000, 60.000 130.000, 60.000 120.000, 60.000 110.000, 60.000 100.000, 60.000 90.123, 61.564 87.639, 61.958 78.729, 66.498 76.489, 67.639 69.418, 74.710 67.639, 76.489 63.099, 85.399 61.958, 87.639 60.393, 97.516 60.000, 100.000 60.000, 110.000 60.000, 120.000 60.000, 130.000 60.000, 140.000 60.000, 150.000 60.000, 160.000 60.000, 170.000 60.000, 180.000 60.000, 190.000 60.000, 200.000 60.000, 210.000 60.000, 220.000 61.564, 229.877 61.958, 232.361 66.498, 241.271 67.639, 243.511 74.710, 250.582 76.489, 252.361 85.399, 256.901 87.639, 258.042 97.516, 259.607 100.000, 260.000 110.000, 260.000 120.000, 260.000 130.000, 260.000 140.000, 260.000 150.000, 260.000 155.000, 260.000 155.000, 250.000 155.000, 240.000 155.000, 230.000 155.000, 220.276 148.145, 219.600 138.575, 216.698 136.745, 216.142 127.926, 211.428 126.239, 210.527 118.509, 204.183 117.031, 202.969 110.687, 195.239 109.473, 193.761 104.759, 184.942 103.858, 183.255 100.955, 173.686 100.400, 171.855 99.419, 161.903 99.232, 160.000 100.212, 150.048 100.400, 148.145 103.302, 138.575 103.858, 136.745 108.572, 127.926 109.473, 126.239 115.817, 118.509 117.031, 117.031 124.761, 110.687 126.239, 109.473 135.058, 104.759 136.745, 103.858 146.314, 100.955 148.145, 100.400 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 202.172, 188.178 206.859, 179.410 209.745, 169.895 210.720, 160.000 209.745, 150.105 206.859, 140.590 202.172, 131.822 195.864, 124.136 188.178, 117.828 179.410, 113.141 169.895, 110.255 160.000, 109.280 150.105, 110.255 140.590, 113.141 131.822, 117.828 124.136, 124.136 117.828, 131.822 113.141, 140.590 110.255, 150.105 109.280, 160.000 110.255, 169.895 113.141, 179.410 117.828, 188.178 124.136, 195.864 131.822, 202.172 140.590, 206.859 150.105, 209.745 160.000, 210.720 169.895, 209.745 179.410, 206.859 188.178, 202.172 195.864, 195.864 202.172, 188.178 " />
</g>
</g>
</svg>

9
frontend/src/lib/components/icons/hue_playbar.svg

@ -0,0 +1,9 @@
<?xml version='1.0' encoding='ascii'?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320">
<g id="View Layer_LineSet" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="View Layer_LineSet">
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes">
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 179.499, 244.450 178.019, 248.678 175.637, 252.470 172.470, 255.637 168.678, 258.019 164.450, 259.499 160.000, 260.000 170.000, 260.000 180.000, 260.000 184.450, 259.499 188.678, 258.019 192.470, 255.637 195.637, 252.470 198.019, 248.678 199.499, 244.450 200.000, 240.000 200.000, 230.000 200.000, 220.000 200.000, 210.000 200.000, 200.000 200.000, 190.000 200.000, 180.000 200.000, 170.000 200.000, 160.000 200.000, 150.000 200.000, 140.000 200.000, 130.000 200.000, 120.000 200.000, 110.000 200.000, 100.000 200.000, 90.000 200.000, 80.000 199.499, 75.550 198.019, 71.322 195.637, 67.530 192.470, 64.363 188.678, 61.981 184.450, 60.501 180.000, 60.000 170.000, 60.000 160.000, 60.000 164.450, 60.501 168.678, 61.981 172.470, 64.363 175.637, 67.530 178.019, 71.322 179.499, 75.550 180.000, 80.000 180.000, 90.000 180.000, 100.000 180.000, 110.000 180.000, 120.000 180.000, 130.000 180.000, 140.000 180.000, 150.000 180.000, 160.000 180.000, 170.000 180.000, 180.000 180.000, 190.000 180.000, 200.000 180.000, 210.000 180.000, 220.000 180.000, 230.000 180.000, 240.000 179.499, 244.450 " />
<path style="fill:#ff0000;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 140.000, 260.000 150.000, 260.000 160.000, 260.000 164.450, 259.499 168.678, 258.019 172.470, 255.637 175.637, 252.470 178.019, 248.678 179.499, 244.450 180.000, 240.000 180.000, 230.000 180.000, 220.000 180.000, 210.000 180.000, 200.000 180.000, 190.000 180.000, 180.000 180.000, 170.000 180.000, 160.000 180.000, 150.000 180.000, 140.000 180.000, 130.000 180.000, 120.000 180.000, 110.000 180.000, 100.000 180.000, 90.000 180.000, 80.000 179.499, 75.550 178.019, 71.322 175.637, 67.530 172.470, 64.363 168.678, 61.981 164.450, 60.501 160.000, 60.000 150.000, 60.000 140.000, 60.000 135.550, 60.501 131.322, 61.981 127.530, 64.363 124.363, 67.530 121.981, 71.322 120.501, 75.550 120.000, 80.000 120.000, 90.000 120.000, 100.000 120.000, 110.000 120.000, 120.000 120.000, 130.000 120.000, 140.000 120.000, 150.000 120.000, 160.000 120.000, 170.000 120.000, 180.000 120.000, 190.000 120.000, 200.000 120.000, 210.000 120.000, 220.000 120.000, 230.000 120.000, 240.000 120.501, 244.450 121.981, 248.678 124.363, 252.470 127.530, 255.637 131.322, 258.019 135.550, 259.499 140.000, 260.000 " />
</g>
</g>
</svg>

10
frontend/src/lib/components/icons/hue_signe.svg

@ -0,0 +1,10 @@
<?xml version='1.0' encoding='ascii'?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320">
<g id="View Layer_LineSet" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="View Layer_LineSet">
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes">
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 90.402, 270.000 90.402, 280.000 90.706, 283.473 91.608, 286.840 93.081, 290.000 95.081, 292.856 97.546, 295.321 100.402, 297.321 103.561, 298.794 106.929, 299.696 110.402, 300.000 120.402, 300.000 130.402, 300.000 140.402, 300.000 150.402, 300.000 160.402, 300.000 170.402, 300.000 180.402, 300.000 190.402, 300.000 200.402, 300.000 210.402, 300.000 220.402, 300.000 229.598, 300.000 229.598, 290.000 229.294, 286.527 228.392, 283.160 226.919, 280.000 224.919, 277.144 222.454, 274.679 219.598, 272.680 216.439, 271.206 213.071, 270.304 209.598, 270.000 199.598, 270.000 189.598, 270.000 179.598, 270.000 169.598, 270.000 159.598, 270.000 149.598, 270.000 139.598, 270.000 129.598, 270.000 119.598, 270.000 109.598, 270.000 99.598, 270.000 90.402, 270.000 " />
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 162.071, 240.000 155.000, 232.929 155.000, 222.929 155.000, 212.929 155.000, 202.929 155.000, 192.929 155.000, 182.929 155.000, 172.929 155.000, 162.929 155.000, 152.929 155.000, 142.929 155.000, 132.929 155.000, 122.929 155.000, 112.929 155.000, 102.929 155.000, 92.929 155.000, 82.929 155.000, 72.929 155.000, 62.929 155.000, 52.929 155.000, 42.929 155.000, 32.929 155.000, 22.929 155.000, 20.000 145.000, 20.000 138.189, 20.000 138.189, 30.000 138.189, 40.000 138.189, 50.000 138.189, 60.000 138.189, 70.000 138.189, 80.000 138.189, 90.000 138.189, 100.000 138.189, 110.000 138.189, 120.000 138.189, 130.000 138.189, 140.000 138.189, 150.000 138.189, 160.000 138.189, 170.000 138.189, 180.000 138.189, 190.000 138.189, 200.000 138.189, 210.000 138.189, 220.000 138.189, 230.000 138.189, 240.000 138.189, 245.000 138.189, 250.000 138.381, 251.951 138.950, 253.827 139.874, 255.556 141.118, 257.071 142.633, 258.315 144.362, 259.239 146.238, 259.808 148.189, 260.000 150.000, 260.000 160.000, 260.000 170.000, 260.000 180.000, 260.000 181.811, 260.000 181.811, 250.000 181.811, 240.000 171.811, 240.000 162.071, 240.000 " />
<path style="fill:#ff0000;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 155.000, 20.000 155.000, 30.000 155.000, 40.000 155.000, 50.000 155.000, 60.000 155.000, 70.000 155.000, 80.000 155.000, 90.000 155.000, 100.000 155.000, 110.000 155.000, 120.000 155.000, 130.000 155.000, 140.000 155.000, 150.000 155.000, 160.000 155.000, 170.000 155.000, 180.000 155.000, 190.000 155.000, 200.000 155.000, 210.000 155.000, 220.000 155.000, 230.000 155.000, 232.929 162.071, 240.000 172.071, 240.000 181.811, 240.000 181.811, 230.000 181.811, 220.000 181.811, 210.000 181.811, 200.000 181.811, 190.000 181.811, 180.000 181.811, 170.000 181.811, 160.000 181.811, 150.000 181.811, 140.000 181.811, 130.000 181.811, 120.000 181.811, 110.000 181.811, 100.000 181.811, 90.000 181.811, 80.000 181.811, 70.000 181.811, 60.000 181.811, 50.000 181.811, 40.000 181.811, 30.000 181.619, 28.049 181.050, 26.173 180.126, 24.444 178.882, 22.929 177.367, 21.685 175.638, 20.761 173.762, 20.192 171.811, 20.000 161.811, 20.000 155.000, 20.000 " />
</g>
</g>
</svg>

2
frontend/src/lib/models/device.ts

@ -1,3 +1,4 @@
import type { IconName } from "$lib/components/Icon.svelte"
import type { ColorFlags, ColorRGB } from "./color"
export default interface Device {
@ -30,6 +31,7 @@ export interface HardwareState {
export interface HardwareMetadata {
firmwareVersion?: string
icon?: IconName
}
export interface State {

8
frontend/src/lib/utils/color.ts

@ -0,0 +1,8 @@
import type { ColorRGB } from "$lib/models/color";
export function rgbToHex(rgb: ColorRGB): string {
const r = Math.min(Math.round(rgb.red * 255), 255).toString(16).padStart(2, "0");
const g = Math.min(Math.round(rgb.green * 255), 255).toString(16).padStart(2, "0");
const b = Math.min(Math.round(rgb.blue * 255), 255).toString(16).padStart(2, "0");
return ["#", r, g, b].join("");
}

19
frontend/src/routes/+page.svelte

@ -1,7 +1,9 @@
<script lang="ts">
import Lamp from "$lib/components/Lamp.svelte";
import Icon from "$lib/components/Icon.svelte";
import Lamp, { DARK_COLOR } from "$lib/components/Lamp.svelte";
import Toolbar from "$lib/components/Toolbar.svelte";
import { getStateContext } from "$lib/contexts/StateContext.svelte";
import { rgb } from "$lib/models/color";
const {deviceList} = getStateContext();
</script>
@ -11,6 +13,20 @@
<Lamp device={device} />
{/each}
</div>
<div class="page" style="font-size: 8em">
<Icon name="generic_ball" brightColor={rgb(0.517,0.537,1.000)} darkColor={DARK_COLOR} />
<Icon name="generic_boob" brightColor={rgb(0.517,0.537,1.000)} darkColor={DARK_COLOR} />
<Icon name="generic_lamp" brightColor={rgb(0.667,0.750,1.000)} darkColor={DARK_COLOR} />
<Icon name="generic_strip" brightColor={rgb(0.667,0.750,1.000)} darkColor={DARK_COLOR} />
<Icon name="hue_adore_tube" brightColor={rgb(1,1,0)} darkColor={DARK_COLOR} />
<Icon name="hue_signe" brightColor={rgb(0.300,1.000,0.300)} darkColor={DARK_COLOR} />
<Icon name="hue_dimmerswitch" brightColor={rgb(0.517,0.537,1.000)} darkColor={DARK_COLOR} />
<Icon name="hue_playbar" brightColor={rgb(1,0,1)} darkColor={DARK_COLOR} />
<Icon name="hue_motionsensor" brightColor={rgb(1,1,1)} darkColor={DARK_COLOR} />
<Icon name="hue_lightbulb_e27" brightColor={rgb(1,1,1)} darkColor={DARK_COLOR} />
<Icon name="hue_lightbulb_e14" brightColor={rgb(1,1,1)} darkColor={DARK_COLOR} />
<Icon name="hue_lightbulb_gu10" brightColor={rgb(1,1,1)} darkColor={DARK_COLOR} />
</div>
<Toolbar />
<style>
@ -19,6 +35,7 @@
width: 100ch;
max-width: 95%;
margin-top: 1em;
font-size: 2em;
display: flex;
flex-direction: row;

7
go.mod

@ -7,21 +7,25 @@ require (
github.com/go-sql-driver/mysql v1.7.0
github.com/gobwas/glob v0.2.3
github.com/google/uuid v1.3.0
github.com/labstack/echo/v4 v4.10.0
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.1
golang.org/x/sync v0.1.0
)
require (
github.com/bocajim/dtls v0.0.0-20190226153416-329cdc841d4f // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-coap v0.0.0-20170214053734-ddcc80675fa4 // indirect
github.com/eriklupander/dtls v0.0.0-20190304211642-b36018226359 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.3.1 // indirect
github.com/labstack/echo/v4 v4.10.0 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.2.0 // indirect
@ -31,4 +35,5 @@ require (
golang.org/x/time v0.2.0 // indirect
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 // indirect
google.golang.org/grpc v1.21.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

13
go.sum

@ -50,6 +50,7 @@ github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peK
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -63,10 +64,14 @@ github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
@ -76,7 +81,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE=
golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
@ -90,12 +94,10 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
@ -111,7 +113,8 @@ google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

39
internal/formattools/compactidlist.go

@ -6,6 +6,45 @@ import (
"strings"
)
func CompactMatch(ids []string) string {
if len(ids) == 1 {
return ids[0]
}
shortestPrefix := ids[0]
for _, curr := range ids[1:] {
minLength := len(shortestPrefix)
if len(curr) < minLength {
minLength = len(curr)
}
if len(shortestPrefix) > minLength {
shortestPrefix = shortestPrefix[:minLength]
}
for i := 0; i < minLength; i++ {
if curr[i] != shortestPrefix[i] {
shortestPrefix = curr[:i]
break
}
}
}
sb := strings.Builder{}
sb.WriteString(shortestPrefix)
sb.WriteByte('{')
for i, curr := range ids {
if i != 0 {
sb.WriteByte(',')
}
sb.WriteString(curr[len(shortestPrefix):])
}
sb.WriteByte('}')
return sb.String()
}
func CompactIDList(ids []string) []string {
ids = append(ids[:0:0], ids...)
sort.Strings(ids)

39
internal/formattools/compactlist_test.go

@ -0,0 +1,39 @@
package formattools
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestCompactMatch(t *testing.T) {
table := []struct {
O string
I []string
}{
{
"nanoleaf:172.16.119.33:{abcd,dead,beef,0000}",
[]string{
"nanoleaf:172.16.119.33:abcd", "nanoleaf:172.16.119.33:dead",
"nanoleaf:172.16.119.33:beef", "nanoleaf:172.16.119.33:0000",
},
},
{
"nanoleaf:172.16.{119.33:abcd,22.33:dead}",
[]string{
"nanoleaf:172.16.119.33:abcd", "nanoleaf:172.16.22.33:dead",
},
},
{
"{nanoleaf:172.16.119.33:abcd,hue:172.16.119.34:81bd3cc3-cf22-4401-a5cb-a4bf18687270}",
[]string{
"nanoleaf:172.16.119.33:abcd", "hue:172.16.119.34:81bd3cc3-cf22-4401-a5cb-a4bf18687270",
},
},
}
for _, row := range table {
t.Run(row.O, func(t *testing.T) {
assert.Equal(t, row.O, CompactMatch(row.I))
})
}
}

19
services/effectenforcer/service.go

@ -13,10 +13,9 @@ import (
"time"
)
func NewService(resolver device.Resolver, sceneMap device.SceneMap) lucifer3.ActiveService {
func NewService(resolver device.Resolver) lucifer3.ActiveService {
s := &effectEnforcer{
resolver: resolver,
sceneMap: sceneMap,
list: make([]*effectEnforcerRun, 0, 16),
index: make(map[string]*effectEnforcerRun, 8),
@ -32,7 +31,6 @@ func NewService(resolver device.Resolver, sceneMap device.SceneMap) lucifer3.Act
type effectEnforcer struct {
mu sync.Mutex
resolver device.Resolver
sceneMap device.SceneMap
supportFlags map[string]device.SupportFlags
colorFlags map[string]device.ColorFlags
@ -68,8 +66,7 @@ func (s *effectEnforcer) HandleEvent(bus *lucifer3.EventBus, event lucifer3.Even
s.mu.Unlock()
case events.DeviceReady:
// If the device is managed by the effect enforcer, cause the effect to be
// re-ran.
// If the device is managed by the effect enforcer, cause the effect to run again
s.mu.Lock()
if run, ok := s.index[event.ID]; ok {
run.due = time.Now()
@ -98,11 +95,9 @@ func (s *effectEnforcer) HandleCommand(bus *lucifer3.EventBus, command lucifer3.
switch command := command.(type) {
case commands.Assign:
pointers := s.resolver.Resolve(command.Match)
allowedIDs := make([]string, 0, len(pointers))
deviceIDs := make([]string, 0, len(pointers))
for _, ptr := range pointers {
if s.sceneMap.SceneID(ptr.ID) == nil {
allowedIDs = append(allowedIDs, ptr.ID)
}
deviceIDs = append(deviceIDs, ptr.ID)
}
if len(pointers) == 0 {
if command.ID != nil {
@ -135,13 +130,13 @@ func (s *effectEnforcer) HandleCommand(bus *lucifer3.EventBus, command lucifer3.
match: command.Match,
id: id,
due: time.Now(),
ids: allowedIDs,
ids: deviceIDs,
effect: command.Effect,
}
s.list = append(s.list, newRun)
// Switch over the indices.
for _, id := range allowedIDs {
for _, id := range deviceIDs {
if oldRun := s.index[id]; oldRun != nil {
oldRun.remove(id)
}
@ -153,7 +148,7 @@ func (s *effectEnforcer) HandleCommand(bus *lucifer3.EventBus, command lucifer3.
go s.runLoop(bus)
}
for _, deviceID := range allowedIDs {
for _, deviceID := range deviceIDs {
bus.RunEvent(events.DeviceAssigned{
DeviceID: deviceID,
AssignmentID: gentools.Ptr(id),

10
services/httpapiv1/service.go

@ -5,6 +5,7 @@ import (
"git.aiterp.net/lucifer3/server/commands"
"git.aiterp.net/lucifer3/server/effects"
"git.aiterp.net/lucifer3/server/events"
"git.aiterp.net/lucifer3/server/services/script"
"git.aiterp.net/lucifer3/server/services/uistate"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
@ -52,6 +53,8 @@ func New(addr string) (lucifer3.Service, error) {
})
case input.PairDevice != nil:
bus.RunCommand(*input.PairDevice)
case input.ConnectDevice != nil:
bus.RunCommand(*input.ConnectDevice)
case input.ForgetDevice != nil:
bus.RunCommand(*input.ForgetDevice)
case input.SearchDevices != nil:
@ -60,6 +63,10 @@ func New(addr string) (lucifer3.Service, error) {
bus.RunCommand(*input.AddAlias)
case input.RemoveAlias != nil:
bus.RunCommand(*input.RemoveAlias)
case input.UpdateScript != nil:
bus.RunCommand(*input.UpdateScript)
case input.ExecuteScript != nil:
bus.RunCommand(*input.ExecuteScript)
default:
return c.String(400, "No supported command found in input")
}
@ -113,8 +120,11 @@ type commandInput struct {
AddAlias *commands.AddAlias `json:"addAlias,omitempty"`
RemoveAlias *commands.RemoveAlias `json:"removeAlias,omitempty"`
PairDevice *commands.PairDevice `json:"pairDevice,omitempty"`
ConnectDevice *commands.ConnectDevice `json:"connectDevice,omitempty"`
SearchDevices *commands.SearchDevices `json:"searchDevices,omitempty"`
ForgetDevice *commands.ForgetDevice `json:"forgetDevice,omitempty"`
UpdateScript *script.UpdateScript `json:"updateScript,omitempty"`
ExecuteScript *script.ExecuteScript `json:"executeScript,omitempty"`
}
type assignInput struct {

23
services/hue/bridge.go

@ -7,7 +7,6 @@ import (
lucifer3 "git.aiterp.net/lucifer3/server"
"git.aiterp.net/lucifer3/server/device"
"git.aiterp.net/lucifer3/server/events"
"git.aiterp.net/lucifer3/server/internal/color"
"git.aiterp.net/lucifer3/server/internal/gentools"
"golang.org/x/sync/errgroup"
"log"
@ -412,6 +411,9 @@ func (b *Bridge) Run(ctx context.Context, bus *lucifer3.EventBus) interface{} {
func (b *Bridge) makeCongruentLoop(ctx context.Context) {
for range b.triggerCongruenceCheckCh {
// I hate that I have to do this, but the SSE is not being reliable.
_, _ = b.RefreshAll()
if ctx.Err() != nil {
break
}
@ -428,8 +430,6 @@ func (b *Bridge) makeCongruentLoop(ctx context.Context) {
colorFlags := b.colorFlags
b.mu.Unlock()
newActiveStates := make(map[string]device.State, 0)
updates := make(map[string]ResourceUpdate)
for id, desired := range desiredStates {
active, activeOK := activeStates[id]
@ -452,7 +452,6 @@ func (b *Bridge) makeCongruentLoop(ctx context.Context) {
newActiveState := activeStates[id]
newActiveState.Power = gentools.Ptr(*desired.Power)
newActiveStates[id] = newActiveState
continue
}
@ -463,7 +462,6 @@ func (b *Bridge) makeCongruentLoop(ctx context.Context) {
updated := false
update := ResourceUpdate{}
newActiveState := activeStates[id]
if active.Color != nil && desired.Color != nil {
ac := *active.Color
@ -497,8 +495,6 @@ func (b *Bridge) makeCongruentLoop(ctx context.Context) {
acMirek = 1000000 / *ac.K
}
if acMirek != dcMirek {
newActiveState.Color = &color.Color{K: gentools.Ptr(*dc.K)}
update.Mirek = &dcMirek
updated = true
}
@ -508,7 +504,6 @@ func (b *Bridge) makeCongruentLoop(ctx context.Context) {
if active.Intensity != nil && desired.Intensity != nil {
if math.Abs(*active.Intensity-*desired.Intensity) >= 0.01 {
update.Brightness = gentools.Ptr(*desired.Intensity * 100)
newActiveState.Intensity = gentools.Ptr(*desired.Intensity)
updated = true
}
}
@ -516,7 +511,6 @@ func (b *Bridge) makeCongruentLoop(ctx context.Context) {
if updated {
update.TransitionDuration = gentools.Ptr(time.Millisecond * 101)
updates["light/"+*lightID] = update
newActiveStates[id] = newActiveState
}
}
@ -539,17 +533,6 @@ func (b *Bridge) makeCongruentLoop(ctx context.Context) {
log.Println("Failed to run update", err)
}
b.mu.Lock()
activeStates = b.activeStates
b.mu.Unlock()
activeStates = gentools.CopyMap(activeStates)
for id, state := range newActiveStates {
activeStates[id] = state
}
b.mu.Lock()
b.activeStates = activeStates
b.mu.Unlock()
cancel()
// Wait the remaining time for the rate limit

37
services/hue/data.go

@ -205,6 +205,22 @@ func (res *ResourceData) GenerateEvent(hostname string, resources map[string]*Re
}
buttonCount := 0
switch res.ProductData.ProductArchetype {
case "candle_bulb":
hwMeta.Icon = "hue_lightbulb_e14"
case "sultan_bulb":
hwMeta.Icon = "hue_lightbulb_e27"
case "hue_signe":
hwMeta.Icon = "hue_signe"
case "unknown_archetype":
switch res.ProductData.ProductName {
case "Hue motion sensor":
hwMeta.Icon = "hue_motionsensor"
case "Hue dimmer switch":
hwMeta.Icon = "hue_dimmerswitch"
}
}
if res.ProductData != nil {
hwMeta.FirmwareVersion = res.ProductData.SoftwareVersion
}
@ -365,6 +381,27 @@ type ResourceUpdate struct {
TransitionDuration *time.Duration
}
func (r ResourceUpdate) Split() []ResourceUpdate {
updates := make([]ResourceUpdate, 0, 2)
if r.Name != nil {
updates = append(updates, ResourceUpdate{Name: r.Name, TransitionDuration: r.TransitionDuration})
}
if r.Power != nil {
updates = append(updates, ResourceUpdate{Power: r.Power, TransitionDuration: r.TransitionDuration})
}
if r.ColorXY != nil {
updates = append(updates, ResourceUpdate{ColorXY: r.ColorXY, TransitionDuration: r.TransitionDuration})
}
if r.Brightness != nil {
updates = append(updates, ResourceUpdate{Brightness: r.Brightness, TransitionDuration: r.TransitionDuration})
}
if r.Mirek != nil {
updates = append(updates, ResourceUpdate{Mirek: r.Mirek, TransitionDuration: r.TransitionDuration})
}
return updates
}
func (r ResourceUpdate) MarshalJSON() ([]byte, error) {
chunks := make([]string, 0, 4)
if r.Name != nil {

12
services/mysqldb/migrations/20230828234420_script.sql

@ -0,0 +1,12 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE script (
name VARCHAR(255) NOT NULL PRIMARY KEY,
data JSON NOT NULL
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS script;
-- +goose StatementEnd

15
services/mysqldb/migrations/20230828235352_script_variable.sql

@ -0,0 +1,15 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE script_variable (
scope VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
value TEXT NOT NULL,
PRIMARY KEY (scope, name)
)
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS script_variable;
-- +goose StatementEnd

40
services/mysqldb/mysqlgen/db.go

@ -63,6 +63,12 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
if q.listDeviceInfosStmt, err = db.PrepareContext(ctx, listDeviceInfos); err != nil {
return nil, fmt.Errorf("error preparing query ListDeviceInfos: %w", err)
}
if q.listScriptVariablesStmt, err = db.PrepareContext(ctx, listScriptVariables); err != nil {
return nil, fmt.Errorf("error preparing query ListScriptVariables: %w", err)
}
if q.listScriptsStmt, err = db.PrepareContext(ctx, listScripts); err != nil {
return nil, fmt.Errorf("error preparing query ListScripts: %w", err)
}
if q.replaceDeviceAssignmentStmt, err = db.PrepareContext(ctx, replaceDeviceAssignment); err != nil {
return nil, fmt.Errorf("error preparing query ReplaceDeviceAssignment: %w", err)
}
@ -72,6 +78,12 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
if q.replaceDeviceInfoStmt, err = db.PrepareContext(ctx, replaceDeviceInfo); err != nil {
return nil, fmt.Errorf("error preparing query ReplaceDeviceInfo: %w", err)
}
if q.saveScriptStmt, err = db.PrepareContext(ctx, saveScript); err != nil {
return nil, fmt.Errorf("error preparing query SaveScript: %w", err)
}
if q.updateScriptVariablesStmt, err = db.PrepareContext(ctx, updateScriptVariables); err != nil {
return nil, fmt.Errorf("error preparing query UpdateScriptVariables: %w", err)
}
return &q, nil
}
@ -142,6 +154,16 @@ func (q *Queries) Close() error {
err = fmt.Errorf("error closing listDeviceInfosStmt: %w", cerr)
}
}
if q.listScriptVariablesStmt != nil {
if cerr := q.listScriptVariablesStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listScriptVariablesStmt: %w", cerr)
}
}
if q.listScriptsStmt != nil {
if cerr := q.listScriptsStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listScriptsStmt: %w", cerr)
}
}
if q.replaceDeviceAssignmentStmt != nil {
if cerr := q.replaceDeviceAssignmentStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing replaceDeviceAssignmentStmt: %w", cerr)
@ -157,6 +179,16 @@ func (q *Queries) Close() error {
err = fmt.Errorf("error closing replaceDeviceInfoStmt: %w", cerr)
}
}
if q.saveScriptStmt != nil {
if cerr := q.saveScriptStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing saveScriptStmt: %w", cerr)
}
}
if q.updateScriptVariablesStmt != nil {
if cerr := q.updateScriptVariablesStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing updateScriptVariablesStmt: %w", cerr)
}
}
return err
}
@ -209,9 +241,13 @@ type Queries struct {
listDeviceAssignmentsStmt *sql.Stmt
listDeviceAuthStmt *sql.Stmt
listDeviceInfosStmt *sql.Stmt
listScriptVariablesStmt *sql.Stmt
listScriptsStmt *sql.Stmt
replaceDeviceAssignmentStmt *sql.Stmt
replaceDeviceAuthStmt *sql.Stmt
replaceDeviceInfoStmt *sql.Stmt
saveScriptStmt *sql.Stmt
updateScriptVariablesStmt *sql.Stmt
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
@ -231,8 +267,12 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries {
listDeviceAssignmentsStmt: q.listDeviceAssignmentsStmt,
listDeviceAuthStmt: q.listDeviceAuthStmt,
listDeviceInfosStmt: q.listDeviceInfosStmt,
listScriptVariablesStmt: q.listScriptVariablesStmt,
listScriptsStmt: q.listScriptsStmt,
replaceDeviceAssignmentStmt: q.replaceDeviceAssignmentStmt,
replaceDeviceAuthStmt: q.replaceDeviceAuthStmt,
replaceDeviceInfoStmt: q.replaceDeviceInfoStmt,
saveScriptStmt: q.saveScriptStmt,
updateScriptVariablesStmt: q.updateScriptVariablesStmt,
}
}

11
services/mysqldb/mysqlgen/models.go

@ -34,3 +34,14 @@ type DeviceInfo struct {
Kind string
Data json.RawMessage
}
type Script struct {
Name string
Data json.RawMessage
}
type ScriptVariable struct {
Scope string
Name string
Value string
}

94
services/mysqldb/mysqlgen/script.sql.go

@ -0,0 +1,94 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
// source: script.sql
package mysqlgen
import (
"context"
"encoding/json"
)
const listScriptVariables = `-- name: ListScriptVariables :many
SELECT scope, name, value FROM script_variable
`
func (q *Queries) ListScriptVariables(ctx context.Context) ([]ScriptVariable, error) {
rows, err := q.query(ctx, q.listScriptVariablesStmt, listScriptVariables)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ScriptVariable{}
for rows.Next() {
var i ScriptVariable
if err := rows.Scan(&i.Scope, &i.Name, &i.Value); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listScripts = `-- name: ListScripts :many
SELECT name, data FROM script
`
func (q *Queries) ListScripts(ctx context.Context) ([]Script, error) {
rows, err := q.query(ctx, q.listScriptsStmt, listScripts)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Script{}
for rows.Next() {
var i Script
if err := rows.Scan(&i.Name, &i.Data); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const saveScript = `-- name: SaveScript :exec
REPLACE INTO script (name, data) VALUES (?, ?)
`
type SaveScriptParams struct {
Name string
Data json.RawMessage
}
func (q *Queries) SaveScript(ctx context.Context, arg SaveScriptParams) error {
_, err := q.exec(ctx, q.saveScriptStmt, saveScript, arg.Name, arg.Data)
return err
}
const updateScriptVariables = `-- name: UpdateScriptVariables :exec
REPLACE INTO script_variable (scope, name, value) VALUES (?, ?, ?)
`
type UpdateScriptVariablesParams struct {
Scope string
Name string
Value string
}
func (q *Queries) UpdateScriptVariables(ctx context.Context, arg UpdateScriptVariablesParams) error {
_, err := q.exec(ctx, q.updateScriptVariablesStmt, updateScriptVariables, arg.Scope, arg.Name, arg.Value)
return err
}

11
services/mysqldb/queries/script.sql

@ -0,0 +1,11 @@
-- name: ListScripts :many
SELECT * FROM script;
-- name: SaveScript :exec
REPLACE INTO script (name, data) VALUES (?, ?);
-- name: ListScriptVariables :many
SELECT * FROM script_variable;
-- name: UpdateScriptVariables :exec
REPLACE INTO script_variable (scope, name, value) VALUES (?, ?, ?);

99
services/mysqldb/service.go

@ -11,13 +11,16 @@ import (
"git.aiterp.net/lucifer3/server/events"
"git.aiterp.net/lucifer3/server/internal/gentools"
"git.aiterp.net/lucifer3/server/services/mysqldb/mysqlgen"
"git.aiterp.net/lucifer3/server/services/script"
"strings"
"time"
_ "github.com/go-sql-driver/mysql"
)
type database struct {
db *sql.DB
db *sql.DB
scriptVariables map[string]string
}
func (d *database) Active() bool {
@ -26,7 +29,7 @@ func (d *database) Active() bool {
func (d *database) HandleEvent(bus *lucifer3.EventBus, event lucifer3.Event) {
q := mysqlgen.New(d.db)
timeout, cancel := context.WithTimeout(context.Background(), time.Second/2)
timeout, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
switch event := event.(type) {
@ -119,6 +122,58 @@ func (d *database) HandleEvent(bus *lucifer3.EventBus, event lucifer3.Event) {
})
}
scripts, err := q.ListScripts(timeout)
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_list_scripts",
Message: "Database could not list scripts: " + err.Error(),
})
}
for _, scr := range scripts {
var lines []script.Line
err := json.Unmarshal(scr.Data, &lines)
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_read_script",
Message: "Database could not read script: " + err.Error(),
})
}
bus.RunCommand(script.UpdateScript{
Name: scr.Name,
Lines: lines,
})
}
scriptVariables, err := q.ListScriptVariables(timeout)
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_list_script_variables",
Message: "Database could not list script variables: " + err.Error(),
})
}
for _, variable := range scriptVariables {
d.scriptVariables[variable.Scope+"::"+variable.Name] = variable.Value
cmd := script.SetVariable{
Key: variable.Name,
Value: variable.Value,
}
scopeKind, scopeValue, _ := strings.Cut(variable.Scope, ":")
switch scopeKind {
case "devices":
cmd.Devices = strings.Split(scopeValue, ";")
case "match":
cmd.Match = gentools.Ptr(scopeValue)
}
bus.RunCommand(cmd)
}
case events.DeviceAccepted:
if event.Extras == nil {
event.Extras = map[string]string{}
@ -298,7 +353,43 @@ func (d *database) HandleEvent(bus *lucifer3.EventBus, event lucifer3.Event) {
}
}
func (d *database) HandleCommand(*lucifer3.EventBus, lucifer3.Command) {}
func (d *database) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) {
q := mysqlgen.New(d.db)
timeout, cancel := context.WithTimeout(context.Background(), time.Second/2)
defer cancel()
switch command := command.(type) {
case script.SetVariable:
scopeName := "global"
if command.Match != nil {
scopeName = "match:" + *command.Match
} else if command.Devices != nil {
scopeName = "devices:" + strings.Join(command.Devices, ";")
}
if d.scriptVariables[scopeName+"::"+command.Key] != command.Value {
_ = q.UpdateScriptVariables(timeout, mysqlgen.UpdateScriptVariablesParams{
Scope: scopeName,
Name: command.Key,
Value: command.Value,
})
}
case script.UpdateScript:
j, _ := json.Marshal(command.Lines)
err := q.SaveScript(timeout, mysqlgen.SaveScriptParams{
Name: command.Name,
Data: j,
})
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_save_script",
Message: "Failed to save script: " + err.Error(),
})
}
}
}
func Connect(host string, port int, username, password, dbname string) (lucifer3.ActiveService, error) {
db, err := sql.Open("mysql", fmt.Sprintf(
@ -317,5 +408,5 @@ func Connect(host string, port int, username, password, dbname string) (lucifer3
return nil, err
}
return &database{db: db}, nil
return &database{db: db, scriptVariables: make(map[string]string)}, nil
}

7
services/resolver.go

@ -154,6 +154,13 @@ func (r *Resolver) ensure(id string) *device.Pointer {
return r.pointers[len(r.pointers)-1]
}
func (r *Resolver) CompileMatcher(pattern string) device.PointerMatcher {
r.mu.Lock()
defer r.mu.Unlock()
return r.patternToGlob(pattern)
}
func (r *Resolver) patternToGlob(pattern string) glob.Glob {
if cached, ok := r.patternCache[pattern]; ok {
return cached

42
services/script/commands.go

@ -0,0 +1,42 @@
package script
import (
"fmt"
"strings"
)
type SetVariable struct {
Match *string
Devices []string
Key string
Value string
}
func (c SetVariable) CommandDescription() string {
switch {
case c.Match != nil:
return fmt.Sprintf("SetVariable(tag(%s), %s, %s)", *c.Match, c.Key, c.Value)
case c.Devices != nil:
return fmt.Sprintf("SetVariable(devices(%s), %s, %s)", strings.Join(c.Devices, ", "), c.Key, c.Value)
default:
return fmt.Sprintf("SetVariable(global, %s, %s)", c.Key, c.Value)
}
}
type UpdateScript struct {
Name string `json:"name"`
Lines []Line `json:"lines"`
}
func (c UpdateScript) CommandDescription() string {
return fmt.Sprintf("UpdateScript(%s, [%d lines...])", c.Name, len(c.Lines))
}
type ExecuteScript struct {
Name string `json:"name"`
Match string `json:"match"`
}
func (c ExecuteScript) CommandDescription() string {
return fmt.Sprintf("ExecuteScript(%s, %s)", c.Name, c.Match)
}

56
services/script/condition.go

@ -0,0 +1,56 @@
package script
import (
"log"
"strconv"
"strings"
)
type Condition struct {
Scope string `json:"scope"`
Key string `json:"key"`
Op string `json:"op"`
Value string `json:"value,omitempty"`
Not bool `json:"not,omitempty"`
}
func (c *Condition) Check(set VariableSet, match string, devices []string) bool {
var value string
switch c.Scope {
case "global":
value = set.Global(c.Key)
case "match":
value = set.Match(match, c.Key)
case "devices":
value = set.Devices(devices, c.Key)
default:
return c.Not
}
valueInt, valueIntErr := strconv.ParseInt(value, 10, 64)
targetInt, _ := strconv.ParseInt(c.Value, 10, 64)
switch c.Op {
case "eq":
return !c.Not == (value == c.Value)
case "neq":
return !c.Not == (value != c.Value)
case "gt":
return !c.Not == (valueInt > targetInt)
case "lt":
return !c.Not == (valueInt < targetInt)
case "gte":
log.Println(c.Not, valueInt, targetInt)
return !c.Not == (valueInt >= targetInt)
case "lte":
return !c.Not == (valueInt <= targetInt)
case "exists":
return !c.Not == (value != "")
case "contains":
return !c.Not == strings.Contains(value, c.Value)
case "numerical":
return !c.Not == (valueIntErr == nil)
default:
return c.Not
}
}

66
services/script/condition_test.go

@ -0,0 +1,66 @@
package script
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestCondition_Check(t *testing.T) {
variables := VariableSet{
"global": map[string]string{
"test": "32",
},
"devices:dummy:01;dummy:02": map[string]string{
"tstxo": "tsyeym",
},
"match:lucifer:tag:Soverom": map[string]string{
"mode": "daytime",
"stuff": "54",
},
}
table := []struct {
L string
R bool
C Condition
M string
D []string
}{
{
"global_gte", true,
Condition{"global", "test", "gte", "16", false},
"", []string{},
},
{
"global_!gte", true,
Condition{"global", "test", "gte", "64", true},
"", []string{},
},
{
"global_eq", true,
Condition{"global", "test", "eq", "32", false},
"", []string{},
},
{
"match_exists", true,
Condition{"devices", "tstxo", "exists", "", false},
"", []string{"dummy:01", "dummy:02"},
},
{
"match_!exists", false,
Condition{"devices", "tstxo", "exists", "", true},
"", []string{"dummy:01", "dummy:02"},
},
{
"devices_contains", true,
Condition{"match", "mode", "contains", "time", false},
"lucifer:tag:Soverom", []string{},
},
}
for _, row := range table {
t.Run(row.L, func(t *testing.T) {
assert.Equal(t, row.R, row.C.Check(variables, row.M, row.D))
})
}
}

33
services/script/script.go

@ -0,0 +1,33 @@
package script
import (
"git.aiterp.net/lucifer3/server/effects"
)
type Script struct {
Name string `json:"name,omitempty"`
Lines []Line `json:"lines,omitempty"`
}
type Line struct {
If *LineIf `json:"if,omitempty"`
Assign *LineAssign `json:"assign,omitempty"`
Set *LineSet `json:"set,omitempty"`
}
type LineSet struct {
Scope string `json:"scope"`
Key string `json:"key"`
Value string `json:"value"`
}
type LineAssign struct {
Match string `json:"match,omitempty"`
Effect effects.Serializable `json:"effect"`
}
type LineIf struct {
Condition *Condition `json:"condition,omitempty"`
Then []Line `json:"then,omitempty"`
Else []Line `json:"else,omitempty"`
}

118
services/script/service.go

@ -0,0 +1,118 @@
package script
import (
lucifer3 "git.aiterp.net/lucifer3/server"
"git.aiterp.net/lucifer3/server/commands"
"git.aiterp.net/lucifer3/server/device"
"git.aiterp.net/lucifer3/server/internal/formattools"
"git.aiterp.net/lucifer3/server/internal/gentools"
"sort"
"sync"
)
type service struct {
mu sync.Mutex
resolver device.Resolver
variables *Variables
scripts map[string]*Script
}
func (s *service) Active() bool {
return true
}
func (s *service) HandleEvent(_ *lucifer3.EventBus, _ lucifer3.Event) {}
func (s *service) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) {
switch command := command.(type) {
case UpdateScript:
s.mu.Lock()
s.scripts[command.Name] = &Script{
Name: command.Name,
Lines: command.Lines,
}
s.mu.Unlock()
case ExecuteScript:
s.mu.Lock()
script := s.scripts[command.Name]
s.mu.Unlock()
if script == nil {
break
}
variables := s.variables.Get()
devices := s.resolver.Resolve(command.Match)
sort.Slice(devices, func(i, j int) bool {
return devices[i].ID < devices[j].ID
})
s.runScript(bus, script.Lines, command.Match, map[string]bool{}, devices, variables)
}
}
func NewService(resolver device.Resolver, variables *Variables) lucifer3.Service {
return &service{
resolver: resolver,
variables: variables,
scripts: map[string]*Script{},
}
}
func (s *service) runScript(bus *lucifer3.EventBus, lines []Line, match string, matchedDevices map[string]bool, devices []device.Pointer, variables VariableSet) {
for _, line := range lines {
if line.If != nil {
devicesIDs := gentools.Map(devices, func(d device.Pointer) string { return d.ID })
if line.If.Condition.Check(variables, match, devicesIDs) {
s.runScript(bus, line.If.Then, match, matchedDevices, devices, variables)
} else {
s.runScript(bus, line.If.Else, match, matchedDevices, devices, variables)
}
} else if line.Assign != nil {
matcher := s.resolver.CompileMatcher(line.Assign.Match)
matched := make([]string, 0)
for _, dev := range devices {
if matchedDevices[dev.ID] {
continue
}
if dev.Matches(matcher) {
matchedDevices[dev.ID] = true
matched = append(matched, dev.ID)
}
}
if len(matched) > 0 {
bus.RunCommand(commands.Assign{
Match: formattools.CompactMatch(matched),
Effect: line.Assign.Effect.Effect,
})
}
} else {
if line.Set != nil {
switch line.Set.Scope {
case "devices":
bus.RunCommand(SetVariable{
Devices: gentools.Map(devices, func(d device.Pointer) string {
return d.ID
}),
Key: line.Set.Key,
Value: line.Set.Value,
})
case "match":
bus.RunCommand(SetVariable{
Match: gentools.Ptr(match),
Key: line.Set.Key,
Value: line.Set.Value,
})
case "global":
bus.RunCommand(SetVariable{
Key: line.Set.Key,
Value: line.Set.Value,
})
}
}
}
}
}

73
services/script/variables.go

@ -0,0 +1,73 @@
package script
import (
lucifer3 "git.aiterp.net/lucifer3/server"
"git.aiterp.net/lucifer3/server/internal/gentools"
"strings"
"sync"
)
type VariableSet map[string]map[string]string
func (v VariableSet) Global(key string) string {
return v["global"][key]
}
func (v VariableSet) Match(tag string, key string) string {
return v["match:"+tag][key]
}
func (v VariableSet) Devices(list []string, key string) string {
return v["devices:"+strings.Join(list, ";")][key]
}
type Variables struct {
mu sync.Mutex
vars VariableSet
}
func NewVariables() *Variables {
return &Variables{
vars: make(VariableSet, 8),
}
}
func (s *Variables) Get() VariableSet {
s.mu.Lock()
defer s.mu.Unlock()
return s.vars
}
func (s *Variables) Active() bool {
return true
}
func (s *Variables) HandleEvent(_ *lucifer3.EventBus, _ lucifer3.Event) {}
func (s *Variables) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) {
if sv, ok := command.(SetVariable); ok {
s.mu.Lock()
curr := gentools.CopyMap(s.vars)
s.mu.Unlock()
key := "global"
if sv.Match != nil {
key = "match:" + *sv.Match
} else if sv.Devices != nil {
key = "devices:" + strings.Join(sv.Devices, ";")
}
curr[key] = gentools.CopyMap(curr["global"])
if sv.Value == "" {
delete(curr[key], sv.Key)
} else {
curr[key][sv.Key] = sv.Value
}
s.mu.Lock()
s.vars = curr
s.mu.Unlock()
}
}

5
services/tradfri/bridge.go

@ -8,6 +8,7 @@ import (
"git.aiterp.net/lucifer3/server/internal/color"
"git.aiterp.net/lucifer3/server/internal/gentools"
"github.com/eriklupander/tradfri-go/tradfri"
"github.com/pkg/errors"
"math"
"math/rand"
"strings"
@ -77,7 +78,7 @@ func (b *Bridge) listen(bus *lucifer3.EventBus) {
if err != nil {
bus.RunEvent(events.DeviceFailed{
ID: b.id,
Error: "Unable to fetch IKEA devices",
Error: "Unable to fetch IKEA devices: " + err.Error(),
})
return
}
@ -243,7 +244,7 @@ const defaultCF = device.CFlagXY
func catchPanic(mutexes ...sync.Locker) (retErr error) {
if err, ok := recover().(error); ok {
retErr = err
retErr = errors.Wrap(err, "panik:")
}
for _, mx := range mutexes {

7
services/uistate/data.go

@ -6,12 +6,14 @@ import (
"git.aiterp.net/lucifer3/server/events"
"git.aiterp.net/lucifer3/server/internal/color"
"git.aiterp.net/lucifer3/server/internal/gentools"
"git.aiterp.net/lucifer3/server/services/script"
"github.com/google/uuid"
)
type Data struct {
Devices map[string]Device `json:"devices"`
Assignments map[uuid.UUID]Assignment `json:"assignments"`
Scripts map[string][]script.Line `json:"scripts"`
}
func (d *Data) WithPatch(patches ...Patch) Data {
@ -89,6 +91,10 @@ func (d *Data) WithPatch(patches ...Patch) Data {
newData.Assignments[pa.ID] = pa
}
}
if patch.Script != nil {
newData.Scripts[patch.Script.Name] = patch.Script.Lines
}
}
return newData
@ -98,6 +104,7 @@ func (d *Data) Copy() Data {
return Data{
Devices: gentools.CopyMap(d.Devices),
Assignments: gentools.CopyMap(d.Assignments),
Scripts: gentools.CopyMap(d.Scripts),
}
}

9
services/uistate/patch.go

@ -6,12 +6,14 @@ import (
"git.aiterp.net/lucifer3/server/effects"
"git.aiterp.net/lucifer3/server/events"
"git.aiterp.net/lucifer3/server/internal/color"
"git.aiterp.net/lucifer3/server/services/script"
"github.com/google/uuid"
)
type Patch struct {
Assignment *AssignmentPatch `json:"assignment,omitempty"`
Device *DevicePatch `json:"device,omitempty"`
Script *ScriptPatch `json:"script,omitempty"`
}
func (e Patch) VerboseKey() string {
@ -41,6 +43,8 @@ func (e Patch) EventDescription() string {
}
} else if e.Assignment != nil {
return fmt.Sprintf("uistate.Patch(assignment=%s)", e.Assignment.ID)
} else if e.Script != nil {
return fmt.Sprintf("uistate.Patch(script=%s)", e.Script.Name)
} else {
return "uistate.Patch"
}
@ -71,3 +75,8 @@ type AssignmentPatch struct {
Variables map[string]float64 `json:"variables,omitempty"`
Delete bool `json:"delete,omitempty"`
}
type ScriptPatch struct {
Name string
Lines []script.Line
}

14
services/uistate/service.go

@ -6,11 +6,19 @@ import (
"git.aiterp.net/lucifer3/server/effects"
"git.aiterp.net/lucifer3/server/events"
"git.aiterp.net/lucifer3/server/internal/gentools"
"git.aiterp.net/lucifer3/server/services/script"
"github.com/google/uuid"
"sync"
)
func NewService() lucifer3.ActiveService {
return &service{}
return &service{
data: Data{
Devices: map[string]Device{},
Assignments: map[uuid.UUID]Assignment{},
Scripts: map[string][]script.Line{},
},
}
}
type service struct {
@ -35,6 +43,10 @@ func (s *service) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command
Device: &DevicePatch{ID: id, DesiredState: gentools.ShallowCopy(&state)},
})
}
case script.UpdateScript:
patches = append(patches, Patch{
Script: &ScriptPatch{Name: command.Name, Lines: command.Lines},
})
}
if len(patches) > 0 {

Loading…
Cancel
Save