Nゲージの部屋にようこそ

2024/04/11 Nゲージ用コントローラ第2弾を作成したので公開します。

概要


できること


使用する機材


使用するもの


拡張予定


回路図


実装写真

コンパイル済みをインストールして動かす


開発(以下は自分で開発してみたい方だけ読んでください)


フォルダー構成


package.json参考(バージョンはダウンロードの時期によって変わります)
21行目は追加してください

                {
                    "name": "react_n-gauge_01",
                    "version": "0.1.0",
                    "private": true,
                    "dependencies": {
                      "@emotion/react": "^11.11.4",
                      "@emotion/styled": "^11.11.5",
                      "@mui/icons-material": "^5.15.15",
                      "@mui/lab": "^5.0.0-alpha.170",
                      "@mui/material": "^5.15.15",
                      "@testing-library/jest-dom": "^5.17.0",
                      "@testing-library/react": "^13.4.0",
                      "@testing-library/user-event": "^13.5.0",
                      "axios": "^1.6.8",
                      "react": "^18.2.0",
                      "react-dom": "^18.2.0",
                      "react-scripts": "5.0.1",
                      "web-vitals": "^2.1.4"
                    },
                    "homepage": "./",
                    "scripts": {
                      "start": "react-scripts start",
                      "build": "react-scripts build",
                      "test": "react-scripts test",
                      "eject": "react-scripts eject"
                    },
                    "eslintConfig": {
                      "extends": [
                        "react-app",
                        "react-app/jest"
                      ]
                    },
                    "browserslist": {
                      "production": [
                        ">0.2%",
                        "not dead",
                        "not op_mini all"
                      ],
                      "development": [
                        "last 1 chrome version",
                        "last 1 firefox version",
                        "last 1 safari version"
                      ]
                    }
                  }
            
