2æã«ãWebフロントエンド技術で作る体験型コンテンツ勉強会ããããã®ã«åå ããã¦ããã ããDMXã®ä¾¿å©ããçæãã¦ããããã¼ã£ã¨æ¬²ããã£ããDMXコントローラーã¨調光ユニット(ディマー)ããã¤ãã«æã«å ¥ãããã¨ãã§ããã®ã§ãLEDé»çã調å ã§ããElectronã¢ããªãã¤ãã£ã¦ã¿ã¾ããã
ãã¼ãæ§æ
PCã¨DMXã³ã³ããã¼ã©ã¼ã¯USBã§æ¥ç¶ã
DMXã³ã³ããã¼ã©ã¼ã¨ãã£ãã¼ã¯DMXã±ã¼ãã«ã§æ¥ç¶ã
ã¨ãã¨ã¦ãã·ã³ãã«ãªæ§æã§ãã
ã¢ããªæ§æ
ãã¤ãéããNext.jsのElectronテンプレートã使ã£ã¦Electronã¢ããªãä½ãã¾ããã
ãã¾ã ãこの問題ã解決ãã¦ããªãããã
electron-nextã¯自分のリポジトリã®ãã®ã使ã£ã¦ãã¾ãã
Node.jsããã®DMXãåºåããé¨åã¯ãnode-dmxã使ãã¾ããã
node-dmxã¯å
é¨ã§node-serialportã使ã£ã¦ãããããElectronã¨åããã¦ä½¿ãããã«ã¯ãelectron-rebuildã使ã£ã¦ããã¼ã¸ã§ã³ã®æ´åæ§ãã¨ãå¿
è¦ãããã¾ãã
åèURL
追è¨
electron-rebuildãå»æ¢äºå®ã ã£ãã®ã§ã@electron/rebuildãæ¤è¨¼ããä¹ãæãã¾ããã
ã½ã¼ã¹ã³ã¼ãï¼æç²ï¼
package.json
{ "private": true, "main": "main/index.js", "productName": "ElectronDMX", "scripts": { "clean": "rimraf dist main renderer/out renderer/.next", "dev": "npm run build-electron && electron .", "build-renderer": "next build renderer", "build-electron": "tsc -p electron-src", "build": "npm run build-renderer && npm run build-electron", "pack-app": "npm run build && electron-builder --dir", "dist": "npm run build && electron-builder", "type-check": "tsc -p ./renderer/tsconfig.json && tsc -p ./electron-src/tsconfig.json", "postinstall": "electron-rebuild -f -w serialport" }, "dependencies": { "dmx": "^0.2.5", "electron-is-dev": "^1.2.0", "electron-next": "git+https://github.com/kimizuka/electron-next#master", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@electron/rebuild": "^3.6.0", "@types/node": "^14.18.63", "@types/react": "^16.14.52", "@types/react-dom": "^16.9.24", "electron": "^27.1.2", "electron-builder": "^24.9.1", "next": "latest", "rimraf": "^3.0.2", "sass": "^1.77.1", "typescript": "^4.9.5" }, "build": { "asar": true, "files": [ "main", "renderer/out" ] } }
electron-src/index.ts
// Native import { join } from 'path'; import { format } from 'url'; // Packages import { BrowserWindow, app, ipcMain, IpcMainEvent } from 'electron'; import isDev from 'electron-is-dev'; import prepareNext from 'electron-next'; const port = '/dev/tty.usbserial-XXXXXXXX'; // ls -l /dev/tty.usb* ãªã©ã§ã³ã³ããã¼ã©ã®ãã¼ãã調ã¹ã const DMX = require('dmx'); const dmx = new DMX(); const universe = dmx.addUniverse('dmx', 'enttec-usb-dmx-pro', port); // Prepare the renderer once the app is ready app.on('ready', async () => { await prepareNext('./renderer'); const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: join(__dirname, 'preload.js'), }, }); const url = isDev ? 'http://localhost:8000/' : format({ pathname: join(__dirname, '../renderer/out/index.html'), protocol: 'file:', slashes: true, }); mainWindow.loadURL(url); }); // Quit the app once all windows are closed app.on('window-all-closed', app.quit); // listen the channel `message` and resend the received message to the renderer process ipcMain.on('sendValue', (_evt: IpcMainEvent, value: number) => { universe.update({ 2: value }); // ãã£ã³ãã«ãåãããå¿ è¦ãã });
electron-src/preload.ts
import { contextBridge, ipcRenderer } from 'electron'; contextBridge.exposeInMainWorld('electron', { sendValue: (value: number) => ipcRenderer.send('sendValue', value) });
renderer/pages/index.tsx
import style from './index.module.scss'; import { useEffect, useState } from 'react'; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace interface Window { electron: { sendValue: (value: number) => void; }; } } export default function IndexPage() { const [ value, setValue ] = useState(0); useEffect(() => { window.electron.sendValue(value); }, [value]); return ( <main className={ style.wrapper }> <label> <input type="range" min={ 0 } max={ 100 } step={ 1 } value={ value } onChange={ (evt) => setValue(Number(evt.target.value)) } /> <input type="text" readOnly={ true } value={ value } /> </label> </main> ); };
renderer/pages/index.module.scss
.wrapper { display: flex; align-items: center; justify-content: center; position: fixed; inset: 0; label { display: flex; align-items: center; justify-content: center; transform: scale(2); > * { + * { margin-left: 8px; } } [type='range'] { cursor: pointer; } [type='text'] { width: 40px; text-align: center; } } }
ãããã£ã¨æ¸ãã¨ãããªæãã§ãã
ã¢ããªä¸ã®ã¹ã©ã¤ãã¼ã®å¤ãIPCéä¿¡ã§ã¡ã¤ã³ããã»ã¹ã«æ¸¡ãã¦ããã®å¤ãDMXã³ã³ããã¼ã©ã«æ¸¡ãã¦ãã¾ãã
ãã®éãåã®ä½¿ã£ã¦ãããã£ãã¼ã®ã¹ã¿ã¼ããã£ã³ãã«ã2ã ã£ãããã1ãã£ã³ãã«ã«æ¥ç¶ãã¦ããLEDé»çã®èª¿å
ã®ããã«2ãã£ã³ãã«ã«å¤ã渡ãã¦ãã¾ããã
universe.update({ 2: value });
ãã
universe.updateAll(value);
ã«ããã°ããã¹ã¦ã®ãã£ã³ãã«ãä¸æ¬ã§èª¿å
ã§ãã¾ãã
DEMO
ã¢ããªä¸ã®ã¹ã©ã¤ãã¼ã§LEDé»çã調å
ã§ãã¦ãã¾ãã
ä»åã¯ä»¥ä¸ã§ãã