-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: new/better mapping composeable
- Loading branch information
Showing
8 changed files
with
515 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
// Use IntelliSense to learn about possible attributes. | ||
// Hover to view descriptions of existing attributes. | ||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"request": "launch", | ||
"name": "Launch Program", | ||
"type": "node", | ||
"program": "${workspaceFolder}/test-modules/script.testlab.ts", | ||
"cwd": "${workspaceFolder}", | ||
"runtimeExecutable": "/opt/homebrew/bin/deno", | ||
"runtimeArgs": [ | ||
"run", | ||
"--unstable", | ||
"--inspect-wait", | ||
"--allow-all" | ||
], | ||
"attachSimplePort": 9229 | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
import { UseNewLightEntity } from "./useNewLight.ts"; | ||
import { UseNewBooleanEntity } from "./useNewBoolean.ts"; | ||
import { | ||
watch, | ||
unref, | ||
parseDuration as parse, | ||
computed, | ||
useNow, | ||
subMilliseconds, | ||
} from "../dep.ts"; | ||
import type { MaybeRef } from "../lib/types.ts"; | ||
import type { HassEntity, Ref } from "../dep.ts"; | ||
|
||
export function useLightMapping({ | ||
entity, | ||
expectedValue, | ||
expectedBrightness, | ||
isDisabled, | ||
isDisabledBrightness, | ||
debug, | ||
autoEnableTime, | ||
autoEnableTimeBrightness, | ||
}: UseLightMappingOptions) { | ||
watch(entity, (newEntityState, oldEntityState) => { | ||
if (newEntityState.value !== unref(expectedValue) && isDisabled) { | ||
if (debug) { | ||
console.log(`automation_toggle(${newEntityState.entity_id}): value`); | ||
} | ||
isDisabled.value = true; | ||
} | ||
|
||
if ( | ||
expectedBrightness && | ||
newEntityState.value && | ||
isDisabledBrightness && | ||
unref(expectedBrightness) !== newEntityState.brightness && | ||
/** Skip initial value, because it might be different. Let it sync first */ | ||
oldEntityState.value | ||
) { | ||
if (debug) { | ||
console.log( | ||
`automation_toggle(${newEntityState.entity_id}): brightness` | ||
); | ||
} | ||
isDisabledBrightness.value = true; | ||
} | ||
}); | ||
|
||
const autoEnableTimeParsed = parseAutoEnableTimeFactory(autoEnableTime); | ||
|
||
const autoEnableTimeBrightnessParsed = parseAutoEnableTimeFactory( | ||
autoEnableTimeBrightness | ||
); | ||
|
||
const now = useNow({ interval: 1000 }); | ||
|
||
const shouldReEnableSince = shouldReEnableSinceFactory( | ||
autoEnableTimeParsed, | ||
now, | ||
isDisabled | ||
); | ||
|
||
const shouldReEnableSinceBrightness = shouldReEnableSinceFactory( | ||
autoEnableTimeBrightnessParsed, | ||
now, | ||
isDisabledBrightness | ||
); | ||
|
||
watch( | ||
[shouldReEnableSince, shouldReEnableSinceBrightness], | ||
([newShouldReEnableSince, newShouldReEnableSinceBrightness]) => { | ||
if (newShouldReEnableSince >= 0 && isDisabled) { | ||
if (debug) { | ||
console.log( | ||
`isDisabled(${entity.entity_id}): reset time=${newShouldReEnableSince}` | ||
); | ||
} | ||
isDisabled.value = false; | ||
} | ||
if (newShouldReEnableSinceBrightness >= 0 && isDisabledBrightness) { | ||
if (debug) { | ||
console.log( | ||
`isDisabledBrightness(${entity.entity_id}): reset time=${newShouldReEnableSinceBrightness}` | ||
); | ||
} | ||
isDisabledBrightness.value = false; | ||
} | ||
}, | ||
{ immediate: true } | ||
); | ||
|
||
const valueTimeSinceUnsync = computed(() => { | ||
if (isDisabled?.value) { | ||
return -1; | ||
} | ||
if (entity.value !== unref(expectedValue)) { | ||
return now.value.getTime() - entity.lastChanged.getTime(); | ||
} | ||
return -1; | ||
}); | ||
|
||
const brightnessTimeSinceUnsync = computed(() => { | ||
if (isDisabledBrightness?.value) { | ||
return -1; | ||
} | ||
if ( | ||
entity.value && | ||
expectedBrightness && | ||
entity.brightness !== unref(expectedBrightness) | ||
) { | ||
return now.value.getTime() - entity.lastChanged.getTime(); | ||
} | ||
return -1; | ||
}); | ||
|
||
watch( | ||
[valueTimeSinceUnsync, brightnessTimeSinceUnsync], | ||
([newValueTime, newBrightnessTime]) => { | ||
if (newValueTime >= 0) { | ||
if (debug) { | ||
console.log( | ||
`out_of_sync(${ | ||
entity.entity_id | ||
}): value time=${newValueTime} expected=${unref( | ||
expectedValue | ||
)} current=${entity.value}` | ||
); | ||
} | ||
entity.value = unref(expectedValue); | ||
} | ||
|
||
const expectedBrightnessValue = unref(expectedBrightness); | ||
|
||
if (newBrightnessTime >= 0 && expectedBrightnessValue) { | ||
if (debug) { | ||
console.log( | ||
`out_of_sync(${entity.entity_id}): brightness time=${newBrightnessTime} expected=${expectedBrightnessValue} current=${entity.brightness}` | ||
); | ||
} | ||
entity.brightness = expectedBrightnessValue; | ||
} | ||
} | ||
); | ||
} | ||
|
||
export type UseLightMappingOptions = { | ||
entity: UseNewLightEntity; | ||
|
||
expectedValue: MaybeRef<boolean>; | ||
expectedBrightness?: MaybeRef<number>; | ||
|
||
isDisabled?: UseNewBooleanEntity; | ||
isDisabledBrightness?: UseNewBooleanEntity; | ||
|
||
autoEnableTime?: MaybeRef<number | HassEntity | string>; | ||
autoEnableTimeBrightness?: MaybeRef<number | HassEntity | string>; | ||
|
||
debug?: boolean; | ||
}; | ||
|
||
export function parseAutoEnableTimeFactory( | ||
input?: MaybeRef<number | HassEntity | string> | ||
) { | ||
return computed(() => { | ||
if (!input) { | ||
return 15 * 60 * 1000; | ||
} | ||
let value = unref(input); | ||
if (typeof value === "number") { | ||
return value; | ||
} | ||
if (typeof value === "object") { | ||
value = value.state; | ||
} | ||
console.log({ value, input }); | ||
return parse(value, "ms"); | ||
}); | ||
} | ||
|
||
/** | ||
* Calculate time since `entity` requires a reset | ||
* @param resetTimeSource Time in ms after which the entity should reset | ||
* @param nowSource useNow() source | ||
* @param entity Entity to reset | ||
* @returns Time since the entity requires a reset | ||
*/ | ||
export function shouldReEnableSinceFactory( | ||
resetTimeSource: Ref<number>, | ||
nowSource: Ref<Date>, | ||
entity?: UseNewBooleanEntity | ||
) { | ||
return computed(() => { | ||
if (!entity || !entity.value) { | ||
return -1; | ||
} | ||
|
||
const now = unref(nowSource); | ||
|
||
const resetTime = unref(resetTimeSource); | ||
|
||
const lastChanged = entity.lastChanged; | ||
|
||
const diffDate = subMilliseconds(now, resetTime); | ||
|
||
if (lastChanged < diffDate) { | ||
return diffDate.getTime() - lastChanged.getTime(); | ||
} | ||
|
||
return -1; | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import type { FullfilledUseState } from "./useState.ts"; | ||
import { | ||
useDebounceFn, | ||
ref, | ||
watch, | ||
watchPausable, | ||
extendRef, | ||
computed, | ||
} from "../dep.ts"; | ||
import type { MessageBase, HassEntity, UnwrapNestedRefs } from "../dep.ts"; | ||
import { connection } from "../hass/connection.ts"; | ||
import { stringBoolToBool } from "../lib/util.ts"; | ||
|
||
export function useNewBoolean(state: FullfilledUseState, debug = false) { | ||
const skipContexts: string[] = []; | ||
|
||
const updateHASSState = useDebounceFn(async (newState: boolean) => { | ||
const entity = state.value; | ||
|
||
const payload = { | ||
type: "call_service", | ||
domain: entity.entity_id.split(".").at(0), | ||
service: `turn_${newState ? "on" : "off"}`, | ||
target: { | ||
entity_id: entity.entity_id, | ||
}, | ||
} satisfies MessageBase; | ||
|
||
if (debug) { | ||
console.log(`call_service(${entity.entity_id}): `, payload); | ||
} | ||
|
||
const result: { context: HassEntity["context"] } = | ||
await connection.sendMessagePromise(payload); | ||
|
||
if (result?.context?.id) { | ||
skipContexts.push(result.context.id); | ||
} | ||
}, 50); | ||
|
||
const localValue = ref(stringBoolToBool(state.value.state)); | ||
|
||
const lastChanged = ref(state.value.last_changed); | ||
|
||
console.log("init", { | ||
value: localValue.value, | ||
lastChanged: lastChanged.value, | ||
entity: state.value.entity_id, | ||
}); | ||
|
||
watch(localValue, (newLocalValue) => { | ||
console.log({ newLocalValue, entity: state.value.entity_id }); | ||
}); | ||
|
||
// Local state changes | ||
const { pause, resume } = watchPausable( | ||
localValue, | ||
(newLocalValue: typeof localValue.value) => { | ||
updateHASSState(newLocalValue); | ||
} | ||
); | ||
|
||
resume(); | ||
|
||
// Incoming state changes from hass | ||
watch( | ||
() => state.value, | ||
(newEntityState) => { | ||
const contextIndex = skipContexts.findIndex( | ||
(value) => value === newEntityState.context.id | ||
); | ||
|
||
if (contextIndex > -1) { | ||
skipContexts.splice(contextIndex, 1); | ||
return; | ||
} | ||
pause(); | ||
localValue.value = stringBoolToBool(newEntityState.state); | ||
lastChanged.value = newEntityState.last_changed; | ||
resume(); | ||
} | ||
); | ||
|
||
const extendObject = { | ||
lastChanged: computed(() => new Date(lastChanged.value)), | ||
getOriginal() { | ||
return localValue; | ||
}, | ||
}; | ||
|
||
return extendRef(localValue, extendObject) as typeof localValue & | ||
UnwrapNestedRefs<typeof extendObject>; | ||
} | ||
|
||
export type UseNewBooleanEntity = ReturnType<typeof useNewBoolean>; |
Oops, something went wrong.