【PHP】文字列を入れ子構造にしたい

PHPを使用している際に,htmlタグごと別ファイルに送りたいときがあります.

その際に,"<a href="#"></a>"とダブルクォーテーションの中にダブルクォーテーションが入り入れ子構造になってしまいます.

当然この場合は#前後で二つの文字列として認識されてしまいます.

 

このような状況になった際はシングルクォーテーションとダブルクォーテーションを使用しましょう.

つまり,外側はダブルクォーテーションで,内側はシングルクォーテーションで記述するとうまくいきます."<a href='#'></a>"

 

Invalid HTTP_HOST header: 'localhost'. You may need to add 'localhost' to ALLOWED_HOSTS.

久しぶりにDjangoで作成したアプリを立ち上げた際に以下のエラーが出て作成したページを表示できなかった.

エラー内容

エラーの原因としては,以前デプロイした際にSettings.pyのALLOWED_HOSTS=['○○']の○○に特定のIPアドレスを設定したことでした.

この部分を,ALLOWED_HOSTS=['*']とアスタリスクに変えると動くようになりました.

VS CodeのGo Liveでバックエンドプログラムが使えない

phpなどで構成したウェブサイトを確認するべくVS Code拡張機能であるGo Liveを使用したところ,.phpファイルがダウンロードされる形となりました.

 

結論として,Go LiveにPHPのようなバックエンドプログラムを処理する機能が備わっていないため,別の方法を考える必要があります.

別の方法とは,MAMPというソフトを使うという方法です.

www.mamp.info

MAMPをインストールした後に起動すると以下の画面になります.

MAMP

こちらの画面の左上のタブ「MAMP」→「Preferences...」を確認するとローカルホストのポート番号などの詳細情報が分かります.

localhost:○○にアクセスすればデフォルトの画面が表示されます.

 

自分の作成した.phpファイルをブラウズするためには,二つの方法があります.

一つ目は,「Preferences...」から参照するファイルの位置の変更などが行えます.

二つ目は,「MAMP」→「htdocs」の中にあるindex.phpを書き換えると良いです.

 

teratail.com

WitMotionをUnityで使う

Unity Hubで新規プロジェクトを作成します.

  • 3D
  • version: 2020.3.21f1

オブジェクトを配置

二つのオブジェクトをHierarchyで作成し,Main CameraをVReye下に移動させます.

  1. VReye
  2. SerialHandler

以下,階層構造

スクリプトを作成

Assets内で以下二つのc#スクリプトを新規作成します.

  1. Rotate
  2. SerialHandler

それぞれの内容は以下をコピペしてください.

Rotate

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Rotate : MonoBehaviour
{
    public SerialHandler serialHandler;

    void Start(){
        // 
    }

    void Update(){
        // transformを取得
        Transform myTransform = this.transform;

        // ワールド座標を基準に、回転を取得
        Vector3 worldAngle = myTransform.eulerAngles;
        worldAngle.y = (float)serialHandler.roll;
        worldAngle.x = (float)serialHandler.pitch;
        myTransform.eulerAngles = worldAngle;
    }
}
    
SerialHander

using UnityEngine;
using System.Collections;
using System.IO.Ports;
using System.Threading;
using System.Collections.Generic;
using System;


public class SerialHandler : MonoBehaviour
{
    public delegate void SerialDataReceivedEventHandler(string message);
    public event SerialDataReceivedEventHandler OnDataReceived;

    //ポート名
    public string portName = "COM1";// 自分の使用したいセンサを確認し,適切なCOM番号に変更してください
    public int baudRate    = 115200;

    private SerialPort serialPort_;
    private Thread thread_;
    private bool isRunning_ = false;

    public Queue cmds = new Queue();


    private string message_;
    private bool isNewMessageReceived_ = false;

    private string msg = "";
    public double roll=0.0, pitch=0.0, yaw=0.0;

    byte[] buffer = new byte[100];
    int cnt=0;

    void Awake()
    {
        Open();
    }

    void Update()
    {
        // nothing
    }

