Browse Source

some more stuffs.

beelzebub
Gisle Aune 8 months ago
parent
commit
07c9398414
  1. 27
      device/pointer.go
  2. 4
      events/assignment.go
  3. BIN
      frontend/icons.blend
  4. 1
      frontend/src/lib/client/lucifer.ts
  5. 24
      frontend/src/lib/components/DeviceIcon.svelte
  6. 26
      frontend/src/lib/components/Lamp.svelte
  7. 75
      frontend/src/lib/components/Toolbar.svelte
  8. 8
      frontend/src/lib/components/icons/hexagon.svg
  9. 10
      frontend/src/lib/components/icons/hue_go.svg
  10. 8
      frontend/src/lib/components/icons/square.svg
  11. 8
      frontend/src/lib/components/icons/triangle.svg
  12. 112
      frontend/src/lib/contexts/StateContext.svelte
  13. 1
      frontend/src/lib/models/device.ts
  14. 2
      frontend/src/lib/models/uistate.ts
  15. 21
      frontend/src/routes/+page.svelte
  16. 96
      services/hue/bridge.go
  17. 6
      services/hue/data.go
  18. 23
      services/nanoleaf/data.go
  19. 6
      services/script/script.go
  20. 62
      services/script/service.go
  21. 10
      services/tradfri/service.go
  22. 24
      services/uistate/data.go
  23. 5
      services/uistate/patch.go

27
device/pointer.go