以下ソースプログラム
Raspberry Pi で実行する WEBブラウザーと通信をして、受信したコマンドでRaspberry PiのD/O出力、PWM出力をする。

        # ************************************************
        #  サーバー
        #  filename : NCServer.py
        #  create : 2024/04/11 Kazuma.Sasaki
        # 起動方法 python NCServer.py
        # ************************************************
        import os
        import sys
        from urllib.parse import urlparse
        from http.server import SimpleHTTPRequestHandler
        from http.server import BaseHTTPRequestHandler
        from http.server import CGIHTTPRequestHandler
        from http.server import HTTPServer
        import json
        import struct
        import pigpio #pigpioライブラリをインポートする
        import urllib
        
        power = 0
        freq = 1000
        pi = pigpio.pi()
        pwmI_pin = 12 #PWM出力ピンを指定
        pwmII_pin = 13 #PWM出力ピンを指定
        
        UPI_pin = 20
        DOWNI_pin = 21
        
        UPII_pin = 23
        DOWNII_pin = 24
        
        # pwm_pin = PWMLED(13)
        # pwm_pin.frequency=freq
        # pwm_pin.value=0
        # UP_pin = LED(20)
        # DOWN_pin = LED(21)
        
        pi.set_mode(UPI_pin, pigpio.OUTPUT) #UP出力ピンを指定
        pi.set_mode(DOWNI_pin, pigpio.OUTPUT) #DOWN出力ピンを指定
        pi.set_mode(UPII_pin, pigpio.OUTPUT) #UP出力ピンを指定
        pi.set_mode(DOWNII_pin, pigpio.OUTPUT) #DOWN出力ピンを指定
        freq = int(freq) #PWM周波数をHzで指定
        
        class Handler(CGIHTTPRequestHandler, SimpleHTTPRequestHandler):
            cgi_directories = ["/cgi-bin"]
            def do_POST(self):
                parsed_path = urlparse(self.path)
                content_len = int(self.headers.get('content-length'))
                requestBody = self.rfile.read(content_len).decode('UTF-8')
                params = urllib.parse.parse_qs(requestBody)
                # print(self.path + " " +  requestBody)
                data={}
                statusText = 'NG'
                if "/power1" == self.path :
                    power = float(params["power"][0])
                    freq = float(params["freq"][0])
        
                    duty = int(power)
                    # pwm_pin.frequency=int(freq)
                    # pwm_pin.value=duty/100
                    # pwm_pin.value = duty / 100
        
                    cnv_dutycycle = int((duty * 1000000 / 100))
                    pi.hardware_PWM(pwmI_pin, int(freq), cnv_dutycycle)
                    
                    data={"power":power, "freq":freq}
                    statusText = 'OK'
                elif "/power2" == self.path :
                    power = float(params["power"][0])
                    freq = float(params["freq"][0])
        
                    duty = int(power)
        
                    cnv_dutycycle = int((duty * 1000000 / 100))
                    pi.hardware_PWM(pwmII_pin, int(freq), cnv_dutycycle)
                    
                    data={"power":power, "freq":freq}
                    statusText = 'OK'
        
                elif "/dir1" == self.path :
                    dir = params["dir"][0]
        
                    if(dir == 'OFF'):   # OFF
                        pi.write(UPI_pin, 0)
                        pi.write(DOWNI_pin, 0)
                    elif(dir == 'UP'):  # UP
                        pi.write(DOWNI_pin, 0)
                        pi.write(UPI_pin, 1)
                    elif(dir == 'DOWN'):  # DOWN
                        pi.write(UPI_pin, 0)
                        pi.write(DOWNI_pin, 1)
                    else:           # ERROR
                        pi.write(UPI_pin, 1)
                        pi.write(DOWNI_pin, 1)
                    
                    data={"dir":dir}
                    statusText = 'OK'
                elif "/dir2" == self.path :
                    dir = params["dir"][0]
        
                    if(dir == 'OFF'):   # OFF
                        pi.write(UPII_pin, 0)
                        pi.write(DOWNII_pin, 0)
                    elif(dir == 'UP'):  # UP
                        pi.write(DOWNII_pin, 0)
                        pi.write(UPII_pin, 1)
                    elif(dir == 'DOWN'):  # DOWN
                        pi.write(UPII_pin, 0)
                        pi.write(DOWNII_pin, 1)
                    else:           # ERROR
                        pi.write(UPII_pin, 1)
                        pi.write(DOWNII_pin, 1)
                    
                    data={"dir":dir}
                    statusText = 'OK'
                else:
                    data={}
                    statusText = 'NG'
                    
                self.send_response(200)
                self.send_header('Content-type', 'application/json')
                self.end_headers()
                result = {'data':data, 'statusText':statusText, "urlparse":parsed_path}
                jsonStr = json.dumps(result)
                self.wfile.write(jsonStr.encode())
        
        host = '0.0.0.0'
        try:
            port = int(sys.argv[1])
        except IndexError:
            port = 8000
        
        httpd = HTTPServer((host, port), Handler)
        
        print('Serving HTTP on %s port %d ...' % (host, port))
        httpd.serve_forever()
