Skip to content

Commit

Permalink
adds wled bundle and wled action mask
Browse files Browse the repository at this point in the history
  • Loading branch information
mvrcPR committed Nov 19, 2024
1 parent 2a5f502 commit 594a94b
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/app/bundles/masks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import customVisualUIMasks from '../masks/custom.masks'
import uiBundleMasks from 'protolib/bundles/ui/masks';
import apiMasks from 'protolib/bundles/apis/masks';
import devicesMasks from 'protolib/bundles/devices/devices/masks';
import wledMasks from '../../protolib/src/bundles/wled/masks';
import devicesUIMasks from 'protolib/bundles/devices/devices/uiMasks';
import baseMasks from 'protolib/bundles/basemasks';
import customEventMasks from 'protolib/bundles/events/masks'
Expand Down Expand Up @@ -41,6 +42,7 @@ export const getFlowsCustomComponents = (path: string, queryParams: {}) => {
...keyMasks
]
if (paths.apis.includes(segment)) return [
...wledMasks,
...customMasks.api,
...flowMasks,
...flowMasks2,
Expand Down
2 changes: 2 additions & 0 deletions packages/protolib/src/bundles/apiContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import playwright from './playwright/context'
import automations from './automations/context'
import network from './network/context'
import deviceContext from './devices/devices/context'
import wledContext from './wled/context'
import stateMachines from './stateMachines/context'
import agents from './agents/agents/context'

Expand All @@ -34,6 +35,7 @@ export const APIContext = {
sendMailWithResend,
executeAutomation,
...deviceContext,
...wledContext,
keys,
chatGPT,
discord,
Expand Down
2 changes: 2 additions & 0 deletions packages/protolib/src/bundles/coreApis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { PagesAPI } from './pages/pagesAPI'
import { KeysAPI } from './keys/keysAPI'
import { APIsAPI } from './apis/api'
import { DevicesAPI } from './devices/devices/devicesAPI'
import { WledAPI } from './wled/api/wledApi'
import { DeviceSdksAPI } from './devices/deviceSdks/deviceSdksAPI'
import { DeviceCoresAPI } from './devices/devicecores/devicecoresAPI'
import { DeviceBoardsAPI } from './devices/deviceBoards/deviceBoardsAPI'
Expand Down Expand Up @@ -33,6 +34,7 @@ export const AdminAPIBundles = (app, context) => {
PagesAPI(app, context)
APIsAPI(app, context)
DevicesAPI(app, context)
WledAPI(app, context)
AgentsAPI(app, context)
DeviceSdksAPI(app, context)
DeviceCoresAPI(app, context)
Expand Down
58 changes: 58 additions & 0 deletions packages/protolib/src/bundles/wled/api/wledApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { API, getLogger } from "protobase";
const logger = getLogger();

export const WledAPI = (app, context) => {
logger.info("wledApi started");

app.get("/api/core/v1/wled/state", async (req, res) => {
const { address } = req.params;
const state = await API.get(`http://${address}/json/state`)
res.send(state);
});

app.post("/api/core/v1/wled/action/:address", async (req, res) => {
let result
const { address } = req.params;
const { payload } = req.body;

const formatedPayload = generateWLEDJson(payload);

const path = `http://${address}/json`
logger.info(`wledApi - Sending action to wled on Address: ${address}`);
logger.info(`wledApi - Payload: ${JSON.stringify(formatedPayload)}`);

try {
const response = await API.post(path, formatedPayload);
logger.info(`wledApi - action sent to ${path}, Response: ${JSON.stringify(response)}`);
if (response.isError) {
throw new Error(response.error);
}
if (!response.error && response.data) {
result = { "response": response.data }
}
} catch (error) {
result = { "error": error }
logger.error('Error sending payload: ' + error);
}

res.send(result);
})


const generateWLEDJson = (data) => {
return {
on: data.on || false,
bri: data.brightness || 128,
transition: data.transition || 0,
seg: [{
id: data.id || 0,
col: [data.color?.replace('#', '') || "ffffff"],
fx: data.effect || 0,
sx: data.speed || 128,
ix: data.intensity || 128,
pal: data.palette || 0
}]
};
}

}
5 changes: 5 additions & 0 deletions packages/protolib/src/bundles/wled/context/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { wledAction } from './wledAction'

export default {
wledAction,
}
15 changes: 15 additions & 0 deletions packages/protolib/src/bundles/wled/context/wledAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { getServiceToken } from "protonode";
import { API } from "protobase";

export const wledAction = async (ipAdress, payload, cb?, errorCb?) => {

const url = `/api/core/v1/wled/action/${ipAdress}?token=${getServiceToken()}`

let result = await API.post(url, { payload })
if (result.isError) {
errorCb && errorCb()
throw result.error
}
if (cb) cb(result.data)
return result.data
}
5 changes: 5 additions & 0 deletions packages/protolib/src/bundles/wled/masks/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import wledActionMask from "./wledActionMask";

export default [
wledActionMask
]
167 changes: 167 additions & 0 deletions packages/protolib/src/bundles/wled/masks/wledActionMask.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { Node, NodeParams, getFieldValue, filterObject, restoreObject, restoreCallback, filterCallback, FallbackPortList } from 'protoflow';
import { useState, useEffect, useRef } from 'react';
import { useColorFromPalette } from 'protoflow/src/diagram/Theme';
import { Play } from '@tamagui/lucide-icons';
import { WledRepository } from '../repositories/wledRepository';
import { CustomFieldType } from 'protoflow/src/fields';


const WledAction = (node: any = {}, nodeData = {}) => {
const ipAdress = getFieldValue('param-1', nodeData);
const [effects, setEffects] = useState<any[]>([]);
const [palettes, setPalettes] = useState<any[]>([]);
const [segments, setSegments] = useState<any[]>([]);
const paramsRef = useRef();

const wledRepository = new WledRepository(ipAdress);

const isArray = (data) => {
return Array.isArray(data);
}

const getEffects = async () => {
const data = await wledRepository.listEffects();
isArray(data) && setEffects(data.map((effect, i) => ({ label: effect, value: i })))
};

const getPalettes = async () => {
const data = await wledRepository.listPalettes();
isArray(data) && setPalettes(data.map((palette, i) => ({ label: palette, value: i })))
};

const getSegments = async () => {
const data = await wledRepository.listSegments();
isArray(data) && setSegments(data.map(segment => segment?.id));
}

const nodeColor = useColorFromPalette(3);

useEffect(() => {
if (ipAdress) {
getSegments();
getEffects();
getPalettes();
}
}, [ipAdress]);

return (
<Node icon={Play} node={node} isPreview={!node.id} title="WLED Action" color={nodeColor} id={node.id} skipCustom={true} >
<div ref={paramsRef}>
<NodeParams
id={node.id}
params={[
{ label: 'IP Address', field: 'param-1', type: 'input' },
{ label: 'On', field: 'mask-on', type: 'boolean', defaultValue: true },
{ label: 'Segment', field: 'mask-segment', type: 'select', data: segments, isDisabled: !segments.length },
{ label: 'Brightness', field: 'mask-brightness', type: 'input', inputType: 'number', defaultValue: 100, description: '0-255' },
{ label: 'Speed', field: 'mask-speed', type: 'input', inputType: 'number', description: '0-255' },
{ label: 'Intensity', field: 'mask-intensity', type: 'input', inputType: 'number', description: '0-255' },
{ label: 'Effect', field: 'mask-effect', type: 'select', data: effects, isDisabled: !effects.length, deleteable: true },
{ label: 'Palette', field: 'mask-palette', type: 'select', data: palettes, isDisabled: !palettes.length, deleteable: true },
{ label: 'Color', field: 'mask-color', type: 'colorPicker', deleteable: true }
]}
/>
</div>
<FallbackPortList
height={'70px'}
startPosX={500}
node={node}
fallbacks={[{
"name": "ondone",
"label": "onDone(data)",
"field": "param-3",
"preText": "async (data) => ",
"postText": "",
"fallbackText": "null"
}, {
"name": "onerror",
"label": "OnError (error)",
"field": "param-4",
"preText": "async (error) => ",
"fallbackText": "null",
"postText": ""
}]}
/>
</Node>
);
};

export default {
id: 'WledAction',
type: 'CallExpression',
check: (node, nodeData) => {
return node.type === 'CallExpression' && nodeData.to === 'context.wledAction';
},
category: 'IoT',
keywords: ['action', 'automation', 'esp32', 'device', 'iot', 'wled'],
getComponent: WledAction,
filterChildren: (node, childScope, edges, nodeData, setNodeData) => {

childScope = filterObject({
skipArrow: false,
port: 'param-2',
keys: {
"mask-on": 'output',
"mask-segment": 'output',
"mask-brightness": 'output',
"mask-speed": 'output',
"mask-intensity": 'output',
"mask-effect": 'output',
"mask-palette": 'output',
"mask-color": 'output',
},
})(node, childScope, edges, nodeData, setNodeData)

childScope = filterCallback("3", "ondone")(node, childScope, edges)
childScope = filterCallback("4", "onerror")(node, childScope, edges)
return childScope;
},
restoreChildren: (node, nodes, originalNodes, edges, originalEdges, nodeData) => {

let resultObj: any = restoreObject({
port: 'param-2',
keys: {
"mask-on": 'output',
"mask-segment": 'output',
"mask-brightness": 'output',
"mask-speed": 'output',
"mask-intensity": 'output',
"mask-effect": 'output',
"mask-palette": 'output',
"mask-color": 'output',
}
})(node, nodes, originalNodes, edges, originalEdges, nodeData);

let result = restoreCallback("3")(node, nodes, originalNodes, edges, originalEdges);
result = restoreCallback("4")(node, result.nodes, originalNodes, result.edges, originalEdges);

const assembledEdges = [...result.edges, ...resultObj.edges]
const assembledNodes = [...result.nodes, ...resultObj.nodes]

const filteredEdges = assembledEdges.filter((edge, index, self) =>
index === self.findIndex(e => e.id === edge.id)
);

const filteredNodes = assembledNodes.filter((node, index, self) =>
index === self.findIndex(n => n.id === node.id)
);

return { ...resultObj, ...result, nodes: filteredNodes, edges: filteredEdges };
},
getInitialData: () => {
return {
to: "context.wledAction",
"param-1": { value: "", kind: "StringLiteral" },
"mask-on": { value: true, kind: "BooleanLiteral" },
"mask-segment": { value: 0, kind: "NumericLiteral" },
"mask-brightness": { value: 100, kind: "NumericLiteral" },
"mask-speed": { value: 200, kind: "NumericLiteral" },
"mask-intensity": { value: 200, kind: "NumericLiteral" },
"mask-effect": { value: 0, kind: "NumericLiteral" },
"mask-palette": { value: 0, kind: "NumericLiteral" },
"param-3": { value: "null", kind: "Identifier" },
"param-4": { value: "null", kind: "Identifier" },
await: true,
};
},
};
27 changes: 27 additions & 0 deletions packages/protolib/src/bundles/wled/repositories/wledRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { API, PendingResult } from "protobase";

export class WledRepository {

constructor(private ip_address: string) {
this.ip_address = ip_address;
}

async listEffects(): Promise<[]> {
const response = await API.get(`http://${this.ip_address}/json/effects`)
const segmentList = response?.data;
return segmentList
}

async listPalettes(): Promise<[]> {
const response = await API.get(`http://${this.ip_address}/json/palettes`)
const paletteList = response?.data;
return paletteList;
}

async listSegments(): Promise<[]> {
const response = await API.get(`http://${this.ip_address}/json/state`)
const segmentList = response?.data?.seg;
return segmentList
}

}

0 comments on commit 594a94b

Please sign in to comment.