    void OnDestroy()
    {
        Close();
    }

    private void Open()
    {
        serialPort_ = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);
        serialPort_.Open();
        serialPort_.ReadTimeout = 50;
        
        isRunning_ = true;

        thread_ = new Thread(Read);
        thread_.Start();
    }

    private void Close()
    {
        isNewMessageReceived_ = false;
        isRunning_ = false;

        if (thread_ != null && thread_.IsAlive) {
            thread_.Join();
        }

        if (serialPort_ != null && serialPort_.IsOpen) {
            serialPort_.Close();
            serialPort_.Dispose();
        }

    }

    private void Read()
    {
        int plus       = 0;
        int minus      = 0;
        double preRoll = 0;

        // COMPort確認
        // string[] ports = SerialPort.GetPortNames();
        // foreach(string port in ports){
        //     // Debug.Log(port);
        // }

        while (isRunning_ && serialPort_ != null && serialPort_.IsOpen) {
            try {
                serialPort_.Read(buffer, 0, buffer.Length);
                isNewMessageReceived_ = true;
            } catch (System.Exception e) {
                Debug.LogWarning(e.Message);
            }

            for(int i=0;i<buffer.Length;i++){
                if(buffer[i]==0x55 && buffer[i+1]==0x53){
                    int start=i+2;
                    this.roll  = (buffer[start+1]*Math.Pow(2,8)+buffer[start+0])/32768.0*180;
                    this.pitch = (buffer[start+3]*Math.Pow(2,8)+buffer[start+2])/32768.0*180;
                    this.yaw   = (buffer[start+5]*Math.Pow(2,8)+buffer[start+4])/32768.0*180;
                    
                    // センサ値を[-180,180]に変換する
                    if(this.roll > 180){
                        this.roll -= 360;
                    }
                    // センサ値を[0→90→0],[0→-90→0]に変換する
                    if (this.pitch > 180) {
                        this.pitch -= 360;
                    }
                    // センサ値を[-180,180]に変換する
                    // if (yaw > 180) {
                    //     yaw -= 360;
                    // }

                    if(preRoll*this.roll <= 0){
                        if(Mathf.Abs((float)this.roll) > 150){
                            if(preRoll < 0){
                                minus++;
                            }else if(preRoll > 0){
                                plus++;
                            }
                        }
                        preRoll=this.roll;
                    }
                    this.roll += (plus-minus)*360;

                    // Check
                    // Debug.Log($"roll:{roll}");
                    // Debug.Log($"pitch:{pitch}");
                    // Debug.Log($"yaw:{yaw}");
                }
            }
        }
    }

    public void Write(string message)
    {
        try {
            serialPort_.Write(message);
        } catch (System.Exception e) {
            Debug.LogWarning(e.Message);
        }
    }
}

 

設定

Unity上でAssets内にあるSerialHanderをドラッグしHierarchyにあるSerialHanderの上でドロップし,スクリプトをアタッチします.

次に,Rotateも同様にMain Cameraにスクリプトをアタッチします.

その後,MainCameraをクリックし右側にあるInspectorタブ内のSerialHandlerの右のボックスにHierarchy内のSerialHanderオブジェクトをドラッグ&ドロップします.

 

※重要!

最後にUnity左上のEditタブ→Project Settings...→Player→Configuration→Api Compatibility Level*の部分を.NET standard 2.0から.NET 4.xに変更してください.

 

以上で終わりです!

画面上部中央の再生ボタンをクリックしてみてちゃんとセンサとUnity上のカメラが同期しているか確認してみてください.

 

追記(2023.9.22)

コード改良版は以下.

challenge-think.hatenablog.com

Witmotionの挙動をUnityで確認する

今回は以下のサイトのようにジャイロセンサ(Witmotion)の挙動をUnityで確認したかったのですが,ダウンロードするzipファイルの内容が更新されていたので新たに記事にしようと思います.

tekuteku-embedded.xyz

 

ダウンロードするべきzipファイルは同じです.

公式ページ下部にあるSoftwareのPC Softwareをクリックするとグーグルドライブが開きます.