Reactのソース、自前でソースを変更するには参考にしてください。 コンパイルしたソースを実行する時は必要ありません。

        /**********************************************************************
        *  スタートアップ
        *  filename : App.js
        *  create : 2024/04/11 Kazuma.Sasaki
        **********************************************************************/
       import './App.css';
       import { AppBar, IconButton, Toolbar, Typography, Box, Tab } from "@mui/material";
       import { TabContext, TabPanel, TabList } from '@mui/lab';
       import { Fragment, useEffect, useRef, useState } from "react";
       import MenuIcon from '@mui/icons-material/Menu';
       import { drawLed, Line, sendDir, update } from './Line';
       import { FREQ_MIN, FREQ_START, POWER_MAX, POWER_MIN } from './Define';
       
       export default function App() {
         const lineParam = {
           datetimeString:"",
           datetime:Date.now(),
           script:'UP,W10,DOWN,W10,OFF,W10,F1,W10,F2,W10,F3,W10,F4,W10,E',
           scriptArray:null,
           scriptIndex:0,
           elapsedTime:0,
           scriptMode:false,
           dir:'OFF',
           powerRange:[POWER_MIN, POWER_MAX],
           power:POWER_MIN,
           freq:FREQ_START,
           dirSending:false,
           selectedValue:'H',
         }
       
         // const lastInput = {
         //   dir:'OFF',
         //   selectedValue:'H',
         // }
       
         const lineName = ["第一本線", "第二本線"]
         const paramSnackbar = {
           openSnackbar:false,
           snackbarMessage:""
         }
         
         const paramOutDir = {
           sending:false,
           outDir:'OFF',
           color:'900',
         }
       
         const paramOutPower = {
           sending:false,
           outPower:POWER_MIN,
           outFreq:FREQ_MIN,
           color:'900',
         }
       
         const dirUrl1='/dir1'
         const powerUrl1='/power1'
         const dirUrl2='/dir2'
         const powerUrl2='/power2'
         const refWorker = useRef();
         const useLineParam1 = useState(lineParam);
         const useLineParam2 = useState(lineParam);
         // const useLastInput1 = useState(lastInput);
         // const useLastInput2 = useState(lastInput);
         const useOutPower1 = useState(paramOutPower);
         const useOutPower2 = useState(paramOutPower);
         const useOutDir1 = useState(paramOutDir);
         const useOutDir2 = useState(paramOutDir);
         const useCount1 = useState(0);
         const useCount2 = useState(0);
         const useSnackbar1 = useState(paramSnackbar);
         const useSnackbar2 = useState(paramSnackbar);
         const useContext1 = useState(null)
         const useContext2 = useState(null)
       
         const [tabValue, setTabValue] = useState('1');
         const [count, setCount] = useState(0);
       
         
         
         useEffect(() => {
           const newlineParam1 = update(useLineParam1, useOutPower1, useOutDir1, useSnackbar1, useContext1, powerUrl1, dirUrl1)
           useLineParam1[1]({ ...newlineParam1 })
       
           const newlineParam2 = update(useLineParam2, useOutPower2, useOutDir2, useSnackbar2, useContext2, powerUrl2, dirUrl2)
           useLineParam2[1]({ ...newlineParam2 })
       
           // const newlineParam2 = update(lineParam2[0], outPowerParam2[0], outDirParam2[0], null, null)
           // lineParam2[1]({ ...newlineParam2 })
       
         }, [count])
       
       
         // useEffect(() => {
         //   const interval = setInterval(() => {
         //     setCount(prevCount => prevCount + 1)
         //   }, 100);
         //   return () => clearInterval(interval)
         // }, [])
       
         const handleWorkerMessage = (e) => {
           // const {timerId} = e.data;
           // console.log(`Got message from worker: ${timerId}`);
           // const newlineParam1 = update(lineParam1[0], outPowerParam1[0], outDirParam1[0], null, null)
           // lineParam1[1]({ ...newlineParam1 })
       
           // const newlineParam2 = update(lineParam2[0], outPowerParam2[0], outDirParam2[0], null, null)
           // lineParam2[1]({ ...newlineParam2 })
             setCount(prevCount => prevCount + 1)
       };
       
       
       
         useEffect(() => {
           // console.log('did mount');
           if (window.Worker && refWorker.current === undefined) {
             // console.log('Generate worker and set message listener');
       
             refWorker.current = new Worker(new URL('timer.worker.js', import.meta.url));
             // console.log('Worker : ', refWorker.current);
             refWorker.current.addEventListener('message', handleWorkerMessage);
             refWorker.current.postMessage({interval:100})
             // console.log('Worker postMessage');
           }
       
           return () => {
             if (window.Worker && refWorker.current) {
               // console.log('terminate worker');
       
               refWorker.current.postMessage(0)
               refWorker.current.removeEventListener('message', handleWorkerMessage);
               refWorker.current.terminate();
               refWorker.current = undefined;
             }
           };
         }, []);
       
         useEffect(() => {
           sendDir(useLineParam1, useOutDir1, useSnackbar1, useContext1, dirUrl1);
           sendDir(useLineParam2, useOutDir2, useSnackbar2, useContext2, dirUrl2);
         }, []);
       
         return (
           <Fragment>
             <AppBar position="static">
               <Toolbar>
                 <IconButton
                   size="large"
                   edge="start"
                   color="inherit"
                   aria-label="menu"
                   sx={{ mr: 2 }}
                 >
                   <MenuIcon />
                 </IconButton>
                 <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
                   Nゲージ コントローラ Ver 1.0
                 </Typography>
               </Toolbar>
             </AppBar>
       
             <Box sx={{ width: '100%', typography: 'body1' }}>
               <TabContext value={tabValue}>
                 <Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
                   <TabList
                     onChange={(event, newValue) => { setTabValue(newValue) }}
                     aria-label="lab API tabs example">
                     <Tab label={lineName[0]} value="1" />
                     <Tab label={lineName[1]} value="2" />
                   </TabList>
                 </Box>
                 <TabPanel value="1">
                   <Line
                     name = {lineName[0]}
                     dirUrl={dirUrl1}
                     powerUrl={powerUrl1}
                     useLineParam = {useLineParam1}
                     // useLaseInput = {useLastInput1}
                     useOutDir = {useOutDir1}
                     useOutPower = {useOutPower1}
                     useCount = {useCount1}
                     useSnackbar = {useSnackbar1}
                     useContext = {useContext1}
                   ></Line>
                 </TabPanel>
                 <TabPanel value="2">
                   <Line
                     name = {lineName[1]}
                     dirUrl={dirUrl2}
                     powerUrl={powerUrl2}
                     useLineParam = {useLineParam2}
                     // useLaseInput = {useLastInput2}
                     useOutDir = {useOutDir2}
                     useOutPower = {useOutPower2}
                     useCount = {useCount2}
                     useSnackbar = {useSnackbar2}
                     useContext = {useContext2}
                   ></Line>
                 </TabPanel>
               </TabContext>
             </Box>
           </Fragment>
         )
       }
    
