From 594a94b10d97734dec61e7bd915157c6b7febcaa Mon Sep 17 00:00:00 2001 From: mvrcPR Date: Tue, 19 Nov 2024 17:05:23 +0100 Subject: [PATCH] adds wled bundle and wled action mask --- packages/app/bundles/masks.ts | 2 + packages/protolib/src/bundles/apiContext.ts | 2 + packages/protolib/src/bundles/coreApis.ts | 2 + .../protolib/src/bundles/wled/api/wledApi.ts | 58 ++++++ .../src/bundles/wled/context/index.ts | 5 + .../src/bundles/wled/context/wledAction.ts | 15 ++ .../protolib/src/bundles/wled/masks/index.tsx | 5 + .../src/bundles/wled/masks/wledActionMask.tsx | 167 ++++++++++++++++++ .../wled/repositories/wledRepository.ts | 27 +++ 9 files changed, 283 insertions(+) create mode 100644 packages/protolib/src/bundles/wled/api/wledApi.ts create mode 100644 packages/protolib/src/bundles/wled/context/index.ts create mode 100644 packages/protolib/src/bundles/wled/context/wledAction.ts create mode 100644 packages/protolib/src/bundles/wled/masks/index.tsx create mode 100644 packages/protolib/src/bundles/wled/masks/wledActionMask.tsx create mode 100644 packages/protolib/src/bundles/wled/repositories/wledRepository.ts diff --git a/packages/app/bundles/masks.ts b/packages/app/bundles/masks.ts index 4da6f91b1..2f4d2c17b 100644 --- a/packages/app/bundles/masks.ts +++ b/packages/app/bundles/masks.ts @@ -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' @@ -41,6 +42,7 @@ export const getFlowsCustomComponents = (path: string, queryParams: {}) => { ...keyMasks ] if (paths.apis.includes(segment)) return [ + ...wledMasks, ...customMasks.api, ...flowMasks, ...flowMasks2, diff --git a/packages/protolib/src/bundles/apiContext.ts b/packages/protolib/src/bundles/apiContext.ts index 71c0816a2..16823051d 100644 --- a/packages/protolib/src/bundles/apiContext.ts +++ b/packages/protolib/src/bundles/apiContext.ts @@ -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' @@ -34,6 +35,7 @@ export const APIContext = { sendMailWithResend, executeAutomation, ...deviceContext, + ...wledContext, keys, chatGPT, discord, diff --git a/packages/protolib/src/bundles/coreApis.ts b/packages/protolib/src/bundles/coreApis.ts index 53a745bcb..7680a6043 100644 --- a/packages/protolib/src/bundles/coreApis.ts +++ b/packages/protolib/src/bundles/coreApis.ts @@ -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' @@ -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) diff --git a/packages/protolib/src/bundles/wled/api/wledApi.ts b/packages/protolib/src/bundles/wled/api/wledApi.ts new file mode 100644 index 000000000..da25d1ee6 --- /dev/null +++ b/packages/protolib/src/bundles/wled/api/wledApi.ts @@ -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 + }] + }; + } + +} diff --git a/packages/protolib/src/bundles/wled/context/index.ts b/packages/protolib/src/bundles/wled/context/index.ts new file mode 100644 index 000000000..eb6b86f51 --- /dev/null +++ b/packages/protolib/src/bundles/wled/context/index.ts @@ -0,0 +1,5 @@ +import { wledAction } from './wledAction' + +export default { + wledAction, +} \ No newline at end of file diff --git a/packages/protolib/src/bundles/wled/context/wledAction.ts b/packages/protolib/src/bundles/wled/context/wledAction.ts new file mode 100644 index 000000000..1ef50db85 --- /dev/null +++ b/packages/protolib/src/bundles/wled/context/wledAction.ts @@ -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 +} \ No newline at end of file diff --git a/packages/protolib/src/bundles/wled/masks/index.tsx b/packages/protolib/src/bundles/wled/masks/index.tsx new file mode 100644 index 000000000..e92bc939f --- /dev/null +++ b/packages/protolib/src/bundles/wled/masks/index.tsx @@ -0,0 +1,5 @@ +import wledActionMask from "./wledActionMask"; + +export default [ + wledActionMask +] \ No newline at end of file diff --git a/packages/protolib/src/bundles/wled/masks/wledActionMask.tsx b/packages/protolib/src/bundles/wled/masks/wledActionMask.tsx new file mode 100644 index 000000000..0504ed126 --- /dev/null +++ b/packages/protolib/src/bundles/wled/masks/wledActionMask.tsx @@ -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([]); + const [palettes, setPalettes] = useState([]); + const [segments, setSegments] = useState([]); + 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 ( + +
+ +
+ ", + "postText": "", + "fallbackText": "null" + }, { + "name": "onerror", + "label": "OnError (error)", + "field": "param-4", + "preText": "async (error) => ", + "fallbackText": "null", + "postText": "" + }]} + /> +
+ ); +}; + +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, + }; + }, +}; \ No newline at end of file diff --git a/packages/protolib/src/bundles/wled/repositories/wledRepository.ts b/packages/protolib/src/bundles/wled/repositories/wledRepository.ts new file mode 100644 index 000000000..e09f96543 --- /dev/null +++ b/packages/protolib/src/bundles/wled/repositories/wledRepository.ts @@ -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 + } + +} \ No newline at end of file