@ -3,9 +3,17 @@ package device
import (
"git.aiterp.net/lucifer3/server/internal/gentools"
"log"
"sort"
"strings"
)
var exclusivePrefixes = []string{
"lucifer:name:",
"lucifer:icon:",
"lucifer:room:",
"lucifer:group:",
}
type PointerMatcher interface {
Match(str string) bool
}
@ -26,19 +34,24 @@ func (p *Pointer) AddAlias(alias string) (added *string, removed *string) {
}
}
if strings.HasPrefix(alias, "lucifer:name:") {
for i, alias2 := range p.Aliases {
if strings.HasPrefix(alias2, "lucifer:name:") {
p.Aliases = append(p.Aliases[:0:0], p.Aliases...)
p.Aliases = append(p.Aliases[:i], p.Aliases[i+1:]...)
removed = gentools.ShallowCopy(&alias2)
break
for _, prefix := range exclusivePrefixes {
if strings.HasPrefix(alias, prefix) {
for i, alias2 := range p.Aliases {
if strings.HasPrefix(alias2, prefix) {
p.Aliases = append(p.Aliases[:0:0], p.Aliases...)
p.Aliases = append(p.Aliases[:i], p.Aliases[i+1:]...)
removed = gentools.ShallowCopy(&alias2)
break
}
}
break
}
}
added = gentools.ShallowCopy(&alias)
p.Aliases = append(p.Aliases, alias)
sort.Strings(p.Aliases)
return
}

4
events/assignment.go

@ -40,6 +40,10 @@ type AssignmentVariables struct {
Map map[string]float64
}
func (e AssignmentVariables) VerboseKey() string {
return "AssignmentVariables"
}
func (e AssignmentVariables) EventDescription() string {
return fmt.Sprintf("AssignmentVariables(id=%s, keys=[%s])",
e.ID, strings.Join(gentools.MapKeys(e.Map), ","),

BIN
frontend/icons.blend

1
frontend/src/lib/client/lucifer.ts

@ -2,7 +2,6 @@ import type UIState from "$lib/models/uistate";
export default async function fetchLucifer<T>(path: string, init?: RequestInit): Promise<T> {
const url = import.meta.env.VITE_LUCIFER4_BACKEND_URL + "/" + path;
console.log(url);
const res = await fetch(url, init);
if (res.status !== 200) {

24
frontend/src/lib/components/DeviceIcon.svelte

@ -11,6 +11,10 @@
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";
import hue_go from "./icons/hue_go.svg?raw";
import square from "./icons/square.svg?raw";
import hexagon from "./icons/hexagon.svg?raw";
import triangle from "./icons/triangle.svg?raw";
export const iconMap = Object.seal({
generic_lamp,
@ -25,6 +29,10 @@
hue_lightbulb_gu10,
hue_adore_tube,
hue_playbar,
square,
hexagon,
triangle,
hue_go,
});
export type IconName = keyof typeof iconMap;
@ -44,16 +52,18 @@
$: voldemort = rgbToHex(darkColor);
let icon: string;
$: icon = iconMap[name];
$: icon = iconMap[name] || iconMap.generic_lamp;
let dataUrl: string;
$: {
const data = encodeURIComponent(
icon
.replaceAll("fill:#ff0000", `fill:${gandalf}`)
.replaceAll("fill:#0000ff", `fill:${voldemort}`)
);
dataUrl = `data:image/svg+xml;url,${data}`
if (icon != null) {
const data = encodeURIComponent(
icon
.replaceAll("fill:#ff0000", `fill:${gandalf}`)
.replaceAll("fill:#0000ff", `fill:${voldemort}`)
);
dataUrl = `data:image/svg+xml;url,${data}`
}
}
</script>

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

@ -11,6 +11,7 @@
import DeviceIcon from "./DeviceIcon.svelte";
export let device: Device;
export let compact: boolean = false;
const {selectedMap, toggleSelection} = getSelectedContext();
@ -41,11 +42,14 @@
const hws = device.hwState;
const sflags = hws.supportFlags;
const hasColor = !!(sflags & SupportFlags.Color);
const hasPower = !!(sflags & SupportFlags.Power)
if (deviceTitle == "") {
deviceTitle = device.hwState.internalName;
}
if (sflags & SupportFlags.Color) {
if (hasColor && (!hasPower || device.desiredState.power)) {
iconColor = device.desiredColorRgb;
} else {
iconColor = null;
@ -68,7 +72,7 @@
barColor = rgb(1.000,0.671,0.355);
}
barFraction = device.desiredState.intensity;
barFraction = device.desiredState?.intensity || 0;
}
if (sflags & SupportFlags.SensorTemperature && !!device.sensors.temperature) {
@ -97,13 +101,13 @@
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="lamp" class:selected={$selectedMap[device.id]} on:click={onSelect}>
<div class="lamp" class:compact class:selected={$selectedMap[device.id]} on:click={onSelect}>
<div class="row">
<div class="row-icon">
{#if iconColor != null}
<DeviceIcon name={device.hwMetadata?.icon||"generic_ball"} brightColor={iconColor} darkColor={darkColor} />
<DeviceIcon name={device.icon||"generic_ball"} brightColor={iconColor} darkColor={darkColor} />
{:else}
<DeviceIcon name={device.hwMetadata?.icon||"generic_ball"} brightColor={darkColor} darkColor={darkColor}>
<DeviceIcon name={device.icon||"generic_ball"} brightColor={darkColor} darkColor={darkColor}>
{#if !!roundboiText}
<div class="roundboi-text">{roundboiText}</div>
{/if}
@ -124,10 +128,10 @@
cursor: pointer
width: 19ch
height: 2em
margin: 0.25em
margin: 0.5ch
background: #18181c
color: #84888f
border-radius: 0.25em
border-radius: 0.5ch
border-top-right-radius: 1em
overflow: hidden
box-shadow: 1px 1px 1px #000
@ -170,4 +174,12 @@
> div.flatboi2
height: 0.2em
&.compact
width: 3ch
border-top-right-radius: 0.25em
div.row
margin-left: -0.1ch
div.title
opacity: 0
</style>

75
frontend/src/lib/components/Toolbar.svelte

@ -1,75 +0,0 @@
<script lang="ts">
import { getSelectedContext } from "$lib/contexts/SelectContext.svelte";
import { getStateContext } from "$lib/contexts/StateContext.svelte";
import type Device from "$lib/models/device";
import { KELVIN_PALETTE, RGB_PALETTE } from "$lib/models/palette";
import ColorPalette from "./ColorPalette.svelte";
const {deviceList} = getStateContext();
const {selectedMap} = getSelectedContext();
let selected: Device[];
let unselected: Device[];
$: selected = $deviceList.filter(d => !!$selectedMap[d.id]);
$: unselected = $deviceList.filter(d => !$selectedMap[d.id]);
let assignment: string;
$: {
assignment = "";
if (selected.length > 0) {
for (const alias of selected[0].aliases) {
if (!selected.find(d => !d.aliases.includes(alias)) && !unselected.find(d => d.aliases.includes(alias))) {
assignment = alias;
break;
}
}
if (assignment === "") {
if (selected.length == 1) {
assignment = selected[0].id;
} else {
let prefix = selected[0].id;
for (const device of selected) {
for (let i = 0; i < prefix.length; i++) {
if (device.id.charAt(i) !== prefix.charAt(i)) {
prefix = prefix.slice(0, i);
break;
}
}
}
if (prefix.length > 0) {
assignment = `${prefix}{${selected.map(d => d.id.slice(prefix.length)).join("|")}}`
}
}
if (assignment === "") {
assignment = `{${selected.map(d => d.id).join("|")}}`
}
}
}
}
</script>
<div class="toolbar">
<ColorPalette palette={RGB_PALETTE} />
</div>
<style lang="sass">
div.toolbar
position: fixed
bottom: 0
width: 100%
height: 2em
padding: 0.35em 1ch
box-sizing: border-box
font-size: 1.5em
background: #18181c
color: #abc
box-shadow: 1px 1px 1px #000
display: flex
flex-direction: row
flex-wrap: wrap
</style>

8
frontend/src/lib/components/icons/hexagon.svg

@ -0,0 +1,8 @@
<?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" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 159.162, 60.200 150.502, 65.200 141.842, 70.200 133.181, 75.200 124.521, 80.200 115.861, 85.200 107.201, 90.200 98.540, 95.200 89.880, 100.200 81.220, 105.200 73.989, 109.374 73.844, 109.473 73.703, 109.598 73.570, 109.746 73.451, 109.912 73.347, 110.090 73.264, 110.277 73.202, 110.465 73.164, 110.650 73.151, 110.826 73.151, 120.826 73.151, 130.826 73.151, 140.826 73.151, 150.826 73.151, 160.826 73.151, 170.826 73.151, 180.826 73.151, 190.826 73.151, 200.826 73.151, 209.174 73.164, 209.350 73.202, 209.535 73.264, 209.723 73.347, 209.910 73.451, 210.088 73.570, 210.254 73.703, 210.402 73.844, 210.527 73.989, 210.626 82.650, 215.626 91.310, 220.626 99.970, 225.626 108.630, 230.626 117.291, 235.626 125.951, 240.626 134.611, 245.626 143.271, 250.626 151.932, 255.626 159.162, 259.800 159.320, 259.877 159.499, 259.937 159.693, 259.977 159.897, 259.998 160.103, 259.998 160.307, 259.977 160.501, 259.937 160.680, 259.877 160.838, 259.800 169.498, 254.800 178.158, 249.800 186.819, 244.800 195.479, 239.800 204.139, 234.800 212.799, 229.800 221.460, 224.800 230.120, 219.800 238.780, 214.800 246.011, 210.626 246.156, 210.527 246.297, 210.402 246.430, 210.254 246.549, 210.088 246.653, 209.910 246.736, 209.723 246.798, 209.535 246.836, 209.350 246.849, 209.174 246.849, 199.174 246.849, 189.174 246.849, 179.174 246.849, 169.174 246.849, 159.174 246.849, 149.174 246.849, 139.174 246.849, 129.174 246.849, 119.174 246.849, 110.826 246.836, 110.650 246.798, 110.465 246.736, 110.277 246.653, 110.090 246.549, 109.912 246.430, 109.746 246.297, 109.598 246.156, 109.473 246.011, 109.374 237.350, 104.374 228.690, 99.374 220.030, 94.374 211.370, 89.374 202.709, 84.374 194.049, 79.374 185.389, 74.374 176.729, 69.374 168.068, 64.374 160.838, 60.200 160.680, 60.123 160.501, 60.063 160.307, 60.023 160.103, 60.002 159.897, 60.002 159.693, 60.023 159.499, 60.063 159.320, 60.123 159.162, 60.200 " />
</g>
</g>
</svg>

10
frontend/src/lib/components/icons/hue_go.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: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 102.711, 217.273 111.522, 222.002 120.333, 226.732 120.539, 226.843 130.105, 229.757 139.671, 232.671 139.883, 232.736 149.834, 233.720 159.786, 234.705 160.000, 234.726 169.951, 233.741 180.096, 232.736 189.661, 229.819 199.419, 226.843 208.228, 222.109 217.036, 217.375 217.227, 217.273 224.941, 210.908 232.654, 204.544 232.837, 204.393 239.160, 196.647 245.484, 188.900 245.647, 188.700 250.341, 179.870 255.035, 171.041 255.166, 170.796 258.054, 161.222 260.943, 151.648 261.027, 151.368 262.002, 141.416 262.977, 131.463 263.007, 131.164 262.032, 121.212 261.057, 111.259 261.027, 110.960 258.139, 101.387 255.734, 93.416 253.254, 97.907 247.342, 103.554 239.211, 109.376 237.998, 110.244 236.800, 110.960 228.090, 115.873 224.443, 117.930 215.490, 122.386 208.089, 126.070 198.961, 130.155 196.707, 131.164 189.437, 134.290 180.121, 137.925 170.806, 141.561 169.492, 142.073 160.064, 145.404 150.635, 148.735 149.474, 149.145 142.438, 151.368 132.871, 154.278 130.319, 155.054 120.651, 157.609 112.834, 159.675 103.062, 161.797 97.397, 163.027 87.516, 164.566 84.360, 165.057 74.381, 165.713 74.018, 165.737 66.624, 165.057 62.386, 163.027 64.733, 170.796 69.431, 179.623 74.129, 188.451 74.262, 188.700 80.589, 196.444 86.917, 204.187 87.086, 204.393 94.802, 210.754 102.519, 217.114 102.711, 217.273 " />
<path style="fill:#ff0000;fill-opacity:0.85" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 242.877, 87.836 233.006, 88.441 223.112, 89.898 220.663, 90.258 214.504, 91.533 205.831, 93.416 196.163, 95.971 188.841, 97.907 179.274, 100.816 170.271, 103.554 160.804, 106.776 151.338, 109.997 150.612, 110.244 148.706, 110.960 139.390, 114.596 130.847, 117.930 121.660, 121.879 112.473, 125.829 111.911, 126.070 102.882, 130.368 101.209, 131.164 94.930, 134.290 86.220, 139.202 81.130, 142.073 72.802, 147.609 70.490, 149.145 67.890, 151.368 64.031, 155.054 61.479, 159.675 62.386, 163.027 66.624, 165.057 74.018, 165.737 83.997, 165.081 84.360, 165.057 94.241, 163.519 97.397, 163.027 107.170, 160.905 112.834, 159.675 122.502, 157.120 130.319, 155.054 139.886, 152.144 142.438, 151.368 149.474, 149.145 158.903, 145.814 168.332, 142.484 169.492, 142.073 178.808, 138.438 188.124, 134.802 189.437, 134.290 196.707, 131.164 205.835, 127.079 208.089, 126.070 217.041, 121.614 224.443, 117.930 233.153, 113.017 236.800, 110.960 237.998, 110.244 246.129, 104.422 247.342, 103.554 253.254, 97.907 255.734, 93.416 255.224, 91.533 254.589, 90.258 250.124, 88.441 242.877, 87.836 " />
<path style="fill:#ff0000;fill-opacity:1.0" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 205.022, 103.230 199.585, 103.563 192.787, 104.564 189.394, 105.266 184.618, 106.303 175.260, 108.777 165.692, 111.686 165.031, 111.887 155.565, 115.109 154.203, 115.572 153.153, 115.967 143.837, 119.602 143.317, 119.806 134.130, 123.755 132.887, 124.289 126.992, 127.095 123.534, 128.816 115.933, 133.104 110.072, 136.999 108.640, 138.223 106.515, 140.253 105.109, 142.799 105.609, 144.645 107.943, 145.763 112.016, 146.137 117.712, 145.763 124.893, 144.645 133.395, 142.799 143.026, 140.253 149.701, 138.223 153.576, 136.999 163.005, 133.668 164.602, 133.104 173.918, 129.468 175.588, 128.816 179.592, 127.095 185.861, 124.289 194.869, 119.806 201.675, 115.967 202.335, 115.572 207.481, 111.887 210.738, 108.777 212.104, 106.303 211.823, 105.266 211.473, 104.564 209.014, 103.563 205.022, 103.230 " />
</g>
</g>
</svg>

8
frontend/src/lib/components/icons/square.svg

@ -0,0 +1,8 @@
<?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" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 60.000, 71.854 60.000, 81.854 60.000, 91.854 60.000, 101.854 60.000, 111.854 60.000, 121.854 60.000, 131.854 60.000, 141.854 60.000, 151.854 60.000, 161.854 60.000, 171.854 60.000, 181.854 60.000, 191.854 60.000, 201.854 60.000, 211.854 60.000, 221.854 60.000, 231.854 60.000, 241.854 60.000, 251.854 60.000, 258.288 60.021, 258.556 60.084, 258.817 60.187, 259.065 60.327, 259.294 60.501, 259.499 60.706, 259.673 60.935, 259.813 61.183, 259.916 61.444, 259.979 61.712, 260.000 71.712, 260.000 81.712, 260.000 91.712, 260.000 101.712, 260.000 111.712, 260.000 121.712, 260.000 131.712, 260.000 141.712, 260.000 151.712, 260.000 161.712, 260.000 171.712, 260.000 181.712, 260.000 191.712, 260.000 201.712, 260.000 211.712, 260.000 221.712, 260.000 231.712, 260.000 241.712, 260.000 248.146, 260.000 250.000, 259.854 251.809, 259.420 253.527, 258.708 255.113, 257.736 256.528, 256.528 257.736, 255.113 258.708, 253.527 259.420, 251.809 259.854, 250.000 260.000, 248.146 260.000, 238.146 260.000, 228.146 260.000, 218.146 260.000, 208.146 260.000, 198.146 260.000, 188.146 260.000, 178.146 260.000, 168.146 260.000, 158.146 260.000, 148.146 260.000, 138.146 260.000, 128.146 260.000, 118.146 260.000, 108.146 260.000, 98.146 260.000, 88.146 260.000, 78.146 260.000, 68.146 260.000, 61.712 259.979, 61.444 259.916, 61.183 259.813, 60.935 259.673, 60.706 259.499, 60.501 259.294, 60.327 259.065, 60.187 258.817, 60.084 258.556, 60.021 258.288, 60.000 248.288, 60.000 238.288, 60.000 228.288, 60.000 218.288, 60.000 208.288, 60.000 198.288, 60.000 188.288, 60.000 178.288, 60.000 168.288, 60.000 158.288, 60.000 148.288, 60.000 138.288, 60.000 128.288, 60.000 118.288, 60.000 108.288, 60.000 98.288, 60.000 88.288, 60.000 78.288, 60.000 71.854, 60.000 70.000, 60.146 68.191, 60.580 66.473, 61.292 64.887, 62.264 63.472, 63.472 62.264, 64.887 61.292, 66.473 60.580, 68.191 60.146, 70.000 60.000, 71.854 " />
</g>
</g>
</svg>

8
frontend/src/lib/components/icons/triangle.svg

@ -0,0 +1,8 @@
<?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" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 159.555, 60.312 154.555, 68.972 149.555, 77.633 144.555, 86.293 139.555, 94.953 134.555, 103.613 129.555, 112.274 124.555, 120.934 119.555, 129.594 114.555, 138.254 109.555, 146.915 104.555, 155.575 99.555, 164.235 94.555, 172.895 89.555, 181.556 84.555, 190.216 79.555, 198.876 74.555, 207.537 69.555, 216.197 64.555, 224.857 59.555, 233.517 54.555, 242.178 49.555, 250.838 44.445, 259.688 44.382, 259.822 44.347, 259.951 44.342, 260.073 44.368, 260.183 44.423, 260.278 44.505, 260.355 44.613, 260.412 44.743, 260.447 44.890, 260.459 54.890, 260.459 64.890, 260.459 74.890, 260.459 84.890, 260.459 94.890, 260.459 104.890, 260.459 114.890, 260.459 124.890, 260.459 134.890, 260.459 144.890, 260.459 154.890, 260.459 164.890, 260.459 174.890, 260.459 184.890, 260.459 194.890, 260.459 204.890, 260.459 214.890, 260.459 224.890, 260.459 234.890, 260.459 244.890, 260.459 254.890, 260.459 264.890, 260.459 275.110, 260.459 275.257, 260.447 275.387, 260.412 275.495, 260.355 275.577, 260.278 275.632, 260.183 275.658, 260.073 275.653, 259.951 275.618, 259.822 275.555, 259.688 270.555, 251.028 265.555, 242.367 260.555, 233.707 255.555, 225.047 250.555, 216.387 245.555, 207.726 240.555, 199.066 235.555, 190.406 230.555, 181.746 225.555, 173.085 220.555, 164.425 215.555, 155.765 210.555, 147.105 205.555, 138.444 200.555, 129.784 195.555, 121.124 190.555, 112.463 185.555, 103.803 180.555, 95.143 175.555, 86.483 170.555, 77.822 165.555, 69.162 160.445, 60.312 160.361, 60.190 160.266, 60.095 160.163, 60.030 160.055, 59.997 159.945, 59.997 159.837, 60.030 159.734, 60.095 159.639, 60.190 159.555, 60.312 " />
</g>
</g>
</svg>

112
frontend/src/lib/contexts/StateContext.svelte

@ -1,5 +1,6 @@
<script lang="ts" context="module">
import { fetchUIState } from "$lib/client/lucifer";
import type { IconName } from "$lib/components/DeviceIcon.svelte";
import type Device from "$lib/models/device";
import type { UIStatePatch } from "$lib/models/uistate";
import type UIState from "$lib/models/uistate";
@ -13,6 +14,7 @@
state: Readable<UIState>
error: Readable<string | null>
deviceList: Readable<Device[]>
roomList: Readable<{name: string, devices: Device[]}[]>
}
export function getStateContext(): StateContextData {
@ -24,10 +26,12 @@
const state = writable<UIState>({
assignments: {},
devices: {},
script: {},
scripts: {},
});
const error = writable<string | null>(null);
let socket: WebSocket | null = null;
const deviceList = derived(state, state => {
if (state == null) {
return [];
@ -35,10 +39,29 @@
return Object.keys(state.devices)
.map(k => state.devices[k])
.sort((a,b) => a.id.localeCompare(b.id));
})
.sort((a,b) => a.name.localeCompare(b.name));
});
const roomList = derived(state, state => {
const roomMap: Record<string, Device[]> = {};
for (const id in state.devices) {
if (!state.devices.hasOwnProperty(id)) {
continue;
}
const roomAlias = state.devices[id].aliases.find(a => a.startsWith("lucifer:room:"));
const roomName = roomAlias?.slice("lucifer:room:".length) || "Unroomed";
if (!roomMap[roomName]) {
roomMap[roomName] = [];
}
roomMap[roomName].push(state.devices[id]);
}
return Object.keys(roomMap)
.map(k => ({ name: k, devices: roomMap[k] }))
.sort((a,b) => a.name.localeCompare(b.name));
});
async function reload() {
error.set(null);
@ -57,9 +80,9 @@
url = import.meta.env.VITE_LUCIFER4_BACKEND_URL.replace("http", "ws") +"/subscribe";
}
const socket = new WebSocket(url);
const currSocket = new WebSocket(url);
socket.onmessage = (msg) => {
currSocket.onmessage = (msg) => {
const patch: UIStatePatch = JSON.parse(msg.data);
state.update(s => {
@ -73,10 +96,47 @@
...s,
devices: {
...s.devices,
[patch.device.id]: {
...s.devices[patch.device.id],
...patch.device,
}
[patch.device.id]: ((patch) => {
if (patch.addAlias) {
const aliases = [...(s.devices[patch.id].aliases || [])];
const exclPrefix = ["lucifer:icon:", "lucifer:group:", "lucifer:name:"].find(p => patch.addAlias?.startsWith(p));
const exclExisting = exclPrefix && aliases.find(a => a.startsWith(exclPrefix));
if (patch.addAlias.startsWith("lucifer:name:")) {
patch.name = patch.addAlias.slice("lucifer:name:".length)
}
if (patch.addAlias.startsWith("lucifer:icon:")) {
patch.icon = patch.addAlias.slice("lucifer:icon:".length) as IconName
}
if (exclExisting) {
return {
...s.devices[patch.id],
aliases: [...aliases.filter(a => a != exclExisting), patch.addAlias].sort(),
name: patch.name || s.devices[patch.id].name,
icon: patch.icon || s.devices[patch.id].icon,
};
} else {
return {
...s.devices[patch.id],
aliases: [...aliases, patch.addAlias].sort(),
name: patch.name || s.devices[patch.id].name,
icon: patch.icon || s.devices[patch.id].icon,
};
}
} else if (patch.removeAlias != null) {
const aliases = [...(s.devices[patch.id].aliases || [])];
return {
...s.devices[patch.id],
aliases: aliases.filter(a => a !== patch.removeAlias),
};
} else {
return {
...s.devices[patch.id],
...patch,
};
}
})(patch.device)
}
}
}
@ -124,14 +184,26 @@
})
}
socket.onerror = err => {
console.warn("Socket failed:", err);
socket.close();
currSocket.onopen = () => {
if (socket !== null) {
socket.close();
}
socket = currSocket;
}
socket.onclose = () => {
currSocket.onerror = err => {
console.warn("Socket failed:", err);
currSocket.close();
setTimeout(() => connectSocket(), 3000);
}
currSocket.onclose = () => {
if (currSocket === socket) {
socket = null;
}
}
}
onMount(() => {
@ -140,6 +212,19 @@
connectSocket();
window.addEventListener("visibilitychange", () => {
if (document.visibilityState == "visible") {
console.log("Reconnecting");
reload();
connectSocket();
} else {
if (socket != null) {
console.log("Disconnecting")
socket.close();
}
}
});
return () => clearInterval(interval);
});
@ -148,6 +233,7 @@
error: { subscribe: error.subscribe },
state: { subscribe: state.subscribe },
deviceList,
roomList,
});
</script>

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

@ -4,6 +4,7 @@ import type { ColorFlags, ColorRGB } from "./color"
export default interface Device {
id: string
name: string
icon: IconName
hwMetadata: HardwareMetadata | null
hwState: HardwareState | null
desiredColorRgb: ColorRGB | null

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

@ -9,7 +9,7 @@ export default interface UIState {
}
export interface UIStatePatch {
device: Partial<Device> & { id: string, delete?: boolean }
device: Partial<Device> & { id: string, delete?: boolean, addAlias?: string, removeAlias?: string }
assignment: Partial<Assignment> & { id: string, delete?: boolean }
script: Partial<Script> & { id: string, delete?: boolean }
}

21
frontend/src/routes/+page.svelte

@ -1,33 +1,20 @@
<script lang="ts">
import DeviceIcon from "$lib/components/DeviceIcon.svelte";
import Lamp, { DARK_COLOR } from "$lib/components/Lamp.svelte";
import Toolbar from "$lib/components/Toolbar.svelte";
import Lamp, { DARK_COLOR } from "$lib/components/Lamp.svelte";
import { getStateContext } from "$lib/contexts/StateContext.svelte";
import { rgb } from "$lib/models/color";
const {deviceList} = getStateContext();
const {deviceList, roomList} = getStateContext();
</script>
<div class="page">
{#each $deviceList as device (device.id) }
<Lamp device={device} />
<Lamp compact={device.name.includes("Square")} device={device} />
{/each}
</div>
<div class="page" style="font-size: 8em">
<DeviceIcon name="generic_ball" brightColor={rgb(0.517,0.537,1.000)} darkColor={DARK_COLOR} />
<DeviceIcon name="generic_boob" brightColor={rgb(0.517,0.537,1.000)} darkColor={DARK_COLOR} />
<DeviceIcon name="generic_lamp" brightColor={rgb(0.667,0.750,1.000)} darkColor={DARK_COLOR} />
<DeviceIcon name="generic_strip" brightColor={rgb(0.667,0.750,1.000)} darkColor={DARK_COLOR} />
<DeviceIcon name="hue_adore_tube" brightColor={rgb(1,1,0)} darkColor={DARK_COLOR} />
<DeviceIcon name="hue_signe" brightColor={rgb(0.300,1.000,0.300)} darkColor={DARK_COLOR} />
<DeviceIcon name="hue_dimmerswitch" brightColor={rgb(0.517,0.537,1.000)} darkColor={DARK_COLOR} />
<DeviceIcon name="hue_playbar" brightColor={rgb(1,0,1)} darkColor={DARK_COLOR} />
<DeviceIcon name="hue_motionsensor" brightColor={rgb(1,1,1)} darkColor={DARK_COLOR} />
<DeviceIcon name="hue_lightbulb_e27" brightColor={rgb(1,1,1)} darkColor={DARK_COLOR} />
<DeviceIcon name="hue_lightbulb_e14" brightColor={rgb(1,1,1)} darkColor={DARK_COLOR} />
<DeviceIcon name="hue_lightbulb_gu10" brightColor={rgb(1,1,1)} darkColor={DARK_COLOR} />
<DeviceIcon name="hue_go" brightColor={rgb(0.517,0.537,1.000)} darkColor={DARK_COLOR} />
</div>
<Toolbar />
<style>
div.page {

96
services/hue/bridge.go

@ -443,71 +443,63 @@ func (b *Bridge) makeCongruentLoop(ctx context.Context) {
continue
}
updated := false
update := ResourceUpdate{}
// Handle power first
if desired.Power != nil && active.Power != nil && *desired.Power != *active.Power {
updates["light/"+*lightID] = ResourceUpdate{
Power: gentools.Ptr(*desired.Power),
TransitionDuration: gentools.Ptr(time.Millisecond * 101),
}
newActiveState := activeStates[id]
newActiveState.Power = gentools.Ptr(*desired.Power)
continue
}
if active.Power != nil && !*active.Power {
// Don't do more with shut-off-light.
continue
update.Power = gentools.Ptr(*desired.Power)
updated = true
}
updated := false
update := ResourceUpdate{}
// Only do the rest if there's power.
if desired.Power == nil || *desired.Power {
if active.Color != nil && desired.Color != nil {
ac := *active.Color
dc := *desired.Color
if active.Color != nil && desired.Color != nil {
ac := *active.Color
dc := *desired.Color
if !dc.IsKelvin() || !colorFlags[id].IsWarmWhite() {
dc, _ = dc.ToXY()
dc.XY = gentools.Ptr(light.Color.Gamut.Conform(*dc.XY))
}
if !dc.IsKelvin() || !colorFlags[id].IsWarmWhite() {
dc, _ = dc.ToXY()
dc.XY = gentools.Ptr(light.Color.Gamut.Conform(*dc.XY))
}
if dc.XY != nil {
if ac.K != nil {
ac.K = gentools.Ptr(1000000 / (1000000 / *ac.K))
}
if dc.XY != nil {
if ac.K != nil {
ac.K = gentools.Ptr(1000000 / (1000000 / *ac.K))
acXY, _ := ac.ToXY()
dist := dc.XY.DistanceTo(*acXY.XY)
if dist > 0.0002 {
update.ColorXY = gentools.Ptr(*dc.XY)
updated = true
}
} else {
dcMirek := 1000000 / *dc.K
if dcMirek < light.ColorTemperature.MirekSchema.MirekMinimum {
dcMirek = light.ColorTemperature.MirekSchema.MirekMinimum
} else if dcMirek > light.ColorTemperature.MirekSchema.MirekMaximum {
dcMirek = light.ColorTemperature.MirekSchema.MirekMaximum
}
acMirek := 0
if ac.K != nil {
acMirek = 1000000 / *ac.K
}
if acMirek != dcMirek {
update.Mirek = &dcMirek
updated = true
}
}
}
acXY, _ := ac.ToXY()
dist := dc.XY.DistanceTo(*acXY.XY)
if dist > 0.0002 {
update.ColorXY = gentools.Ptr(*dc.XY)
updated = true
}
} else {
dcMirek := 1000000 / *dc.K
if dcMirek < light.ColorTemperature.MirekSchema.MirekMinimum {
dcMirek = light.ColorTemperature.MirekSchema.MirekMinimum
} else if dcMirek > light.ColorTemperature.MirekSchema.MirekMaximum {
dcMirek = light.ColorTemperature.MirekSchema.MirekMaximum
}
acMirek := 0
if ac.K != nil {
acMirek = 1000000 / *ac.K
}
if acMirek != dcMirek {
update.Mirek = &dcMirek
if active.Intensity != nil && desired.Intensity != nil {
if math.Abs(*active.Intensity-*desired.Intensity) >= 0.01 {
update.Brightness = gentools.Ptr(*desired.Intensity * 100)
updated = true
}
}
}
if active.Intensity != nil && desired.Intensity != nil {
if math.Abs(*active.Intensity-*desired.Intensity) >= 0.01 {
update.Brightness = gentools.Ptr(*desired.Intensity * 100)
updated = true
}
}
if updated {
update.TransitionDuration = gentools.Ptr(time.Millisecond * 101)
updates["light/"+*lightID] = update
@ -515,7 +507,7 @@ func (b *Bridge) makeCongruentLoop(ctx context.Context) {
}
if len(updates) > 0 {
timeout, cancel := context.WithTimeout(ctx, time.Second)
timeout, cancel := context.WithTimeout(ctx, time.Millisecond*500*time.Duration(len(updates)))
eg, ctx := errgroup.WithContext(timeout)
for key := range updates {

6
services/hue/data.go

@ -210,8 +210,10 @@ func (res *ResourceData) GenerateEvent(hostname string, resources map[string]*Re
hwMeta.Icon = "hue_lightbulb_e14"
case "sultan_bulb":
hwMeta.Icon = "hue_lightbulb_e27"
case "hue_signe":
hwMeta.Icon = "hue_signe"
case "hue_play":
hwMeta.Icon = "hue_playbar"
case "hue_signe", "hue_go":
hwMeta.Icon = res.ProductData.ProductArchetype
case "unknown_archetype":
switch res.ProductData.ProductName {
case "Hue motion sensor":

23
services/nanoleaf/data.go

@ -3,6 +3,7 @@ package nanoleaf
import (
"encoding/binary"
"git.aiterp.net/lucifer3/server/device"
"git.aiterp.net/lucifer3/server/internal/color"
"time"
)
@ -141,13 +142,13 @@ var shapeTypeMap = map[int]string{
var shapeIconMap = map[int]string{
0: "triangle",
1: "rhythm",
2: "Square",
1: "triangle",
2: "square",
3: "square",
4: "square",
7: "hexagon",
8: "triangle",
9: "triangle-small",
9: "triangle",
12: "hexagon",
}
@ -218,14 +219,16 @@ func (p *panel) apply(change device.State, transitionTime time.Time) {
p.Intensity = *change.Intensity
}
if change.Color != nil {
if !p.On {
newColor := [4]byte{0, 0, 0, 0}
if newColor != p.ColorRGBA {
p.update(newColor, transitionTime)
}
}
if change.Color == nil {
change.Color = &color.Color{RGB: &color.RGB{Red: 255, Green: 255, Blue: 255}}
}
if !p.On {
newColor := [4]byte{0, 0, 0, 0}
if newColor != p.ColorRGBA {
p.update(newColor, transitionTime)
}
} else {
rgbColor, ok := change.Color.ToRGB()
if !ok {
newColor := [4]byte{255, 255, 255, 255}

6
services/script/script.go

@ -13,6 +13,7 @@ type Line struct {
If *LineIf `json:"if,omitempty"`
Assign *LineAssign `json:"assign,omitempty"`
Set *LineSet `json:"set,omitempty"`
Select *LineSelect `json:"select,omitempty"`
}
type LineSet struct {
@ -31,3 +32,8 @@ type LineIf struct {
Then []Line `json:"then,omitempty"`
Else []Line `json:"else,omitempty"`
}
type LineSelect struct {
Match string `json:"match"`
Then []Line `json:"then"`
}

62
services/script/service.go

@ -1,6 +1,7 @@
package script
import (
"fmt"
lucifer3 "git.aiterp.net/lucifer3/server"
"git.aiterp.net/lucifer3/server/commands"
"git.aiterp.net/lucifer3/server/device"
@ -37,7 +38,9 @@ func (s *service) HandleEvent(bus *lucifer3.EventBus, event lucifer3.Event) {
return devices[i].ID < devices[j].ID
})
s.runScript(bus, script.Lines, trig.ScriptTarget, map[string]bool{}, devices, variables)
fmt.Println("[TRIGGER] Running", trig.ID, "for", len(devices), "devices:", event.EventDescription())
s.runScript(bus, lines, trig.ScriptTarget, map[string]bool{}, devices, variables)
}
}
}
@ -97,7 +100,7 @@ func (s *service) runScript(bus *lucifer3.EventBus, lines []Line, match string,
continue
}
if dev.Matches(matcher) {
if line.Assign.Match == "*" || dev.Matches(matcher) {
matchedDevices[dev.ID] = true
matched = append(matched, dev.ID)
}
@ -109,30 +112,41 @@ func (s *service) runScript(bus *lucifer3.EventBus, lines []Line, match string,
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,
})
} 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,
})
}
} else if line.Select != nil {
matcher := s.resolver.CompileMatcher(line.Select.Match)
matched := make([]device.Pointer, 0)
for _, dev := range devices {
if dev.Matches(matcher) {
matched = append(matched, dev)
}
}
if len(matched) > 0 {
s.runScript(bus, line.Select.Then, match, matchedDevices, matched, variables)
}
}
}
}

10
services/tradfri/service.go

@ -60,10 +60,12 @@ func (s *service) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command
})
}
case commands.SearchDevices:
bus.RunEvent(events.DeviceFailed{
ID: command.ID,
Error: "Please follow instructions in IKEA app; they will appear here after pairing.",
})
if _, ok := command.Matches("tradfri"); ok {
bus.RunEvent(events.DeviceFailed{
ID: command.ID,
Error: "Please follow instructions in IKEA app; they will appear here after pairing.",
})
}
case commands.SetState:
if _, ok := command.Matches("tradfri"); ok {
bridge, ok := s.findBridge(command.ID)

24
services/uistate/data.go

@ -8,6 +8,7 @@ import (
"git.aiterp.net/lucifer3/server/internal/gentools"
"git.aiterp.net/lucifer3/server/services/script"
"github.com/google/uuid"
"strings"
)
type Data struct {
@ -22,7 +23,6 @@ func (d *Data) WithPatch(patches ...Patch) Data {
if patch.Device != nil {
pd := d.ensureDevice(patch.Device.ID)
gentools.ApplyUpdateSlice(&pd.Aliases, patch.Device.SetAliases)
gentools.ApplyUpdate(&pd.Name, patch.Device.Name)
gentools.ApplyUpdatePtr(&pd.HWState, patch.Device.HWState)
gentools.ApplyUpdatePtr(&pd.HWMetadata, patch.Device.HWMetadata)
@ -32,18 +32,31 @@ func (d *Data) WithPatch(patches ...Patch) Data {
gentools.ApplyUpdatePtr(&pd.DesiredColorRGB, patch.Device.DesiredColorRGB)
if patch.Device.AddAlias != nil {
pd.Aliases = append(pd.Aliases[:0:0], pd.Aliases...)
pd.Aliases = append(pd.Aliases, *patch.Device.AddAlias)
ptr := device.Pointer{ID: "dummy", Aliases: pd.Aliases}
ptr.AddAlias(*patch.Device.AddAlias)
if strings.HasPrefix(*patch.Device.AddAlias, "lucifer:name:") {
pd.Name = (*patch.Device.AddAlias)[len("lucifer:name:"):]
}
if strings.HasPrefix(*patch.Device.AddAlias, "lucifer:icon:") {
pd.Icon = (*patch.Device.AddAlias)[len("lucifer:icon:"):]
}
pd.Aliases = ptr.Aliases
}
if patch.Device.RemoveAlias != nil {
for i, alias := range pd.Aliases {
if alias == *patch.Device.RemoveAlias {
pd.Aliases = append(pd.Aliases[:0:0], pd.Aliases...)
pd.Aliases = append(pd.Aliases[:i], pd.Aliases[i+1:]...)
break
}
}
}
if patch.Device.HWMetadata != nil {
if pd.Icon == "" {
pd.Icon = patch.Device.HWMetadata.Icon
}
}
if patch.Device.ClearAssignment {
pd.Assignment = nil
}
@ -136,6 +149,7 @@ type Device struct {
Aliases []string `json:"aliases"`
Assignment *uuid.UUID `json:"assignment"`
Sensors DeviceSensors `json:"sensors"`
Icon string `json:"icon"`
}
type DeviceSensors struct {

5
services/uistate/patch.go

@ -32,9 +32,9 @@ func (e Patch) EventDescription() string {
case e.Device.HWMetadata != nil:
return fmt.Sprintf("uistate.Patch(device=%s, hwMetadata)", e.Device.ID)
case e.Device.AddAlias != nil:
return fmt.Sprintf("uistate.Patch(device=%s, addAlias=%s)", e.Device.ID, *e.Device.AddAlias)
return fmt.Sprintf("uistate.Patch(device=%s, addAlias)", e.Device.ID)
case e.Device.RemoveAlias != nil:
return fmt.Sprintf("uistate.Patch(device=%s, removeAlias=%s)", e.Device.ID, *e.Device.RemoveAlias)
return fmt.Sprintf("uistate.Patch(device=%s, removeAlias)", e.Device.ID)
case e.Device.ActiveColorRGB != nil:
col := color.Color{RGB: e.Device.ActiveColorRGB}
return fmt.Sprintf("uistate.Patch(device=%s, activeColorRgb=%s)", e.Device.ID, col.String())
@ -59,7 +59,6 @@ type DevicePatch struct {
HWMetadata *events.HardwareMetadata `json:"hwMetadata,omitempty"`
HWState *events.HardwareState `json:"hwState,omitempty"`
DesiredState *device.State `json:"desiredState,omitempty"`
SetAliases []string `json:"setAliases,omitempty"`
AddAlias *string `json:"addAlias,omitempty"`
RemoveAlias *string `json:"removeAlias,omitempty"`
Assignment *uuid.UUID `json:"assignment,omitempty"`

Loading…
Cancel
Save