Reactのソース、自前でソースを変更するには参考にしてください。 コンパイルしたソースを実行する時は必要ありません。

            /**********************************************************************
            *  共通パラメータの定義
            *  filename : Define.js
            *  create : 2024/04/11 Kazuma.Sasaki
            **********************************************************************/
           export const POWER_MAX = 100;
           export const POWER_MIN = 0;
           export const FREQ_MAX = 10000;
           export const FREQ_MIN = 5;
           export const FREQ_START = 1000;
           export const DEMO_VERSION = falae;
        
Reactのソース、自前でソースを変更するには参考にしてください。 コンパイルしたソースを実行する時は必要ありません。

                /**********************************************************************
                *  タイマーのワーカ
                *  filename : timer.warker.js
                *  create : 2024/04/11 Kazuma.Sasaki
                **********************************************************************/
               var timerId = 0;
               onmessage = function(e) {
                   // console.log('worker recved : ', e.data);
                   var remainTime = e.data.interval;
                   if (remainTime === 0 && timerId !== 0) {
                       // console.log('cleanup Intervaltimer');
                       clearInterval(timerId);
                       timerId = 0;
                   }
                   if (timerId === 0 && remainTime > 0) {
                       // console.log('Generate Intervaltimer : ', remainTime);
                       timerId = setInterval(function() {
                           postMessage({timerId:timerId});
                       }, remainTime);
                   }
                 };
            