www.wit-motion.com

以下のドライブの中のWitMotion New Software.zipをダウンロード→解凍

WitMotion.exeをクリックすると,以下画像のようなアプリが起動します.

drive.google.com

PC Software

ここに自分のデバイスを追加しないといけません.

左部分のport,Baudに適切な値を入れてAddボタンをクリックすると右画面でセンサ値を確認することができるようになります.

 

ここで,画面上部の3D poseタブをクリックするとUnityが開きかくにんすることができるようになります.

マルチディスプレイにした際に接続が断続的になる

マルチディスプレイにした際に,通信がキャパを超えてしまったのか,暗くなったり明るくなったりを繰り返してしまう現象が起こりました.

 

原因としては,USBハブにディスプレイや外部センサなど様々なものをつないでいたせいで,一つのUSBポートから出力可能な電力をオーバーしてしまっていたことでした.

 

解決策としては,USB2.0ではなく,USB3.0のポートに繋ぎ変えるか,ディスプレイなどある程度電圧が必要な外部アタッチメントはUSBハブを介さず直接USBポートに接続する等方法が考えられます.

別ブラウザを開き,カメラを起動させる

別ブラウザを開く部分に関しては,以下を参考ください

challenge-think.hatenablog.com

 

htmlに関しては,上の記事と似たものを使用します.


<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>サンプル</title>
</head>
<body>

  <p>サンプルページです。</p>
    <!-- video part -->
    <div id="videoBox">
      <video id="videoMain"></video>
      <div id="captionMain">支援者</div>
    </div>
    <div id="cameraIcon">
      <input type="image" id="UserCamIcon">
    </div>
</body>

次に,後々変更が加えやすいように,一番最初に実行されるファイルにすべて記述するのではなく,カメラに関する関数類は別ファイルに記述することにします.

以下index.tsです.


import { CameraManager }   from "./ts/Manager/Modules/CameraManager";
// 接続されているカメラを取得する
const CamMG =  await CameraManager.setup();

次に,CameraManager.tsです.


export const UserCamIcon: HTMLElement       = document.getElementById("UserCamIcon")  as HTMLElement;
export const videoMain  : HTMLMediaElement  = document.getElementById("videoMain") as HTMLMediaElement;
export const videoBox   : HTMLElement       = document.getElementById("videoBox")  as HTMLElement;

export class CameraManager{
	public  CameraLists  : any;
	private IntegratedCam: any;
	// private WebCam       : any;

	public static async setup(): Promise<cameramanager>{
		const CamMG = new CameraManager();
		CamMG.CameraLists = await this.getCameraInfo();
		CamMG.detectCam();
		CamMG.CameraStart();
		return CamMG;
	}

	// 使用できるカメラ情報を取得する
	private static async getCameraInfo(){
		try {
			const CamLists = (await navigator.mediaDevices.enumerateDevices()).filter(device => device.kind === "videoinput");
			// console.log(CamLists);
			return CamLists;
		} catch (error){
			console.log(error);
			return null;
		}
	}

	private detectCam(){
		this.CameraLists.forEach((Cam) => {
			if(Cam.label.includes('Integrated Camera')){
				this.IntegratedCam = Cam;
			}else if(Cam.label.includes('Webcam')){
				// this.WebCam        = Cam;
			}
		});
	}

	// カメラを起動する
	private CameraStart(){
		// 内蔵カメラとウェブカメラを起動する
		navigator.mediaDevices.getUserMedia({
			video: {
				deviceId: this.IntegratedCam.deviceId,
			},
			audio: false,
		}).then(stream => {
			videoMain.srcObject       = stream;
			videoMain.style.transform = "scaleX(-1)";
			videoMain.play()
		}).catch(error => {
			console.log("Integrated Camera Error:"+ error);
			return;
		})

		// ユーザカメラのアイコンクリック時の処理
		UserCamIcon.addEventListener('click', () => { // width=300,height=300,left=0,top=0
			let option = 'width=1000,height=1000,left=0,top=0'
			open('./src/html/userCam.html','mywindow', option);
		});
	}

