Skip to content

Commit

Permalink
carrying through refactors to button
Browse files Browse the repository at this point in the history
  • Loading branch information
zoe-codez committed May 25, 2024
1 parent b6e1a4d commit 0fd0633
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 332 deletions.
34 changes: 16 additions & 18 deletions src/extensions/binary-sensor.extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ import {
export function VirtualBinarySensor({ context, synapse }: TServiceParams) {
const registry = synapse.registry.create<SynapseVirtualBinarySensor>({
context,
details: entity => ({
attributes: entity._rawAttributes,
configuration: entity._rawConfiguration,
state: entity.state,
}),
domain: "binary_sensor",
});

Expand All @@ -31,14 +26,7 @@ export function VirtualBinarySensor({ context, synapse }: TServiceParams) {
const proxy = new Proxy({} as SynapseVirtualBinarySensor, {
// #MARK: get
get(_, property: keyof SynapseVirtualBinarySensor) {
// * state
if (property === "state") {
return loader.state;
}
// * is_on
if (property === "is_on") {
return loader.state === "on";
}
// > common
// * name
if (property === "name") {
return entity.name;
Expand Down Expand Up @@ -67,13 +55,28 @@ export function VirtualBinarySensor({ context, synapse }: TServiceParams) {
if (property === "configuration") {
return loader.configurationProxy();
}
// > domain specific
// * state
if (property === "state") {
return loader.state;
}
// * is_on
if (property === "is_on") {
return loader.state === "on";
}
return undefined;
},

ownKeys: () => [...VIRTUAL_ENTITY_BASE_KEYS, "is_on", "state"],

// #MARK: set
set(_, property: string, value: unknown) {
// * attributes
if (property === "attributes") {
loader.setAttributes(value as ATTRIBUTES);
return true;
}
// > domain specific
// * state
if (property === "state") {
loader.setState(value as STATE);
Expand All @@ -85,11 +88,6 @@ export function VirtualBinarySensor({ context, synapse }: TServiceParams) {
loader.setState(new_state);
return true;
}
// * attributes
if (property === "attributes") {
loader.setAttributes(value as ATTRIBUTES);
return true;
}
return false;
},
});
Expand Down
164 changes: 44 additions & 120 deletions src/extensions/button.extension.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,28 @@
import { is, TBlackHole, TServiceParams } from "@digital-alchemy/core";

import { TRegistry } from "..";
import { TRegistry, VIRTUAL_ENTITY_BASE_KEYS } from "..";
import {
BUTTON_CONFIGURATION_KEYS,
ButtonConfiguration,
HassButtonUpdateEvent,
TButton,
TVirtualButton,
SynapseButtonParams,
SynapseVirtualButton,
} from "../helpers/domains";

export function VirtualButton({
logger,
event,
hass,
context,
synapse,
internal,
}: TServiceParams) {
const registry = synapse.registry.create<TVirtualButton>({
export function VirtualButton({ context, synapse }: TServiceParams) {
const registry = synapse.registry.create<SynapseVirtualButton>({
context,
details: entity => ({
attributes: entity._rawAttributes,
configuration: entity._rawConfiguration,
state: undefined,
}),
// @ts-expect-error it's fine
domain: "button",
});

// #MARK: create
function create<
STATE extends void = void,
ATTRIBUTES extends object = object,
CONFIGURATION extends ButtonConfiguration = ButtonConfiguration,
>(entity: TButton<ATTRIBUTES>) {
const entityOut = new Proxy({} as TVirtualButton<STATE, ATTRIBUTES>, {
return function <ATTRIBUTES extends object = object>(
entity: SynapseButtonParams,
) {
// - Define the proxy
const proxy = new Proxy({} as SynapseVirtualButton, {
// #MARK: get
get(_, property: keyof TVirtualButton<STATE, ATTRIBUTES>) {
// * state
if (property === "state") {
return undefined;
}
get(_, property: keyof SynapseVirtualButton) {
// > common
// * name
if (property === "name") {
return entity.name;
Expand All @@ -53,16 +35,6 @@ export function VirtualButton({
if (property === "onUpdate") {
return loader.onUpdate();
}
// * onPress
if (property === "onPress") {
return function (callback: (remove: () => void) => TBlackHole) {
const remove = () => event.removeListener(EVENT_ID, exec);
const exec = async () =>
await internal.safeExec(async () => callback(remove));
event.on(EVENT_ID, exec);
return { remove };
};
}
// * _rawConfiguration
if (property === "_rawConfiguration") {
return loader.configuration;
Expand All @@ -73,64 +45,27 @@ export function VirtualButton({
}
// * attributes
if (property === "attributes") {
return new Proxy({} as ATTRIBUTES, {
get: <KEY extends Extract<keyof ATTRIBUTES, string>>(
_: ATTRIBUTES,
property: KEY,
) => {
return loader.attributes[property];
},
set: <
KEY extends Extract<keyof ATTRIBUTES, string>,
VALUE extends ATTRIBUTES[KEY],
>(
_: ATTRIBUTES,
property: KEY,
value: VALUE,
) => {
loader.setAttribute(property, value);
return true;
},
});
return loader.attributesProxy();
}
// * configuration
if (property === "configuration") {
return new Proxy({} as CONFIGURATION, {
get: <KEY extends Extract<keyof CONFIGURATION, string>>(
_: CONFIGURATION,
property: KEY,
) => {
return loader.configuration[property];
},
set: <
KEY extends Extract<keyof CONFIGURATION, string>,
VALUE extends CONFIGURATION[KEY],
>(
_: CONFIGURATION,
property: KEY,
value: VALUE,
) => {
loader.setConfiguration(property, value);
return true;
},
});
return loader.configurationProxy();
}
// > domain specific
// * onPress
if (property === "onPress") {
return (callback: (remove: () => void) => TBlackHole) =>
synapse.registry.removableListener(PRESS_EVENT, callback);
}
return undefined;
},
// #MARK: ownKeys
ownKeys: () => {
return [
"attributes",
"configuration",
"_rawAttributes",
"_rawConfiguration",
"name",
"onUpdate",
"onPress",
];
},

ownKeys: () => [...VIRTUAL_ENTITY_BASE_KEYS, "onPress"],

// #MARK: set
set(_, property: string, value: unknown) {
// > common
// * attributes
if (property === "attributes") {
loader.setAttributes(value as ATTRIBUTES);
return true;
Expand All @@ -139,45 +74,34 @@ export function VirtualButton({
},
});

// Validate a good id was passed, and it's the only place in code that's using it
const unique_id = registry.add(entityOut, entity);
const EVENT_ID = `synapse/press/${unique_id}`;
// - Add to registry
const unique_id = registry.add(proxy, entity);

const loader = synapse.storage.wrapper<STATE, ATTRIBUTES, CONFIGURATION>({
// - Initialize value storage
const loader = synapse.storage.wrapper<
never,
ATTRIBUTES,
ButtonConfiguration
>({
load_keys: ["device_class"],
name: entity.name,
registry: registry as TRegistry<unknown>,
unique_id,
value: {
attributes: (entity.defaultAttributes ?? {}) as ATTRIBUTES,
configuration: Object.fromEntries(
BUTTON_CONFIGURATION_KEYS.map(key => [key, entity[key]]),
) as unknown as CONFIGURATION,
state: undefined,
},
});

logger.error({ event }, `listening for event`);

hass.socket.onEvent({
// - Attach bus events
const PRESS_EVENT = synapse.registry.busTransfer({
context,
event: synapse.registry.eventName("press"),
async exec({ data: { unique_id: id } }: HassButtonUpdateEvent) {
if (id !== unique_id) {
return;
}
logger.trace({ context, name: entity.name }, `press`);
event.emit(EVENT_ID);
},
eventName: "press",
unique_id,
});

// - Attach static listener
if (is.function(entity.press)) {
const remove = () => event.removeListener(EVENT_ID, callback);
const callback = async () =>
await internal.safeExec(async () => entity.press(remove));
event.on(EVENT_ID, callback);
synapse.registry.removableListener(PRESS_EVENT, entity.press);
}

return entityOut;
}

return create;
// - Done
return proxy;
};
}
55 changes: 54 additions & 1 deletion src/extensions/registry.extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
InternalError,
is,
SECOND,
TBlackHole,
TContext,
TServiceParams,
} from "@digital-alchemy/core";
Expand Down Expand Up @@ -57,6 +58,7 @@ export function Registry({
config,
internal,
context,
event,
scheduler,
}: TServiceParams) {
const LOADERS = new Map<ALL_DOMAINS, () => object[]>();
Expand Down Expand Up @@ -238,10 +240,61 @@ export function Registry({
domains.set(domain, out as unknown as TDomain);
return out;
}

create.registeredDomains = domains;
return { buildEntityState, create, eventName: name };

return {
buildEntityState,

/**
* Listen for specific socket events, and transfer to internal event bus
*/
busTransfer({ context, eventName, unique_id }: BusTransferOptions) {
const target = `synapse/${eventName}/${unique_id}`;
const source = name(eventName);
hass.socket.onEvent({
context,
event: source,
exec({ data }: BaseEvent) {
if (data.unique_id !== unique_id) {
return;
}
event.emit(target, data);
},
});
logger.debug({ source, target }, `setting up bus transfer`);
return target;
},
create,
eventName: name,
/**
* Generate an event listener that is easy to remove by developer
*/
removableListener<DATA extends object>(
eventName: string,
callback: (data: DATA, remove: () => void) => TBlackHole,
) {
const remove = () => event.removeListener(eventName, exec);
const exec = async (data: DATA) =>
await internal.safeExec(async () => await callback(data, remove));
event.on(eventName, exec);
return { remove };
},
};
}

type BusTransferOptions = {
context: TContext;
eventName: string;
unique_id: TSynapseId;
};

type BaseEvent = {
data: {
unique_id: TSynapseId;
};
};

export type TRegistry<DATA extends unknown = unknown> = {
add(data: DATA, entity: { unique_id?: string }): TSynapseId;
byId(unique_id: TSynapseId): DATA;
Expand Down
Loading

0 comments on commit 0fd0633

Please sign in to comment.