Reactのソース、自前でソースを変更するには参考にしてください。 コンパイルしたソースを実行する時は必要ありません。

        /**********************************************************************
        *  本線1本分のコントロール
        *  filename : Line.js
        *  create : 2024/04/11 Kazuma.Sasaki
        **********************************************************************/
       import './App.css';
       import { AppBar, Button, Grid, IconButton, LinearProgress, Paper, Slider, TextField, Toolbar, Typography, Snackbar, Alert, Box, Tab } from "@mui/material";
       import axios from "axios";
       import { Fragment, useEffect, useRef, useState } from "react";
       import { DEMO_VERSION, FREQ_MAX, FREQ_MIN, POWER_MAX, POWER_MIN } from './Define';
       import BoltIcon from '@mui/icons-material/Bolt';
       import { green } from '@mui/material/colors';
       
       // var newlineParam;
       export const update = (useLineParam, useOutPower, useOutDir, useSnackbar, useContext, powerUrl, dirUrl) => {
         const NextStep = (command, newlineParam) => {
           switch (command) {
             case 'OFF':
             case 'UP':
             case 'DOWN':
               newlineParam.dir = command
               break;
             case 'STOP':
             case 'E':
             case 'B8':
             case 'B7':
             case 'B6':
             case 'B5':
             case 'B4':
             case 'B3':
             case 'B2':
             case 'B1':
             case 'H':
             case 'N':
             case 'F1':
             case 'F2':
             case 'F3':
             case 'F4':
               newlineParam.selectedValue = command
               break;
             default:
               break;
           }
           newlineParam.elapsedTime = 0
           newlineParam.scriptIndex++;
         }
       
         // 回生
         const NewRegeneration = (diff, newlineParam, scale) => {
           if (newlineParam.power > newlineParam.powerRange[0]) {
             newlineParam.power -= scale * diff;
             if (newlineParam.power < newlineParam.powerRange[0])
               newlineParam.power = newlineParam.powerRange[0]
           }
         }
       
         // 力行
         const NewPowerRunning = (diff, newlineParam, limit1, limit2, scale1, scale2, scale3) => {
           if (newlineParam.power < limit1)
             newlineParam.power += scale1 * diff;
           else if (newlineParam.power < limit2)
             newlineParam.power += scale2 * (limit2 - newlineParam.power) * diff;
           else
             newlineParam.power -= (scale3 * newlineParam.power * diff);
         }
       
       
         const [lineParam, setLineParam] = useLineParam;
         const [outPower, setOutPower] = useOutPower;
         const [outDir, setOutDir] = useOutDir;
       
         const date = new Date()
         const diff = (Date.now() - lineParam.datetime) / 100.0;
       
         if (lineParam.scriptMode === true) {
           if (lineParam.scriptIndex < lineParam.scriptArray.length) {
             const command = lineParam.scriptArray[lineParam.scriptIndex].trim();
             if (command.indexOf('P') === 0) {
               lineParam.power = Number(command.slice(1))
             }
             if (command.indexOf('Q') === 0) {
               lineParam.freq = Number(command.slice(1))
             }
             if (command.indexOf('W') === 0) {
               const waitingTime = command.slice(1) * 10
               lineParam.elapsedTime += diff
               if (waitingTime <= lineParam.elapsedTime) {
                 NextStep(command, lineParam)
               }
             } else {
               NextStep(command, lineParam)
             }
           } else {
             lineParam.scriptIndex = 0
           }
         }
         switch (lineParam.selectedValue) {
           case 'STOP':
             lineParam.power = 0;
             lineParam.selectedValue = "H"
             break;
           case 'E':
             if (lineParam.power > lineParam.powerRange[0]) {
               lineParam.power -= 5.0 * diff;
               if (lineParam.power < lineParam.powerRange[0])
                 lineParam.power = lineParam.powerRange[0]
             }
             break;
           case 'B8':
             NewRegeneration(diff, lineParam, 1.0)
             break;
           case 'B7':
             NewRegeneration(diff, lineParam, 0.8)
             break;
           case 'B6':
             NewRegeneration(diff, lineParam, 0.5)
             break;
           case 'B5':
             NewRegeneration(diff, lineParam, 0.4)
             break;
           case 'B4':
             NewRegeneration(diff, lineParam, 0.3)
             break;
           case 'B3':
             NewRegeneration(diff, lineParam, 0.2)
             break;
           case 'B2':
             NewRegeneration(diff, lineParam, 0.1)
             break;
           case 'B1':
             NewRegeneration(diff, lineParam, 0.05)
             break;
           case 'N':
             NewRegeneration(diff, lineParam, 0.001 * lineParam.power)
             break;
           case 'F1':
             NewPowerRunning(diff, lineParam, 20, 30, 0.05, 0.005, 0.0001)
             break;
           case 'F2':
             NewPowerRunning(diff, lineParam, 40, 50, 0.1, 0.005, 0.0001)
             break;
           case 'F3':
             NewPowerRunning(diff, lineParam, 70, 80, 0.2, 0.01, 0.0001)
             break;
           case 'F4':
             NewPowerRunning(diff, lineParam, 100, 100, 0.5, 0.5, 0.0001)
             break;
           default:
             break;
         }
       
         // 最高速を超えたらカット
         if (lineParam.power > lineParam.powerRange[1])
           lineParam.power = lineParam.powerRange[1];
       
         // パワー周波数が変わったら出力変更
         if (parseInt(lineParam.power) !== parseInt(outPower.outPower) || parseInt(lineParam.freq) !== parseInt(outPower.outFreq)) {
           console.log("newPower : ", parseInt(lineParam.power), "outPower : ", parseInt(outPower.outPower), "freq : ", parseInt(lineParam.freq), "outFreq : ", parseInt(outPower.outFreq))
       
           sendPower(useLineParam, useOutPower, useSnackbar, useContext, powerUrl)
           // sendPower();
         }
       
         // 進行方向が変わったら出力変更
         if (lineParam.dir !== outDir.outDir) {
           console.log("dir : ", lineParam.dir, "outDir : ", outDir.outDir)
           sendDir(useLineParam, useOutDir, useSnackbar, useContext, dirUrl);
         }
       
         // 時間の退避
         lineParam.datetimeString = date.toLocaleTimeString('ja-JP', { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" })
         lineParam.datetime = date
       
         // 処理したパラメータの退避
         return lineParam;
       }
       
       export const sendPower = (uselineParam, useOutPower, useSnackbar, useContext, powerUrl) => {
         const [lineParam, setLineParam] = uselineParam
         const [outPower, setOutPower] = useOutPower
         const [snackbar, setSnackbar] = useSnackbar
         const [context, setContext] = useContext
       
         if(DEMO_VERSION === true){
           setOutPower(prev => { return { ...prev, outPower: lineParam.power} })
         }else{
           if (outPower.sending === false) {
           // drawLed(context, 20, 20, 10, 'lime')
           // setSending(true)
           const params = new URLSearchParams();
           params.append('power', lineParam.power);
           params.append('freq', parseInt(lineParam.freq));
       
           // setOutPower({ ...outPower, outPower: lineParam.power, outFreq: lineParam.freq })
           setOutPower({ ...outPower, color: 'A400', sending: true })
       
           axios.post(powerUrl, params)
             .then((response) => {
               if (response.statusText === 'OK') {
                 setOutPower(prev => { return { ...prev, outPower: response.data.data.power, outFreq: response.data.data.freq } })
               }
             }).catch((error) => {
               setSnackbar({ ...snackbar, snackbarMessage: "出力、周波数、設定エラー", openSnackbar: true })
             }).finally(() => {
               setOutPower(prev => { return { ...prev, color: '900', sending: false } })
               // drawLed(context, 20, 20, 10, 'darkGreen')
             });
           }
         }
       }
       
       export const sendDir = (uselineParam, useOutDir, useSnackbar, useContext, dirUrl) => {
         const [lineParam, setLineParam] = uselineParam
         const [outDir, setOutDir] = useOutDir
         const [snackbar, setSnackbar] = useSnackbar
         const [context, setContext] = useContext
       
         if(DEMO_VERSION === true){
           setOutDir(prev => { return { ...prev, outDir: lineParam.dir} })
         }else{
           if (outDir.sending === false) {
           // drawLed(context, 60, 20, 10, 'lime')
           // setLineParam({ ...lineParam, dirSending: true })
       
           const params = new URLSearchParams();
           params.append('dir', lineParam.dir)
       
           // setOutDir({ ...outDir, outDir: lineParam.dir })
           setOutDir({ ...outDir, color: 'A400', sending: true })
       
           axios.post(dirUrl, params)
             .then((response) => {
               if (response.statusText === 'OK') {
                 setOutDir(prev => { return { ...prev, outDir: response.data.data.dir, color: '900' } })
               }
             }).catch((error) => {
               setSnackbar({ ...snackbar, snackbarMessage: "方向、設定エラー", openSnackbar: true })
               // setOutDir({ ...outDir, color:'900' })
             }).finally(() => {
               // setLineParam({ ...lineParam, dirSending: false })
               setOutDir(prev => { return { ...prev, color: '900', sending: false } })
               // drawLed(context, 60, 20, 10, 'darkGreen')
             });
           }
         }
       }
       
       
       export const drawLed = (ctx, x, y, r, color) => {
         // if (ctx !== null) {
         //   ctx.fillStyle = color;
         //   ctx.beginPath();
         //   ctx.arc(x, y, r, 0, 2 * Math.PI);
         //   ctx.fill();
         // }
       
         return (
           
         )
       }
       
       export const Line = (props) => {
         const { name, dirUrl, powerUrl, useLineParam, useCount, useOutPower, useOutDir, useSnackbar, useContext } = props
       
         const [context, setContext] = useContext;
         const [lineParam, setLineParam] = useLineParam;
         const [count, setCount] = useCount;
         const [outPower, setOutPower] = useOutPower;
         const [outDir, setOutDir] = useOutDir;
         const [snackbar, setSnackbar] = useSnackbar;
         // const [lastInput, setLastInput] = useLaseInput;
       
         const powerMarks = [
           {
             value: POWER_MIN,
             label: '0%',
           },
           {
             value: POWER_MAX,
             label: '100%',
           },
         ];
       
         const freqMarks = [
           {
             value: FREQ_MIN,
             label: `${FREQ_MIN}Hz`,
           },
           {
             value: FREQ_MAX,
             label: `${FREQ_MAX}Hz`,
           },
         ];
       
         const freqNormalise = (value) => ((value - FREQ_MIN) * 100) / (FREQ_MAX - FREQ_MIN);
       
         const handlePowerChange = (event, newValue) => {
           setLineParam({ ...lineParam, power: newValue })
         }
       
         const handlePowerRangeChange = (event, newValue) => {
           setLineParam({ ...lineParam, powerRange: newValue })
         }
       
         const handleFreqChange = (event, newValue) => {
           setLineParam({ ...lineParam, freq: newValue })
         }
       
         const handlePowerClick = () => {
           setLineParam({ ...lineParam, selectedValue: "STOP", scriptMode: false })
         }
         const handleScriptClick = () => {
           const newScriptArray = lineParam.script.split(',')
           setLineParam({ ...lineParam, scriptArray: newScriptArray, scriptIndex: 0, scriptMode: true })
         }
       
         // useEffect(() => {
         //   const newlineParam = update(lineParam, outPower, outDir, sendPower, sendDir)
         //   setLineParam({ ...newlineParam })
         // }, [count])
       
         useEffect(() => {
           const canvas = document.getElementById("canvas")
           const canvasContext = canvas.getContext("2d")
           setContext(canvasContext)
         }, [])
       
         // useEffect(() => {
         //   const interval = setInterval(() => {
         //     setCount(prevCount => prevCount + 1)
         //   }, 100);
         //   return () => clearInterval(interval)
         // }, [])
       
         return (
            <Fragment >
       
            <AppBar position="static">
              <Toolbar>
                <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
                  {name}
                </Typography>
              </Toolbar>
            </AppBar>
      
            <Grid container spacing={4} sx={{ p: 8 }}>
              <Grid item xs={2}>
                <Button
                  variant="contained"
                  onClick={handlePowerClick}
                  fullWidth
                >
                  STOP(停止)
                </Button>
              </Grid>
              <Grid item xs={2}>
                <Button
                  variant="contained"
                  onClick={handleScriptClick}
                  fullWidth
                  disabled={lineParam.scriptMode === true}
                >
                  SCRIPT(スクリプト)
                </Button>
              </Grid>
              <Grid item xs={1}>
                <canvas width="10" height="32" id="canvas"></canvas>
              </Grid>
              <Grid item xs={1}>
                {"出力"}
                <BoltIcon sx={{ color: green[outPower.color] }}></BoltIcon>
              </Grid>
              <Grid item xs={1}>
                {"方向"}
                <BoltIcon sx={{ color: green[outDir.color] }}></BoltIcon>
              </Grid>
              <Grid item xs={5} />
              <Grid item xs={1}>
                <Paper
                  sx={{ p: 1, bgcolor: 'green', textAlign: 'center' }}
                >{lineParam.dir}</Paper>
              </Grid>
              <Grid item xs={2}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const dir = "UP"
                    setLineParam({ ...lineParam, dir: dir })
                    // setLastInput({ ...lastInput, dir: dir })
                  }}
                >
                  UP(上り)
                </Button>
              </Grid>
              <Grid item xs={2}>
                <Button
                  variant="contained"
                  onClick={(event) => {
                    const dir = "OFF"
                    setLineParam({ ...lineParam, dir: dir })
                    // setLastInput({ ...lastInput, dir: dir })
                  }}
                  fullWidth
                >
                  OFF(切る)
                </Button>
              </Grid>
              <Grid item xs={2}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const dir = "DOWN"
                    setLineParam({ ...lineParam, dir: dir })
                    // setLastInput({ ...lastInput, dir: dir })
                  }}
                >
                  DOWN(下り)
                </Button>
              </Grid>
              <Grid item xs={5} />
      
              <Grid item xs={1}>
                <Paper
                  sx={{ p: 1, bgcolor: 'green', textAlign: 'center' }}
                >{lineParam.selectedValue}</Paper>
              </Grid>
              <Grid item xs={1}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "E"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  E(非常)
                </Button>
              </Grid>
      
              <Grid item xs={0.5}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "B8"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  B8
                </Button>
              </Grid>
              <Grid item xs={0.5}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "B7"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  B7
                </Button>
              </Grid>
              <Grid item xs={0.5}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "B6"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  B6
                </Button>
              </Grid>
              <Grid item xs={0.5}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "B5"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  B5
                </Button>
              </Grid>
              <Grid item xs={0.5}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "B4"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  B4
                </Button>
              </Grid>
              <Grid item xs={0.5}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "B3"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  B3
                </Button>
              </Grid>
              <Grid item xs={0.5}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "B2"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  B2
                </Button>
              </Grid>
              <Grid item xs={0.5}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "B1"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  B1
                </Button>
              </Grid>
              <Grid item xs={1.0}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "N"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  N
                </Button>
              </Grid>
              <Grid item xs={1}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "F1"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  F1
                </Button>
              </Grid>
              <Grid item xs={1}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "F2"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  F2
                </Button>
              </Grid>
              <Grid item xs={1}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "F3"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  F3
                </Button>
              </Grid>
              <Grid item xs={1}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "F4"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  F4
                </Button>
              </Grid>
      
              <Grid item xs={1}>
                <Button
                  variant="contained"
                  fullWidth
                  onClick={(event) => {
                    const selectedValue = "H"
                    setLineParam({ ...lineParam, selectedValue: selectedValue })
                    // setLastInput({ ...lastInput, selectedValue: selectedValue })
                  }}
                >
                  H
                </Button>
              </Grid>
              <Grid item xs={4} />
              <Grid item xs={12}>
                出力 : {lineParam.power.toFixed(1)}[%]
                <LinearProgress
                  sx={{ height: 20, borderRadius: 10 }}
                  variant="determinate"
                  value={outPower.outPower}
                />
                <Slider
                  marks={powerMarks}
                  step={1}
                  value={lineParam.power}
                  valueLabelDisplay="auto"
                  min={POWER_MIN}
                  max={POWER_MAX}
                  onChange={handlePowerChange}
                />
                範囲[%]
                <Slider
                  marks={powerMarks}
                  step={1}
                  value={lineParam.powerRange}
                  valueLabelDisplay="auto"
                  min={POWER_MIN}
                  max={POWER_MAX}
                  onChange={handlePowerRangeChange}
                />
              </Grid>
              <Grid item xs={12}>
                周波数 : {lineParam.freq}[Hz]
                <LinearProgress
                  sx={{ height: 20, borderRadius: 10 }}
                  variant="determinate"
                  value={freqNormalise(outPower.outFreq)}
                />
                <Slider
                  marks={freqMarks}
                  step={5}
                  value={lineParam.freq}
                  valueLabelDisplay="auto"
                  min={FREQ_MIN}
                  max={FREQ_MAX}
                  onChange={handleFreqChange}
                />
              </Grid>
              <Grid item xs={12}>
                <TextField
                  label="Script"
                  multiline
                  rows={4}
                  value={lineParam.script}
                  variant="filled"
                  onChange={(event) =>
                    setLineParam({ ...lineParam, script: event.target.value })
                  }
                  fullWidth
                />
              </Grid>
              <Grid item xs={10} />
              <Grid item xs={2}
                sx={{ textAlign: 'right' }}
              >
                {lineParam.datetimeString}
              </Grid>
            </Grid>
            <Snackbar
              open={snackbar.openSnackbar}
              autoHideDuration={6000}
              onClose={(event) => {
                setSnackbar({ ...snackbar, openSnackbar: false })
              }}
            >
              <Alert
                onClose={(event) => {
                  setSnackbar({ ...snackbar, openSnackbar: false })
                }}
                severity="warning"
                variant="filled"
                sx={{ width: '100%' }}
              >
                {snackbar.snackbarMessage}
              </Alert>
            </Snackbar>
          </Fragment >
        );
      }
    

はまったところ


興味のある方はこちらにデモ画面があります。