	// ビデオを表示する
	public CamShow(){
		// videoを表示する
		videoBox.style.display = "block";
	}
}

最後に表示させるhtmlファイルを設定します.


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>ユーザカメラ</title>
    <script src="../ts/Modules/CameraManager.ts"></script>
    <link rel="stylesheet" href="../css/style(UserCam).css">
    <!-- bootstrap -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body style="margin: 5%; background-color: rgb(202, 202, 202);">
    <div class="row">
        <div class="col">
            <div class="backgroundCaputure">
                <h3 style="background-color: wheat;">ユーザが見ている画面</h3>
                <p>
                    <button id="start">表示画面の選択する</button>
                    <button id="stop">画面表示を終了する</button>
                </p>
                <video id="video" autoplay width="800" height="400" style="object-fit: contain;"></video>
                <br>
            </div>
        </div>
        <div class="col" style="margin: 2%;">
            <div class="backgroundVideo">
                <div id="videoBox1">
                    <video id="videoSub"></video>
                    <div id="captionSub">ユーザ</div>
                </div>
                <div id="control">
                    <button class="recordStart" onClick="beginRecorde()">録画開始</button>
                    <button class="recordEnd">録画終了</button>
                    <div id="stateBox">
                        状態:&tl;a id="state">カメラが見つかりません</a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
<script>
    const videoSub = document.getElementById("videoSub");
    const myPlayer = document.getElementById('myPlayer');
    const state    = document.getElementById('state');

    // カメラ起動
    state.innerText = "カメラ起動中";
    navigator.mediaDevices.getUserMedia({
        video: {
            deviceId: "7ac074b780a5ceccfa6ac177ffcf014a24a684b67a1dbe9fe22388f53994b250",
        },
        audio: false,
    }).then(stream => {
        videoSub.srcObject       = stream;
        videoSub.style.transform = "scaleX(-1)";
        videoSub.play();
    }).catch(error => {
        console.log("Web Camera Error:"+ error);
        return;
    })

    //========== Recorder ==========//
    var recorder = null;
    var chunks = [];
    function beginRecorde(){
        if(!videoSub.srcObject) return;
        if(recorder) return;
        
        recorder = new MediaRecorder(videoSub.srcObject, {
            audioBitsPerSecond : 64000,
            videoBitsPerSecond : 512000,
            mimeType : 'video/webm; codecs=vp9'
        });
        chunks = [];
        state.innerText = "録画開始";
        recorder.ondataavailable = function(e){
            chunks.push(e.data);
        };
        recorder.onstop = function(e){
            recorder = null;
            startDownload();
        };
        recorder.start(1000);
    };
    
    function endRecorde(){
        if(recorder) recorder.stop();
    };
    
    //========== Downloader ==========//
    var blobUrl = null;
    function startDownload(){
        const recodeDate = getDate();
        if(!blobUrl){
            window.URL.revokeObjectURL(blobUrl);
            blobUrl = null;
        };
        var videoBlob = new Blob(chunks, { type : "video/webm" });
        console.log(videoBlob);
        blobUrl = window.URL.createObjectURL(videoBlob);
        console.log(blobUrl);
        // ビデオをダウンロードする(Blob→mp4)
        /* dawnload先を変更する */
        if(blobUrl){
            const a = document.createElement("a");
            document.body.appendChild(a);
            state.innerText = 'ダウンロードします';
            a.download = 'ユーザ表情録画'+recodeDate+'.mp4';
            a.href = blobUrl;
            a.click();
            a.remove();
            URL.revokeObjectURL(blobUrl);
            state.innerText = 'ダウンロード完了';
        };
    };

    /* 日付を取得する関数 */
    function getDate(){
        const now = new Date();
        const year = now.getFullYear();
        const month = now.getMonth();
        const date = now.getDate();
        const hour = now.getHours();
        const minute = now.getMinutes();

        const recodeDate = year+'/'+month+'/'+date+'/'+hour+'/'+minute;

        return recodeDate;
    }
</script>