ブラウザから操作可能なラジコンを作る【５】


## 【５】DualSenseの情報をRaspberryPiに飛ばす

DualSenseの入力が取得できたので、次はRaspberryPi側に飛ばします。
できるだけ低遅延のほうが間違いなく良いので
WebSocket経由で情報を飛ばします。


<br></br>
## 目次
* [【１】Raspberry pi の、GPIOをTypescriptから操作](https://qiita.com/Yasukosan/items/1bf9480c10de7d69c2a4)
* [【２】DCモーターをPWMで速度制御](https://qiita.com/Yasukosan/items/291c76c10be135fd7595)
* [【３】サーボモーターを制御](https://qiita.com/Yasukosan/items/e81c9c9d0159b32e5893)
* [【４】DualSenseをブラウザに接続](https://qiita.com/Yasukosan/items/bf3a085145db84a48823)
* [【５】DualSenseの情報をRaspberryPiに飛ばす](https://qiita.com/Yasukosan/items/400883be94751966221a)
* [【６】DualSenseのチャタリング？問題対応](https://qiita.com/Yasukosan/items/7e52609699b4b71196ca)
* [【７-１】RaspberryPiから低遅延で映像を飛ばす](https://qiita.com/Yasukosan/items/98426f1ba4dea4c9ec5f)
* [【７-２】RaspberryPiから低遅延で映像を飛ばす](https://qiita.com/Yasukosan/items/2246c8a32602ca77ace9)
* [【８】ThreeJSでVRもどきを作成](https://qiita.com/Yasukosan/items/e15e8fd002cf49e3d04d)
* [【９-１】iPhoneの加速度から頭の向きをVRに反映](https://qiita.com/Yasukosan/items/9b4333770baea4110361)
* [【９-２】スマホVRゴーグル向けデザインに変える](https://qiita.com/Yasukosan/items/7f1bb7a9d95cb76f8117)
* [【１０】ラジコン本体の製作](https://qiita.com/Yasukosan/items/0d37c1df5b5d6c02257b)
* 【１１】パワーアップとバッテリー問題の解決


<br></br>
## WebSocketクライアント作成

**WebSocket自体の解説は、先人の詳しい情報が山程溢れていますので
可能な限り端折っていきます。**

WebSocketでデータを送信するクライアント部分になります。
例示しているのは、かなり古いコードですが
データを送るだけならば特に問題はないので、そのまま使います。
例によって簡単なサービス化されたファイルになっています。

必要な機能としては

* 後の方で必須になるので、適当なサーバー証明でhttps化出来ていること
* Socket送信部分が、Callbackに渡せるように

以上が満たせていれば、ChatGPTにでも
「WebSocketを使ったクライアント側の機能を作って」
のように軽い感じで返ってきたコードで必要十分と思います。

本件の制作環境では、nextjsで制作を進めたので
package.jsonのスクリプトに、13.5以降で追加された
「--experimental-https」オプションを追記し、クライアント側のhttps化を行っています。
```powershell
  "scripts": {
    "dev": "next dev --turbopack --experimental-https",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "depen
```


ファイル名は「socket-client.ts」にて制作しています。

コードの全文はこちらから
<details><summary>socket-client.tsの中身</summary>

```typescript

type wsData {
    job: string;
    room?: string;
    client?: string;
    data: any;
}

type NextCallType = {
    [K in 'open' | 'message' | 'close' | 'error']: 
        ((e: Event | MessageEvent) => void) | false 
}

export class SocketClient2Service {
    private static instance: SocketClient2Service;

    private socket: WebSocket | undefined;
    private port = 3000;
    private url = 'ws://localhost';

    private onRoom = false;
    private onEvent = false;

    private Nexts: NextCallType = {
        open: false,
        message: false,
        close: false,
        error: false
    };

    // メッセージ受信後の処理
    private next: { [key: string]: Function } = {
        on: () => {
            return null;
        },
        room: () => {
            return null;
        }
    };

    public constructor(port = 0, url = '') {
        this.port = port > 0 ? port : this.port;
        this.url = url !== '' ? url : this.url;
        try {
            this.socket = new WebSocket(this.url + ':' + this.port);
        } catch (error) {
            console.error(error);
        }
    }

    public static call(
        port = 3000,
        url = 'http://localhost'
    ): SocketClient2Service {
        if (!SocketClient2Service.instance) {
            SocketClient2Service.instance = new SocketClient2Service(port, url);
        }
        return SocketClient2Service.instance;
    }


    public setNext(
        next: (e: Event | MessageEvent) => void,
        target: 'open' | 'message' | 'close' | 'error'
    ): SocketClient2Service {
        this.Nexts[target] = next
        return this
    }

    /**
     * 待受開始
     */
    public listen(): SocketClient2Service {
        if (this.onEvent || this.socket === undefined) {
            return this;
        }

        this.socket.addEventListener('open', (e: Event) => {
            console.log('Open WebSocket', e);
            if (this.Nexts.open !== false) {
                this.Nexts.open(e);
            }
        });

        this.socket.addEventListener('message', (m: MessageEvent) => {
            const d = this.fromJson(m.data)
            DataTypeHelper.call().setFile(d.data).check()
            if (this.Nexts.message !== false) {
                this.Nexts.message(d)
            }
        })
        this.socket.addEventListener('close', (e: Event) => {
            console.log('Close WebSocket', e)
            if (this.Nexts.close !== false) {
                this.Nexts.close(e)
            }
        })
        this.socket.addEventListener('error', (e: Event) => {
            console.log('Error::')
            console.error(e)
            if (this.Nexts.error !== false) {
                this.Nexts.error(e)
            }
        })
        this.onEvent = true

        return this
    }

    /**
     * メッセージの送信
     * @param data wsData
     * @returns
     */
    public send(data: wsData): SocketClient2Service {
        if (this.socket === undefined) return this
        this.socket.send(this.toJson(data))
        return this
    }

    public joinRoom(room = ''): SocketClient2Service {
        if (this.onRoom) {
            return this;
        }
        const _room = room === '' ? this.rand() : room;
        console.log(`room: ${_room}`);
        this.send({
            job: 'join',
            room: _room,
            data: {}
        });
        this.onRoom = true;

        return this;
    }

    private rand() {
        const S =
            'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
        const N = 16
        return Array.from(Array(N))
            .map(() => S[Math.floor(Math.random() * S.length)])
            .join('')
    }

    private toJson(data: object): string {
        return JSON.stringify(data)
    }

    private fromJson(data: string): any {
        return JSON.parse(data)
    }
}


```

</details>

## DualSensの情報を投げる処理

Websocketのクライアントサービスを読み込み
* 実際の接続を行う機能
* WebHIDからの入力をサーバーに送信する

この2機能を持たせたファイルを作ります。

### WebSocketサーバーと接続

作成済みのサービスから接続部分を順に呼ぶだけなので
非常に簡素です。

接続先は、RaspberryPiの設定に合わせて適時変えて下さい。
今回は
* 接続先IP      ： 192.168.11.10
* 接続先ポート  ： 8800

にて処理を書いています。

接続部分で1秒待ちが入っているのは
本来は接続イベントをキャッチした後に続く処理を明記しますが
**書き換えが面倒**であったため、1秒待てばほぼ100%接続が終わってるであろうと
1秒間のスリープを挟んでいます。

```typescript
    // インスタンス作成
    ws = SocketClient.call(
            8800,                    // RaspberryPi側のWebSocketのポート
            'https://192.168.11.10', // RaspberryPi側のIP
        )
    // サーバーと通信開始
    ws.listen()
    await sleep(1000)
    // 部屋に入る
    ws.joinRoom()
```

ですので、本来はWebSocketのサービス側で
処理できていないとだめな部分ですが
今回はさして重要ではありませんのでスルーします。

### WebSocketサーバーに投げる
サーバーと接続し、送信の準備が出来ると
WebHIDからの入力イベントを処理したコールバック関数から
ボタンのマッピングを行った変数を受け取り
そのまま投げる部分が要りますので

下記のように

JSON文字列に変換して投げるだけ

の処理を作ります。

```typescript
    ws.send({
        job: 'other',
        data: JSON.stringify({
            task: 'ctrl',
            button: {
                up: state.buttons.dPadUp,
                down: state.buttons.dPadDown,
                left: state.buttons.dPadLeft,
                right: state.buttons.dPadRight,
                left_stick_x: state.axes.leftStickX,
                left_stick_y: state.axes.leftStickY,
                left_stick: state.buttons.leftStick,
                right_stick_x: state.axes.rightStickX,
                right_stick_y: state.axes.rightStickY,
                right_stick: state.buttons.rightStick,
                triangle: state.buttons.triangle,
                circle: state.buttons.circle,
                cross: state.buttons.cross,
                square: state.buttons.square,
                l1: state.buttons.l1,
                l2: state.buttons.l2,
                l3: state.buttons.l3,
                r1: state.buttons.r1,
                r2: state.buttons.r2,
                r3: state.buttons.r3,
                left_axes: convertRange(state.axes.l2),
                right_axes: convertRange(state.axes.r2),
                start: state.buttons.start,
                select: state.buttons.select,
            }
        })
    })
```


コントローラーのボタンOn/Offはそのまま送って問題無いのですが
アナログ部分、スピード調整を行うトリガーの入力は
0.0～1.0の範囲となり、モーターコントローラーの0～255と異なります。
ですので、アナログ入力の数値範囲を変換する関数「convertRange」を
トリガーの入力にだけ挟んでいます。
```typescript
// DCモーターのパワーを、0～255の間に変換する
export const convertRange = (value: number): number => {
    if (value < 0 || value > 1) {
        throw new Error('Value must be between 0.0 and 1.0');
    }
    return Math.round(value * 254) + 1;
}
```

以下、サンプルの全文です。
このファイルを「socket.helper.ts」というファイル名で保存し、使います。

<details><summary>socket.helper.tsのコード</summary>

```typescript
import { SocketClient } from './socket_client'
import { ControlType } from '@/src/definisions'
import { DualSense } from './webhid_ds'

let ws: SocketClient | null = null

// WebSocketサーバーに接続
export const socketConnect = async (): Promise<void> => {
    // インスタンス作成
    ws = SocketClient.call(
            8800,                    // RaspberryPi側のWebSocketのポート
            'https://192.168.11.10', // RaspberryPi側のIP
        )
    // サーバーと通信開始
    ws.listen()
    await sleep(1000)
    // 部屋に入る
    ws.joinRoom()
    
}

// WebHIDのサービスに渡す、コールバック関数
export const buttonSend = async (
    state: DualSense['state'],
    data: DataView
): Promise<void> => {
    if (!ws) {
        console.log('ws is not connected')
        return
    }

    ws.send({
        job: 'other',
        data: JSON.stringify({
            task: 'ctrl',
            button: {
                up: state.buttons.dPadUp,
                down: state.buttons.dPadDown,
                left: state.buttons.dPadLeft,
                right: state.buttons.dPadRight,
                left_stick_x: state.axes.leftStickX,
                left_stick_y: state.axes.leftStickY,
                left_stick: state.buttons.leftStick,
                right_stick_x: state.axes.rightStickX,
                right_stick_y: state.axes.rightStickY,
                right_stick: state.buttons.rightStick,
                triangle: state.buttons.triangle,
                circle: state.buttons.circle,
                cross: state.buttons.cross,
                square: state.buttons.square,
                l1: state.buttons.l1,
                l2: state.buttons.l2,
                l3: state.buttons.l3,
                r1: state.buttons.r1,
                r2: state.buttons.r2,
                r3: state.buttons.r3,
                left_axes: convertRange(state.axes.l2),
                right_axes: convertRange(state.axes.r2),
                start: state.buttons.start,
                select: state.buttons.select,
            }
        })
    })
}

// DCモーターのパワーを、0～255の間に変換する
export const convertRange = (value: number): number => {
    if (value < 0 || value > 1) {
        throw new Error('Value must be between 0.0 and 1.0');
    }
    return Math.round(value * 254) + 1;
}

// sleep
const sleep = (msec: number) => new Promise(resolve => setTimeout(resolve, msec))
```

</details>

<br></br>
## DualSenseの情報をサーバーに投げる

* WebSocketのクライアント
* サーバーへの接続と、コントローラーの入力の送信
* DualSenseと接続し、入力を受ける

前回と今回で、必要な機能が揃ったと思います。
Bluetooth接続されたDualSenseを、WebHID経由で操作情報を受け取り、WebSocket経由でサーバーに投げる
という一連の処理を続けて行います。

BluetoothとDualSenseの接続は
WebHIDの呼び出し時に、機器の選択が1クッション入ります。
この段階で繋いでも問題ありませんので、気がついたタイミングで繋いでおいてください。

機能は既に出来てますので
順に呼ぶだけで機能します。

今の段階では、サーバー側が無いのでエラーになります。



```typescript
import { ReturnSuccess, ReturnError } from "@/src/definisions"
import { ButtonStatus, init } from "@/src/_lib/bluetooth/ds4.service"
import { socketConnect, buttonSend } from "@/src/domain/Control/helper/socket.helper"
import { DualSenseStateType } from "../../../_lib/bluetooth/_helper/webhid_ds"

const BS = ButtonStatus

export const controlDispatch = async (
): Promise<ReturnSuccess<string> | ReturnError> => {
    // WebSocket接続
    await socketConnect()
    // DualSense接続
    initDS4()
    // WebHID情報が更新されるたびにWebSocketで送信
    init(buttonSend)

    return {
        status: true,
        message: ''
    }
}
```

実装の際は、ブラウザのボタンイベント等に紐づけし
「controlDispatch」を呼び出してもらうと
クライアント側の処理が順に行われます。


<br></br>
# WebSocketサーバー作成

次にサーバー側に入ります。

クライアント側と同じく、かなり前に作ったサービスをそのまま利用しています。
nodejsの問題でGPIOの制御側と同じJavascriptに変更しています

そして、先人達の知恵が大量に見つかりますので
WebSocketの実装部分の説明は割愛します。

このサービスは「socket-server.js」で保存しています。

コードの全文はこちらから
<details><summary>socket-server.jsの中身</summary>

```javascript
const ws = require('ws')
const fs = require('fs')
const https = require('https')

let certs = {
    key: '',
    cert: '',
}

let server = null
let port   = 8800
//const app = express()
let _server = null

let jobStack = {
    cast    : [],
    other   : [],
}

    // メッセージ受信後の処理
const next = {
    cast    : () => {return null},
    other   : () => {return false},
}

/**
 * 証明書を設定
 * @param key string
 * @param cert string
 * @returns SocketServer2Service
 */
function setCerts(key, cert) {
    certs.key  = fs.readFileSync(key)
    certs.cert = fs.readFileSync(cert)
}

/**
 * WebSocketの待受ポート設定
 * @param port number 
 * @returns SocketServer2Service
 */
function setPort(_port) {
    _port = (typeof(_port) === 'number' ) ? _port : port
}

/**
 * メッセージ受信後の処理を設定
 * クライアントから送信された[message]を
 * そのまま受け取り処理する関数を任意で追加出来る
 * ※上書きではなく処理の追加なので注意
 * 
 * @param next Function 
 * @param target string Casts
 * @returns 
 */
function setNext(
    _next = (data) => {console.log(data)},
    target
) {
    try {
        _next('a')
        next[target] = _next
    } catch (error) {
        
    }
}

/**
 * 接続中の全クライアントに送信
 * @param data any（文字列 or 配列 or オブジェクト）
 * @returns ScoketServer2Service
 */
function sendAllClient(data) {
    server.clients.forEach((client) => {
        send(client, 'cast', data)
    })
}


function otherRequest(data) {
    next.other(data)
}

/**
 * websocketサーバーを起動
 * @returns 
 */
function start() {
    if (certs.key !== '' && certs.cert !== '') {
        _server = https.createServer({
            key: certs.key,
            cert: certs.cert,
        })
        server = new ws.Server({ server : _server })
        console.log(`
    [Start SSL Socket Server] Listen Port :: ${port}
        `);
    } else {
        server = new ws.Server({ port : port})
        console.log(`
    [Start Socket Server] Listen Port :: ${port}
        `);
    }

    // サーバーアクション設定
    action()

    if (_server !== null) {
        _server.listen(port)
    }
}

/**
 * 受信時の動作を登録
 * @returns 
 */
function action() {
    server.on('connection', (s) => {
        // IDを付与
        s.ipAddress = s._socket.remoteAddress.replace(/^.*:/g,'')
        s.port      = s._socket.remotePort
        s.unique    = buildId()
        s.id        = `${s.ipAddress}:${s.port}:${s.unique}`

        // 生成されたIDを返す
        s.send(JSON.stringify({
            job : 'connection',
            id  : s.id,
        }))

        // 待受開始
        s.on(
            'message',
            async (message) => {
                const m = fromJson(message)
                await lisnHub(m, s)
                executeJob()
            }
        )
    })
}

/**
 * job毎に処理を振り分け
 * @param m any 受信データ
 * @param s any 接続ClientのSocket
 * @returns Promise<SocketServer2Service>
 */
async function lisnHub(m, s) {

    if (m.job === 'cast') {
        const job = (next.cast() !== null)
                        ? next.cast(m) : {to: 'cast', data: m.data}
        addJob(job, 'cast')
    }

    if (m.job === 'other') {
        const job = {to: 'other', data: m.data}
        addJob(job, 'other')
    }

}

/**
 * Jobスタックにデータを追加
 * @param job {to: string, data: any} 送信先と送信データ
 * @param send Casts 送信方式、全配信、部屋配信、指定配信
 * @returns SocketServer2Service
 */
function addJob(
    job,
    send
) {
    jobStack[send].push(job);
}

/**
 * スタックされたジョブを実行
 * @returns SocketServer2Service
 */
function executeJob() {

    for (const key in jobStack) {
        if (Object.prototype.hasOwnProperty.call(jobStack, key)) {
            if (jobStack[key].length > 0) {
                jobStack[key].map((job) => {
                    if (key === 'cast') {
                        sendAllClient(job.data);
                    } else if (key === 'other') {
                        otherRequest(fromJson(job.data));
                    }
                })
                jobStack[key] = [];
            }
        }
    }
}

/**
 * クライアントに送信
 * @param client any WebSocketオブジェクト
 * @param job string 送信処理種別
 * @param data any 送信データの中身（JSON文字列）
 * @returns SocketServer2Service
 */
function send(client, job, data) {
    client.send(toJson({
        job     : job,
        data    : data
    }))
}

/**
 * ClientIDを生成
 * @returns string ランダム生成されたClinetID
 */
function buildId() {
    return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
}

function toJson(data) {
    return JSON.stringify(data);
}

function fromJson(data) {
    return JSON.parse(data);
}

module.exports = {
    setCerts,
    setPort,
    setNext,
    sendAllClient,
    start,
    action,
    lisnHub,
    addJob,
    executeJob,
    send,
    buildId,
    toJson,
    fromJson
};
```

</details>


<br></br>
## サーバー証明書
使用するサーバー証明書は
openssl等を使用し、適時作成するか、手元の証明書を活用ください。
```powershell
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
```

**オレオレ証明書の場合
不明なサーバー証明を許可する必要があるので
WebSocketサーバー起動後、該当のポートにブラウザでアクセスする必要があります。**


接続クライアントが実質1台のみ
通信内容も定型で、それ以外を考える必要も無いので
かなり簡素に

データが来たら、GPIOの制御関数に渡す

のみ行います。

以下の内容を記述した、「test.js」を保存します。

```javascript
const socket = require('./socket_server')
const control = require('./control.helper')

const keys = {
    key: './keys/key.pem',
    cert: './keys/cert.pem'
}

function setup() {
    socket.setCerts(keys.key, keys.cert)
    control.setup()
    socket.setNext(
        async (data) => {
            if (data.task === 'ctrl') {
                control.control(data.button)
            }
        },
        'other'
    )
    return this
}

const listen_start = () => {
    socket.start()
}

setup()
listen_start()

```


## 操作情報でGPIOを操作する

ボタン数は非常に多くありますが
とりあえず、ハンドルとアクセル（前進・後退）のみあればいいので

|入力キー|操作|
|--|--|
|左キー|ハンドルを左に|
|右キー|ハンドルを右に|
|右トリガー|前進|
|左トリガー|後退|

以上の入力を割り振ります。
後々、ブースト機能や、ライト点灯、ミスト噴射、何かしら射出等、付けても面白くなります。

モーターのパワー、ハンドル角の制御変数と合わせて
このようなオブジェクトにまとめています。
```typescript
const MotorPorts = {
    MOROT1_IN: 19,
    MOROT1_OUT: 26,
    MORTOR_PWM: 12,
    MORTOR_POWER: 1,
    HANDLE: 17,
    INITIAL_ANGLE: 850,
    INITIAL: false
}
```

初期化処理は、GPIOのサンプルと同じで
DCモーターと、サーボの処理が一緒になっただけです。
複数回呼ばれるとよろしく無いので
実行済みフラグを入れています。

```typescript
    if (MotorPorts.INITIAL) {
        return
    }
    gpio.addPort({port: MotorPorts.MOROT1_IN, mode: 'out'})
    gpio.addPort({port: MotorPorts.MOROT1_OUT, mode: 'out'})
    gpio.addPort({port: MotorPorts.MORTOR_PWM, mode: 'out'})
    gpio.addPort({port: MotorPorts.HANDLE, mode: 'out'})

    await gpio.initGpio()

    console.log(gpio.write(MotorPorts.MOROT1_IN, 0))
    console.log(gpio.write(MotorPorts.MOROT1_OUT, 0))
    console.log(gpio.pwmWrite(MotorPorts.MORTOR_PWM, MotorPorts.MORTOR_POWER))

    MotorPorts.INITIAL = true

    gpio.searvoWrite(MotorPorts.HANDLE, MotorPorts.INITIAL_ANGLE)
```

websocketサーバーのところで実際に呼ばれていた
入力情報を受け取る部分です。

見たまんまですが
押されたボタンに合わせて紐づけされたGPIOに出力を送ります。
```typescript
const control = (button) => {
    
    ButtonMap = button

    if (button.right_axes > 1) {
        MotorPorts.MORTOR_POWER = Number(button.right_axes)
        console.log(gpio.write(MotorPorts.MOROT1_IN, 0))
        console.log(gpio.write(MotorPorts.MOROT1_OUT, 1))
        console.log(gpio.pwmWrite(MotorPorts.MORTOR_PWM, MotorPorts.MORTOR_POWER))
    }
    if (button.left_axes > 1 && button.right_axes === 1) {
        MotorPorts.MORTOR_POWER = Number(button.left_axes)
        console.log(gpio.write(MotorPorts.MOROT1_IN, 1))
        console.log(gpio.write(MotorPorts.MOROT1_OUT, 0))
        console.log(gpio.pwmWrite(MotorPorts.MORTOR_PWM, MotorPorts.MORTOR_POWER))
    }
    if (button.left) {
        console.log(gpio.searvoWrite(MotorPorts.HANDLE, 1150))
    }
    if (button.right) {
        console.log(gpio.searvoWrite(MotorPorts.HANDLE, 550))
    }
}
```


この内容をまとめて、「control.helper.js」で保存しています。
コードの全文は以下

<details><summary>control.helper.jsの中身</summary>

```javascript
const gpio = require('./gpio.service')

let ButtonMap = {
    up: false,
    down: false,
    left: false,
    right: false,
    left_stick_x: 0,
    left_stick_y: 0,
    left_stick: false,
    right_stick_x: 0,
    right_stick_y: 0,
    right_stick: false,
    triangle: false,
    circle: false,
    cross: false,
    square: false,
    l1: false,
    l2: false,
    l3: false,
    r1: false,
    r2: false,
    r3: false,
    left_axes: 1,
    right_axes: 1,
    start: false,
    select: false,
}

const MotorPorts = {
    MOROT1_IN: 19,
    MOROT1_OUT: 26,
    MORTOR_PWM: 12,
    MORTOR_POWER: 1,
    HANDLE: 17,
    INITIAL_ANGLE: 850,
    INITIAL: false
}


const setup = async () =>{
    if (MotorPorts.INITIAL) {
        return
    }
    gpio.addPort({port: MotorPorts.MOROT1_IN, mode: 'out'})
    gpio.addPort({port: MotorPorts.MOROT1_OUT, mode: 'out'})
    gpio.addPort({port: MotorPorts.MORTOR_PWM, mode: 'out'})
    gpio.addPort({port: MotorPorts.HANDLE, mode: 'out'})

    await gpio.initGpio()

    console.log(gpio.write(MotorPorts.MOROT1_IN, 0))
    console.log(gpio.write(MotorPorts.MOROT1_OUT, 0))
    console.log(gpio.pwmWrite(MotorPorts.MORTOR_PWM, MotorPorts.MORTOR_POWER))

    MotorPorts.INITIAL = true

    gpio.searvoWrite(MotorPorts.HANDLE, MotorPorts.INITIAL_ANGLE)
}

const control = (button) => {
    
    ButtonMap = button

    if (button.right_axes > 1) {
        MotorPorts.MORTOR_POWER = Number(button.right_axes)
        console.log(gpio.write(MotorPorts.MOROT1_IN, 0))
        console.log(gpio.write(MotorPorts.MOROT1_OUT, 1))
        console.log(gpio.pwmWrite(MotorPorts.MORTOR_PWM, MotorPorts.MORTOR_POWER))
    }
    if (button.left_axes > 1 && button.right_axes === 1) {
        MotorPorts.MORTOR_POWER = Number(button.left_axes)
        console.log(gpio.write(MotorPorts.MOROT1_IN, 1))
        console.log(gpio.write(MotorPorts.MOROT1_OUT, 0))
        console.log(gpio.pwmWrite(MotorPorts.MORTOR_PWM, MotorPorts.MORTOR_POWER))
    }
    if (button.left) {
        console.log(gpio.searvoWrite(MotorPorts.HANDLE, 1150))
    }
    if (button.right) {
        console.log(gpio.searvoWrite(MotorPorts.HANDLE, 550))
    }
}


module.exports = {
    setup,
    control
}
```

</details>


<br></br>
## 試験

完成した内容を、下記コマンドで実行し
待受状態にした後
```powershell
sudo node test.js
```

ブラウザから接続処理を開始すると
コントローラーの入力に合わせて、各モーターが動きますが
問題が起こります。

<iframe width="560" height="315" src="https://www.youtube.com/embed/nU750q60obE?si=Ax3YD37ttL8m2kim" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

DCモーターは動きっぱなしで
サーボモーターはセンター位置に戻りません

次回はこの問題を修正します。

<br></br>
<br></br>
# UZAYA
Uzayaでは、多分仕事を求めています。
何かの役に立ちそうでしたら、是非お知らせを。