2019年12月26日木曜日

ラズパイ(IOT・28)

続きまして、PHPをインストールします。
普段よく使うモジュールごと一度に選択します。
 sudo apt-get install php7.3 php7.3-fpm php7.3-mysql php7.3-mbstring php7.3-xml php7.3-gd php7.3-curl

バージョンを確認します。
 php -v

あっさり入りました。
そして、厄介なのがNginXと連携。
phpのページにアクセスした場合、スクリプトが実行できるように
調整します。
まず、設定ファイルを調整し
WebサーバーからPHPを使えるようにします。

開いたコンフィグで以下となるようにします。
  index index.html index.htm index.nginx-debian.html index.php;
  location ~ \.php$ {
          include snippets/fastcgi-php.conf;
          fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
  }

この後、NginXを再起動します。
CentOSですと、php-fpmの調整も必要なのですが、
ラズパイ版は調整済みなのですぐに動作可能となります。

2019年12月25日水曜日

ラズパイ(IOT・27)

今回はNginXをインストールしてみます。
NginXは軽量ながら、
HTTP, HTTPS, SMTP, POP3, IMAPのリバースプロキシ、ロードバランサ、HTTPキャッシュ
を使え大量のアクセスにも耐える高性能なWebサーバーです。

さてさて、ラズパイでのインストールは以下となります。
sudo apt-get install nginx

お手軽に入りますね。
ただ、CentOSにはいるものとは違い、
初期設定も大まかできているものが入ります。

インストール後、バージョン確認して
nginx -v

スタートします。
sudo /etc/init.d/nginx start

ドキュメントの場所は、
/var/www/html

になってます。
面倒なので、設置場所の権限を変えておきます。
sudo chown -R pi:root /var/www/html

2019年12月24日火曜日

ラズパイ(IOT・26)

前回の引き続きで、MariaDBのインストール後は、
DBの初期設定を行います。
まず、DBにログインしたいところですが、
rootのパスワードが分からない状態です。
以前のMySQLとかでは自動設定されたパスワードを
ログから採取したりもしていましたが、
今は、コマンドから初期設定ができるから便利ですね。
$ mysql_secure_installation

このコマンドからrootのパスワードの変更が
できるようになってます。
ここで、設定したパスワードでログインします。
sudo mysql -u root -p

現在のMySQLはPHPやPythonなどとつなぐ
モジュールのパスワードの形式がこれまでと違い
やり取りするために、mysql_native_password
形式を使う必要がありますが、今のところ必要なさそうです。

2019年12月23日月曜日

ラズパイ(IOT・25)

ラズパイだってWebサーバーにしたい!
ということで、コンパクトに高性能な各種サーバー向けソフトを
インストールしてみます。

まずは、データベースを入れます。
データベースには、一般的なサーバーにも使われる
大容量データも高速に動作するMySQLをインストールします。

はじめに、MySQLの最新のバージョンをホームページから確認します。
https://dev.mysql.com/downloads/repo/apt/
MySQLのAPT Repository を選択しました。

次に、パッケージを取得します。
wget https://repo.mysql.com/mysql-apt-config_0.8.14-1_all.deb
sudo dpkg -i mysql-apt-config_0.8.14-1_all.deb
sudo apt update
で、通常ですと
sudo apt install mysql-server mysql-client
でインストールできるのですが、MySQLから派生したMariaDBが
標準になって以降、こちらが優先になってます。
sudo apt install mariadb-client-10.0 mariadb-server-10.0
でインストールします。

2019年12月20日金曜日

ラズパイ(IOT・24)

アンプの動作テストとして、pythonからmp3の再生(IOT・19)で
作った方法を試してみます。
コマンドを実行してみると、
サウンドの再生が確認できました。



https://www.filetalk.info/index.html

2019年12月19日木曜日

ラズパイ(IOT・23)

前回、アンプ基板との配線ができましたので、
ラズパイへ接続してみます。
今回、アンプ自体は単体で動いているので
あとは、電源とミニプラグを接続するだけで
完了します。



簡単ですね~。

https://www.filetalk.info/index.html

2019年12月18日水曜日

ラズパイ(IOT・22)

前回集めたパーツを組み合わせ行きたいと思います。
最終的には、はんだ付けして固定しますが、
配線の具合や基板の動作確認もしたいので、
ブレッドボード上にジャンパワイヤーを取り付けて
仮配線をしてみます。
ブレッドボードの他に、ジャンパワイヤーも
オスオス・オスメス・メスメスがあると便利ですね~



配線するとこんな感じです。


このアンプ基板は、シングルエンドの接続も可能ですが、
別電源を利用したいと思いますので、差動入力のタイプで
配線します。(こっちのほうがシンプルですね)

https://www.filetalk.info/index.html

2019年12月16日月曜日

ラズパイ(IOT・21)

前回、サウンドの出力をヘッドホン端子から
流せるように調整を行いました。
ただ、ヘッドホン端子から出力できる音の
大きさはせいぜいヘッドホンから聞く分ほどの
音量しか出すことはできません。
スマートスピーカーの用途としては、
力不足な感じです。
そこで、今回は、以下のアンプを接続して
スピーカーを追加し、別電源から電力を
供給して音を鳴らせるようにしてみます。
使うのは、こんなやつです。

ミニスピーカー




アンプ



ミニステレオプラグ



2019年12月13日金曜日

ラズパイ(IOT・20)

前回、ラズパイからMP3の再生を行いました。
ただ、サウンドの出力先は、HDMIで接続している
モニターからの音でした。
モニタのスピーカーを利用するのでは、
持ち回りが不便なので、ヘッドホンジャックから
出力できるようにしたいと思います。

(ただ、ヘッドホンジャックに、ヘッドホンを差し込むと
そのまま、音声が出てくるかと思いきや、
すんなりと行かず、設定から変更しないとダメなんですね)

ということで、以下から調整です。

sudo raspi-config

そして、

「7 Advanced Options」
   ↓
「A4 Audio」

で、Force 3.5mm('headphone')jack
を選択します。


これで、こちらが優先して出力されるようになります。


2019年12月12日木曜日

ラズパイ(IOT・19)

今回からは、サウンド回りを確認してみます。
最近では、スマートスピーカーなどが手頃になり、
簡単に使える環境の構築が、誰でもできる時代になりました。
そこで、ラズパイをつかってスマートスピーカーや
遠隔通話などの端末として機能できるように
拡張してみます。

まず、ラズパイから音声の再生を試してみます。
再生するmp3ファイルを用意し、以下のコードで実行。

import pygame.mixer
import time

pygame.mixer.init()
pygame.mixer.music.load("sample.mp3")
pygame.mixer.music.play(-1)

time.sleep(60)
pygame.mixer.music.stop()


ファイルからの再生成功です。

https://www.filetalk.info/index.html

2019年12月11日水曜日

ラズパイ(IOT・18)

お次は、前回の行ったモバイルバッテリーでの
稼働テストと同じ条件をラズパイ4で行ってみたいと思います。
必要な電力が上昇していますので、
さて、何時間くらい連続稼働できるでしょうか~


条件は前回と同じで
簡単なPythonプログラムを
動かし続けたのですが、モバイルバッテリーの
25%ランプが消えるまでの所要時間です。

同じように、動作確認してみたところ
こちらは、4時間動作することができました。
ラズパイ3に比べ、概ね6割ほどの駆動時間になりました。
処理速度を重視しないなら、ラズパイ3の方が稼働時間延ばせますね。


2019年12月10日火曜日

ラズパイ(IOT・17)

ゆくゆくはモーターを付けて自走できるように
軽量そうなモバイルバッテリーを買ってみました。
実際のところ、このバッテリーを使うと
どれくらいの時間運用できるのかを見てみたいと思います。

詳細は以下となります。
maxellの5200mAhのモバイルバッテリー
サイズ:63.5×98.0×14.6mm(タバコ箱の半分くらい)
重さ:127g
出力:3A(2つのUSB端子の合計)

まずは、ラズパイ3に付けて稼働時間を見てみました。
朝から、テスト稼働として簡単なPythonプログラムを
動かし続けたのですが、モバイルバッテリーの
25%ランプが消えるまでに、6時間ジャストでした。
ですので、簡単な動作とかですと、この構成で
丸一日の稼働が可能そうな感じです。

https://www.filetalk.info/index.html

2019年12月9日月曜日

ラズパイ(IOT・16)

さて、ラスパイ3と4の温度の状況がどのくらい
違うのかを比較してみたいと思います。

お互いヒートシンクを付けた状態のものを
同じ時間程度起動させてみたところで、
温度状況を確認してみます。

ラズパイ4の方が、使用電力が大きいのですが、
通常の軽い動作しかさせていない状態ですと、
ラズパイ4のCPUは5℃ほど低く
逆に温度は低めなようですね。
意外にも、アイドル時はエコなのかもです。
https://www.filetalk.info/index.html

2019年12月6日金曜日

ラズパイ(IOT・15)

メインメモリーもラズパイ3の1GBから、ラズパイ4の
メモリー4GB版へと進化したので、
SDカード上に作成されるスワップファイルの利用をやめ、
OSの作業ファイルをメモリー上に移動するようにしたいと思います。
まず、スワップファイルの利用停止のコマンドを実行します。

sudo swapoff --all
sudo apt-get remove dphys-swapfile
次に、メモリー上へ作業領域を移動するようにします。

sudo vi /etc/fstab
以下の行を追加

tmpfs /tmp tmpfs defaults,size=256m,noatime,mode=1777 0 0
tmpfs /var/tmp tmpfs defaults,size=128m,noatime,mode=1777 0 0
最後に、free と df -h を実行して確認してみます。


すっきりとなりました。

https://www.filetalk.info/index.html

2019年12月4日水曜日

セキュリティー対策(13)

不用意に開かれたネットワークのポートは、
セキュリティ対策の上で穴となることがあるので、
ポートの状態を確認しておくことは重要ですね。
そこで、ラズパイでネットワークに使用されているポートの状況を
調べてみます。
調査には、ポートスキャンツールのNmapを使ってみます。
このツールは高性能で高速に実行できる定番ツールですね。
まず、インストールは以下となります。

sudo apt-get install nmap


次に、このラズパイで開かれているポートをスキャンしてみます。

nmap localhost


実行すると次のようになりました。

22番ポートをSSHが利用していることが分かりました。
他のポートは、未使用なことが再確認できました。

https://www.filetalk.info/index.html

2019年12月3日火曜日

ラズパイ(IOT・14)

さて次は、ラズパイ4へヒートシンクを取り付けてみます。
温度の状況はどのくらい変わるのか確認してみます。
まず、通常起動した後、そこそこ動かしたあとの温度は
こんな感じです。

次に、前回の温度の具合から4か所へヒートシンクを
取り付けるとこんな感じなりました。
(前の記事で、購入したヒートシンクの数が3つ
と書いてましたが、よく見ると4つ入ってました)
ラズパイ3と違い4は、基盤の表面だけの取り付けで
よくなりました。

そして、同じようにそこそこ動かしたあとの
温度は以下のようになりました。

やはり、ラズパイ3に引き続き、4の方も
ヒートシンクの効果はあるようですね。


https://www.filetalk.info/index.html

2019年12月2日月曜日

ラズパイ(IOT・13)

ラズパイ4のアルミケースの検証をしてみます。
アルミ製のケースなのでプラスチックのと比べれば、
放熱性は良さそうですが、プラスチックのケースを付けて
試したときは、ケースを付けた方がCPUの温度が3℃ほど
上昇していましたので、気になるところですね。
温度の計測には、前回紹介した非接触温度計を利用してます。
https://pillon-pg.blogspot.com/2019/11/iot_27.html

さっそく稼働時のそれぞれの温度を測っていきたいと思います。
まずは、ケースを付けない場合(下はつけて、上蓋を付けない場合)

通常使用で30分程度動作後の各場所の温度は次のようになりました。

それぞれの要素で、温度はだいぶ異なりますね~。
ラズパイ4用のヒートシンクがあるのですが、
同梱されていたのは3種類でした。CPUには付けるとして、なんとなく
どこに付ければいいか不安でしたが、
これを熱が多く出されている部分に、取り付ける指標となりました。


https://www.filetalk.info/index.html

2019年11月29日金曜日

ラズパイ(IOT・12)

前回購入したラズパイ4用のアルミケースを取り付けてみようと思います。
このケースのセットには、ケースのほかにねじとドライバー。そして、
冷却用のファンと、ファンを取り付けたねじ穴を隠すプレートと滑り止めも
ついていていい感じな内容になってます。
また、ファンを取り付けるためのケーブルをどこに刺すのかも、同梱してあるマニュアルに
絵付きで載っているので、初心者にも安心して取り付けることができます。

このファンを刺すピンは、ラズパイのGPIOポートに刺すのですが、
Interfacing Options のI2Cを有効にしなくても、普通に使えます。
あくまで制御するとき用ですね。

こんな感じになりました。
ロットによっては、ケースの蓋がはめにくいものがありましたが。
押し込めば大丈夫でした。
次回は、このケースを利用した温度の変化について検証してみます。



https://www.filetalk.info/index.html

2019年11月28日木曜日

ラズパイ(IOT・11)

ラズパイも3から4になり、HDMIの端子が2つになるなどの
関係でケースも互換性の無い新しいものへと変更
されています。
そこで、発熱量もおおいラズパイ4に合う、
ファン付きのアルミケースがあるので
取り付けてみようと思います。

見た目もかっこいいし、安っぽくないので
おすすめです。

ひとつ残念な点は、カメラ用のケーブル出す
すき間がないので、どうしても接続したい場合は、
横の蓋を付けない状態にする感じになります。(汗


https://www.filetalk.info/index.html

2019年11月27日水曜日

ラズパイ(IOT・10)

ラズパイ4のスペックアップは嬉しい限りですが、温度は?消費電力は?
そこで、まず温度を非接触で測れるこんなのが便利です。

先端を図りたい部分に向けてボタンを押すと、
温度が表示されます。

これを使って、チップの表面温度とかを計測して
いきたいと思います。
https://www.filetalk.info/index.html

2019年11月25日月曜日

ラズパイ(IOT・9)

待望のラズベリーパイの最新版である4が発売されて、
日本でも技適申請が通り、胸を張って使えるようになりました。
そこで、ラズパイ4を購入してみたので、色々確認してみようと思います。




まずは、スペック
Raspberry Pi 4 Model B
CPU:ARM Cortex-A72 1.5GHz
GPU:Broadcom VideoCore VI Dual Core 500MHz
メモリー:1GB, 2GB, 4GBの3種類


細かいことは、別のサイトに色々ありますので、
実際の使用感をお伝えしていこうと思います。
速度は、ラズパイ3から比べ、3倍高速とか言われていますが、
発熱を大きくなっていますので、
熱対策も必要かと思われます。

https://www.filetalk.info/index.html

2019年11月22日金曜日

ラズパイ(カメラ・29)

Three.jsを使った、RICOH R Development Kit の
360°動画ライブストリーミングぐりぐり操作の
ソースコードまとめ
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"/>
<STYLE type="text/css">
* {
    margin: 0;
    padding: 0;
    border: 0;
}

body, html {
    overflow: hidden;
    height: 100%;
}

#stage {
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.bt {
 position: absolute;
 color: #ffffff;
}
.bt1 {
 left: 0px;
 top: 0px;
}
.bt2 {
 left: 40px;
 top: 0px;
}
.bt a {
 display: block;
 position: absolute;
 padding: 3px;
 color: #ffffff;
}
.bt a:hover {
 box-shadow: 0 0 45px rgba(255,255,255, 0.8);
}
</STYLE>
<script type="text/javascript" src="./js/three.min.js"></script>
<script type="text/javascript" src="./js/StereoEffect.js"></script>
<script type="text/javascript" src="./js/DeviceOrientationControls.js"></script>
<script type="text/javascript" src="./js/OrbitControls.js"></script>
<script type="text/javascript" src="./js/jquery.min.js"></script>
<script type="text/javascript" src="./js/hls.min.js"></script>
<SCRIPT>
var fov = 75;
var element;
var scene, camera, renderer, controls;
var sphere1;
var material1;
var texture1;
var onMouseDownMouseX = 0, onMouseDownMouseY = 0,
    lon = 0, onMouseDownLon = 0,
    lat = 0, onMouseDownLat = 0,
    phi = 0, theta = 0;
var isStereo = false;
var isFullScreen = false;
var video = document.createElement('video');

window.onload = function () {
  init();
};

function init() {
  if (Hls.isSupported()) {
    var hls = new Hls();
    hls.loadSource('./playlist.m3u8');
    hls.attachMedia(video);
    hls.on(Hls.Events.MANIFEST_PARSED, function() {
      video.play();
    });
  } else {
    console.log('his err');
  }
  
  var width = window.innerWidth;
  var height = window.innerHeight;
  scene = new THREE.Scene(); // シーンの作成
  // リサイズイベントを検知してリサイズ処理を実行
  window.addEventListener('resize', handleResize, false);
  // カメラの作成
  camera = new THREE.PerspectiveCamera(fov, width / height, 1, 1000);
  camera.position.set(0, 0, 0);
  scene.add(camera);
  // 球体の形状を作成
  var geometry = new THREE.SphereGeometry(5, 60, 40);
  geometry.scale(-1, 1, 1);
  // マテリアルの作成
  material1 = initMaterial('', 1);
  // 球体(形状)にマテリアル(質感)を貼り付けて物体を作成
  sphere1 = new THREE.Mesh(geometry, material1);
  // シーンに追加
  scene.add(sphere1);
  // レンダラーの作成
  renderer = new THREE.WebGLRenderer();
  // レンダラーをwindowサイズに合わせる
  renderer.setSize(width, height);
  renderer.setClearColor({color: 0x000000});
  effect = new THREE.StereoEffect(renderer);
  element = renderer.domElement;
  document.getElementById('stage').appendChild(element);
  renderer.render(scene, camera);
  // デバイスの判別
  var isAndroid = false;
  var isIOS = false;
  if (navigator.userAgent.indexOf('Android') != -1) {
    isAndroid = true; // デバイスがAndroidの場合
  } else if (/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) {
  isIOS = true;  // デバイスがiOSの場合
  }
  if (isAndroid || isIOS) {
    // スマートフォンまたはタブレット端末の場合、ジャイロセンサーで視点操作を可能にする
    // ジャイロの取得には、SSLが必要(https://)
    window.addEventListener('deviceorientation', setOrientationControls, true);
  } else {
    setOrbitControls(); // パソコンの場合、マウスドラッグで視点操作を可能にする
  }
  
  render();
}

// リサイズ処理
function handleResize() {
  renderer.setSize(window.innerWidth, window.innerHeight);
  effect.setSize(window.innerWidth, window.innerHeight);
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
}

function getTexter(image) {
  return THREE.ImageUtils.loadTexture(image);
}

function initMaterial(image, opacity) {
  texture1 = new THREE.VideoTexture(video);
  texture1.minFilter = THREE.LinearFilter;
  material1 = new THREE.MeshBasicMaterial({ map: texture1 });
  return material1;
}


function onDocumentMouseDown(event) {
  event.preventDefault();
  if (event.clientX) {
    onMouseDownMouseX = event.clientX;
    onMouseDownMouseY = event.clientY;
  } else if (event.touches) {
    onMouseDownMouseX = event.touches[0].clientX
    onMouseDownMouseY = event.touches[0].clientY;
  } else {
    onMouseDownMouseX = event.changedTouches[0].clientX
    onMouseDownMouseY = event.changedTouches[0].clientY
  }
  onMouseDownLon = lon;
  onMouseDownLat = lat;
  if (window.ontouchstart === null) { //タッチ出来たら
    document.addEventListener('touchmove', onDocumentMouseMove, false);
    document.addEventListener('touchend', onDocumentMouseUp, false);
  } else {
    document.addEventListener('mousemove', onDocumentMouseMove, false);
    document.addEventListener('mouseup', onDocumentMouseUp, false);
  }
}

function onDocumentMouseMove( event ) {
  event.preventDefault();
  if (event.clientX) {
    var touchClientX = event.clientX;
    var touchClientY = event.clientY;
  } else if (event.touches) {
    var touchClientX = event.touches[0].clientX
    var touchClientY = event.touches[0].clientY;
  } else {
    var touchClientX = event.changedTouches[0].clientX
    var touchClientY = event.changedTouches[0].clientY
  }
  lon = (touchClientX - onMouseDownMouseX) * -0.1 + onMouseDownLon;
  lat = (touchClientY - onMouseDownMouseY) * -0.1 + onMouseDownLat;
}

function onDocumentMouseUp(event) {
  if (window.ontouchstart === null) { // タッチ出来たら
    document.removeEventListener('touchmove', onDocumentMouseMove, false);
    document.removeEventListener('touchend', onDocumentMouseUp, false);
  } else {
    document.removeEventListener('mousemove', onDocumentMouseMove, false);
    document.removeEventListener('mouseup', onDocumentMouseUp, false);
  }
}

// パソコン閲覧時マウスドラッグで視点操作する
function setOrbitControls() {
  controls = new THREE.OrbitControls(camera, element);
  controls.target.set(
    camera.position.x + 0.15,
    camera.position.y,
    camera.position.z
  );
  controls.enableDamping = true; // 視点操作のイージングをONにする
  controls.dampingFactor = 0.2;  // 視点操作のイージングの値
  controls.rotateSpeed = 0.1;  // 視点変更の速さ
  controls.noZoom = false;   // ズーム禁止
  controls.noPan = false;   // パン操作禁止
}

// ジャイロセンサーで視点操作する
function setOrientationControls(e) {
  if (!e.alpha) return; // スマートフォン以外で処理させない
  controls = new THREE.DeviceOrientationControls(camera, true);
  controls.connect();
  controls.update();
  window.removeEventListener("deviceorientation", setOrientationControls, true);
}

function render() {
  lat = Math.max(-90, Math.min(90, lat));
  phi = THREE.Math.degToRad(90 - lat);
  theta = THREE.Math.degToRad(lon);
  
  var angle = (theta * 360 / Math.PI);
  if (angle > 360) {
    var tmp = Math.floor(angle / 360);
    if (tmp > 0) angle = angle - (360 * tmp);
  } else if (angle < 0) {
    var tmp = Math.floor(angle / 360);
    if (tmp < 0) angle = angle + (360 * tmp * -1);
  }
  var deg = (theta * 360 / Math.PI) * -1;
  if (deg > 180) {
    var tmp = Math.floor(deg / 180);
    deg = (180 * tmp * -1) + deg - 180;
    if (tmp % 2 == 0) deg += 180;
  } else if (deg < -180) {
    var tmp = Math.floor(deg / 180) * -1;
    deg = deg + (180 * (tmp * -1) * -1);
    if (tmp % 2 > 0) deg -= 180;
  }
  
  $('#courseBox').css({
    transform: 'rotate(' + (deg * -1) + 'deg)'
  });
  $('#arrowBox').css({
    transform: 'rotate(' + (deg * -1) + 'deg)'
  });
  
  requestAnimationFrame(render);
  renderer.render(scene, camera);
  if (isStereo) {
    camera.fov = fov;
    effect.render(scene, camera);
  }
  
  controls.update();
}

function setStereoScreen() {
  isStereo = !isStereo;
  if (isStereo) {
    if (!isFullScreen) setFullScreen();
  } else
    setFullScreen();
}

function setFullScreen() {
  var fulltarget = document.getElementById('stage');
  if (!isFullScreen) {
    if (fulltarget.webkitRequestFullscreen) {
      fulltarget.webkitRequestFullscreen(); // Chrome15+, Safari5.1+, Opera15+
    } else if (fulltarget.mozRequestFullScreen) {
      fulltarget.mozRequestFullScreen(); // FF10+
    } else if (fulltarget.msRequestFullscreen) {
      fulltarget.msRequestFullscreen();  // IE11+
    } else if (fulltarget.requestFullscreen) {
      fulltarget.requestFullscreen();  // HTML5 Fullscreen API仕様
    } else {
      alert('ご利用のブラウザはフルスクリーン操作に対応していません');
      return;
    }
    $('#scrimg').attr('src', 'img/win.png');
    isFullScreen = true;
  } else {
    if (document.webkitCancelFullScreen) {
      document.webkitCancelFullScreen(); // Chrome15+, Safari5.1+, Opera15+
    } else if (document.mozCancelFullScreen) {
      document.mozCancelFullScreen();  // FF10+
    } else if (document.msExitFullscreen) {
      document.msExitFullscreen();   // IE11+
    } else if (document.cancelFullScreen) {
      document.cancelFullScreen();   // Gecko:FullScreenAPI仕様
    } else if (document.exitFullscreen) {
      document.exitFullscreen();   // HTML5 Fullscreen API仕様
    }
    $('#scrimg').attr('src', 'img/full.png');
    isFullScreen = false;
    if (isStereo) isStereo = false;
  }
}
</SCRIPT>
</head>

<body>
  <div id="stage">
    <div class="bt bt1"><a href="#" onclick="setStereoScreen();"><img src="img/vr.png" /></a></div>
    <div class="bt bt2"><a href="#" onclick="setFullScreen();"><img id="scrimg" src="img/full.png" /></a></div>
  </div>
</body>
</html>


https://www.filetalk.info/index.html

2019年11月21日木曜日

ラズパイ(カメラ・28)

続きまして、両眼対応の表示ができるようにしてみます。
画面を2つにわけて、それぞれを別の角度から見れるように
することで、VR表示ができるようにしてみます。
同じく、Three.js に機能を追加します。
  <script type="text/javascript" src="./js/StereoEffect.js"></script>

var isStereo = false;

  if (isStereo) {
    camera.fov = fov;
    effect.render(scene, camera);
  }

function setStereoScreen() {
  isStereo = !isStereo;
  if (isStereo) {
    if (!isFullScreen) setFullScreen();
  } else
    setFullScreen();
}


あとは、プラグインのJavaScriptがいいように調整してくれます。


https://www.filetalk.info/index.html

2019年11月20日水曜日

ラズパイ(カメラ・27)

前回、Three.jsをつかってグリグリ動かせるWebページの
ベースができましたので、おつぎは、スマホで表示
したときに、傾けた角度によって視点が
変わるようにジャイロに対応してみます。
変更する部分は、以下となります。
<script type="text/javascript" src="./js/DeviceOrientationControls.js"></script>

  if (isAndroid || isIOS) {
    // スマートフォンまたはタブレット端末の場合、ジャイロセンサーで視点操作を可能にする
    // ジャイロの取得には、SSLが必要(https://)
    window.addEventListener('deviceorientation', setOrientationControls, true);
  } else {
    setOrbitControls(); // パソコンの場合、マウスドラッグで視点操作を可能にする
  }

// ジャイロセンサーで視点操作する
function setOrientationControls(e) {
  if (!e.alpha) return; // スマートフォン以外で処理させない
  controls = new THREE.DeviceOrientationControls(camera, true);
  controls.connect();
  controls.update();
  window.removeEventListener("deviceorientation", setOrientationControls, true);
}


ただ、最近のセキュリティ管理の一環で、以前はこれで動いていましたが、
SSLが設置されているサイトでないと角度を取ることができなくなってしまっている
ようです(汗

https://www.filetalk.info/index.html

2019年11月18日月曜日

ラズパイ(カメラ・26)

360°の映像をブラウザから視点操作は、3Dの球体オブジェクトに
動画画像を貼り付けて、操作するイメージになります。
基本は、インストールなしで誰にでも見てもらえるような
コンテンツにしたいのでJavaScriptで作成された
ライブラリを使うことになります。
有名どころには、手軽に組み込める A-Frame https://aframe.io/
と、A-Frame のベースになっている Three.js https://threejs.org/
があります。
今回は、カスタマイズが色々可能である Three.js をベースに
作っていきたいと思います。

まず、全体の画面用のページを作成します。
  

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"/>
<STYLE type="text/css">
* {
    margin: 0;
    padding: 0;
    border: 0;
}

body, html {
    overflow: hidden;
    height: 100%;
}

#stage {
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.bt {
 position: absolute;
 color: #ffffff;
}
.bt1 {
 left: 0px;
 top: 0px;
}
.bt2 {
 left: 40px;
 top: 0px;
}
.bt a {
 display: block;
 position: absolute;
 padding: 3px;
 color: #ffffff;
}
.bt a:hover {
 box-shadow: 0 0 45px rgba(255,255,255, 0.8);
}
</STYLE>
<script type="text/javascript" src="./js/three.min.js"></script>
<script type="text/javascript" src="./js/OrbitControls.js"></script>
<script type="text/javascript" src="./js/jquery.min.js"></script>
<script type="text/javascript" src="./js/hls.min.js"></script>
<SCRIPT>
var fov = 75;
var element;
var scene, camera, renderer, controls;
var sphere1;
var material1;
var texture1;
var onMouseDownMouseX = 0, onMouseDownMouseY = 0,
    lon = 0, onMouseDownLon = 0,
    lat = 0, onMouseDownLat = 0,
    phi = 0, theta = 0;
var isFullScreen = false;
var video = document.createElement('video');

window.onload = function () {
  init();
};

function init() {
  if (Hls.isSupported()) {
    var hls = new Hls();
    hls.loadSource('./playlist.m3u8');
    hls.attachMedia(video);
    hls.on(Hls.Events.MANIFEST_PARSED, function() {
      video.play();
    });
  } else {
    console.log('his err');
  }
  
  var width = window.innerWidth;
  var height = window.innerHeight;
  scene = new THREE.Scene(); // シーンの作成
  // リサイズイベントを検知してリサイズ処理を実行
  window.addEventListener('resize', handleResize, false);
  // カメラの作成
  camera = new THREE.PerspectiveCamera(fov, width / height, 1, 1000);
  camera.position.set(0, 0, 0);
  scene.add(camera);
  // 球体の形状を作成
  var geometry = new THREE.SphereGeometry(5, 60, 40);
  geometry.scale(-1, 1, 1);
  // マテリアルの作成
  material1 = initMaterial('', 1);
  // 球体(形状)にマテリアル(質感)を貼り付けて物体を作成
  sphere1 = new THREE.Mesh(geometry, material1);
  // シーンに追加
  scene.add(sphere1);
  // レンダラーの作成
  renderer = new THREE.WebGLRenderer();
  // レンダラーをwindowサイズに合わせる
  renderer.setSize(width, height);
  renderer.setClearColor({color: 0x000000});
  effect = new THREE.StereoEffect(renderer);
  element = renderer.domElement;
  document.getElementById('stage').appendChild(element);
  renderer.render(scene, camera);
  setOrbitControls(); // パソコンの場合、マウスドラッグで視点操作を可能にする
      
  render();
}

// リサイズ処理
function handleResize() {
  renderer.setSize(window.innerWidth, window.innerHeight);
  effect.setSize(window.innerWidth, window.innerHeight);
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
}

function getTexter(image) {
  return THREE.ImageUtils.loadTexture(image);
}

function initMaterial(image, opacity) {
  texture1 = new THREE.VideoTexture(video);
  texture1.minFilter = THREE.LinearFilter;
  material1 = new THREE.MeshBasicMaterial({ map: texture1 });
  return material1;
}


function onDocumentMouseDown(event) {
  event.preventDefault();
  if (event.clientX) {
    onMouseDownMouseX = event.clientX;
    onMouseDownMouseY = event.clientY;
  } else if (event.touches) {
    onMouseDownMouseX = event.touches[0].clientX
    onMouseDownMouseY = event.touches[0].clientY;
  } else {
    onMouseDownMouseX = event.changedTouches[0].clientX
    onMouseDownMouseY = event.changedTouches[0].clientY
  }
  onMouseDownLon = lon;
  onMouseDownLat = lat;
  if (window.ontouchstart === null) { //タッチ出来たら
    document.addEventListener('touchmove', onDocumentMouseMove, false);
    document.addEventListener('touchend', onDocumentMouseUp, false);
  } else {
    document.addEventListener('mousemove', onDocumentMouseMove, false);
    document.addEventListener('mouseup', onDocumentMouseUp, false);
  }
}

function onDocumentMouseMove( event ) {
  event.preventDefault();
  if (event.clientX) {
    var touchClientX = event.clientX;
    var touchClientY = event.clientY;
  } else if (event.touches) {
    var touchClientX = event.touches[0].clientX
    var touchClientY = event.touches[0].clientY;
  } else {
    var touchClientX = event.changedTouches[0].clientX
    var touchClientY = event.changedTouches[0].clientY
  }
  lon = (touchClientX - onMouseDownMouseX) * -0.1 + onMouseDownLon;
  lat = (touchClientY - onMouseDownMouseY) * -0.1 + onMouseDownLat;
}

function onDocumentMouseUp(event) {
  if (window.ontouchstart === null) { // タッチ出来たら
    document.removeEventListener('touchmove', onDocumentMouseMove, false);
    document.removeEventListener('touchend', onDocumentMouseUp, false);
  } else {
    document.removeEventListener('mousemove', onDocumentMouseMove, false);
    document.removeEventListener('mouseup', onDocumentMouseUp, false);
  }
}

// パソコン閲覧時マウスドラッグで視点操作する
function setOrbitControls() {
  controls = new THREE.OrbitControls(camera, element);
  controls.target.set(
    camera.position.x + 0.15,
    camera.position.y,
    camera.position.z
  );
  controls.enableDamping = true; // 視点操作のイージングをONにする
  controls.dampingFactor = 0.2;  // 視点操作のイージングの値
  controls.rotateSpeed = 0.1;  // 視点変更の速さ
  controls.noZoom = false;   // ズーム禁止
  controls.noPan = false;   // パン操作禁止
}

function render() {
  lat = Math.max(-90, Math.min(90, lat));
  phi = THREE.Math.degToRad(90 - lat);
  theta = THREE.Math.degToRad(lon);
  
  var angle = (theta * 360 / Math.PI);
  if (angle > 360) {
    var tmp = Math.floor(angle / 360);
    if (tmp > 0) angle = angle - (360 * tmp);
  } else if (angle < 0) {
    var tmp = Math.floor(angle / 360);
    if (tmp < 0) angle = angle + (360 * tmp * -1);
  }
  var deg = (theta * 360 / Math.PI) * -1;
  if (deg > 180) {
    var tmp = Math.floor(deg / 180);
    deg = (180 * tmp * -1) + deg - 180;
    if (tmp % 2 == 0) deg += 180;
  } else if (deg < -180) {
    var tmp = Math.floor(deg / 180) * -1;
    deg = deg + (180 * (tmp * -1) * -1);
    if (tmp % 2 > 0) deg -= 180;
  }
  
  $('#courseBox').css({
    transform: 'rotate(' + (deg * -1) + 'deg)'
  });
  $('#arrowBox').css({
    transform: 'rotate(' + (deg * -1) + 'deg)'
  });
  
  requestAnimationFrame(render);
  renderer.render(scene, camera);
  
  controls.update();
}

function setFullScreen() {
  //var fulltarget = document.getElementsByClassName('fullsc')[0];
  var fulltarget = document.getElementById('stage');
  if (!isFullScreen) {
    if (fulltarget.webkitRequestFullscreen) {
      fulltarget.webkitRequestFullscreen(); // Chrome15+, Safari5.1+, Opera15+
    } else if (fulltarget.mozRequestFullScreen) {
      fulltarget.mozRequestFullScreen(); // FF10+
    } else if (fulltarget.msRequestFullscreen) {
      fulltarget.msRequestFullscreen();  // IE11+
    } else if (fulltarget.requestFullscreen) {
      fulltarget.requestFullscreen();  // HTML5 Fullscreen API仕様
    } else {
      alert('ご利用のブラウザはフルスクリーン操作に対応していません');
      return;
    }
    $('#scrimg').attr('src', 'img/win.png');
    isFullScreen = true;
  } else {
    if (document.webkitCancelFullScreen) {
      document.webkitCancelFullScreen(); // Chrome15+, Safari5.1+, Opera15+
    } else if (document.mozCancelFullScreen) {
      document.mozCancelFullScreen();  // FF10+
    } else if (document.msExitFullscreen) {
      document.msExitFullscreen();   // IE11+
    } else if (document.cancelFullScreen) {
      document.cancelFullScreen();   // Gecko:FullScreenAPI仕様
    } else if (document.exitFullscreen) {
      document.exitFullscreen();   // HTML5 Fullscreen API仕様
    }
    $('#scrimg').attr('src', 'img/full.png');
    isFullScreen = false;
  }
}
</SCRIPT>
</head>

<body>
  <div id="stage">
    <div class="bt bt2"><a href="#" onclick="setFullScreen();"><img id="scrimg" src="img/full.png" /></a></div>
  </div>
</body>
</html>



https://www.filetalk.info/index.html

2019年11月15日金曜日

ラズパイ(カメラ・25)

前回つづいて、RICOH R Development Kitをつかって
360°のライブ配信をラズパイからできるように、
Webサーバー用のプログラムを調整します。
import os
import http.server
import socketserver

PORT = 8000

#--------------------------------------------------------------------------
# 開始
#--------------------------------------------------------------------------
def run():
    web_dir = os.path.join(os.path.dirname(__file__), '/home/pi/Public')
    os.chdir(web_dir)
    
    Handler = http.server.SimpleHTTPRequestHandler
    httpd = socketserver.TCPServer(("", PORT), Handler)
    print("serving at port", PORT)
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    
    httpd.server_close()

if (__name__ == "__main__"):
    run()

これで配信する映像をホームページから
見えるように準備されました。次回は、
360°の映像をブラウザから視点操作が
できるようにページを作成します。

https://www.filetalk.info/index.html

2019年11月14日木曜日

ラズパイ(カメラ・24)

 さてさて、リコーの360°カメラが届いたので、
さっそく接続してビデオ動画を取得してみようと思います。

まず、ラズパイにUSBで接続し、どのポートに
接続されたのかを確認します。
$ v4l2-ctl --list-device



上記のコマンドからデバイス番号を確認し、
動画配信用のコマンドとして
前回使ったHLSのコードを変更します。
httpDir=~/Public
cd $httpDir

ffmpeg -f alsa -thread_queue_size 2048 \
  -i plughw:1,0 \
  -f v4l2 -thread_queue_size 1024 -input_format yuyv422 -video_size 1920x1080 \
  -i /dev/video1 \
  -filter_complex scale=1920x1080,fps=30 \
  -c:v h264_omx -b:v 2438k -g 24 \
  -c:a aac -b:a 64k \
  -flags +cgop+global_header \
  -f hls \
  -hls_time 4 -hls_list_size 3 -hls_allow_cache 0 \
  -hls_segment_filename $httpDir/stream/stream_%d.ts \
  -hls_base_url stream/ \
  -hls_flags delete_segments \
  $httpDir/playlist.m3u8

rm $httpDir/stream/stream_*.ts
rm $httpDir/playlist.m3u8


RICOH R Development Kitでは、解像度がフルHDなので
解像度などを調整します。

https://www.filetalk.info/index.html

2019年11月1日金曜日

ラズパイ(カメラ・23)

カメラにも色々種類がありますが、
近年のウェアラブルカメラをはじめインスタ360や
リコーのシータなどの360°カメラも手軽に買えるほどに
普及しています。
今回は、VR用にも使える360°カメラのTHETAシリーズの中で
唯一ストリーミング配信が24時間使用にも耐えうると
宣伝の RICOH R Development Kit を試してみます。

https://ricohr.ricoh/ja/
RICOH R Development Kit の特徴としましては、
・FullHD画質で、電源を付けたら即ストリーミングモード
・バッテリー搭載なしで、リチウムの寿命に気を付けなくてOK
・連続稼働OK
・リアルタイムスティッチングで画像の加工必要なし
という感じで、値段が通常のシータと比べて
お高めなことを除けば、他の機器から制御するために
利用するには最高に便利な一品です。


次回は、まず接続を試してみます。
https://www.filetalk.info/index.html

2019年10月31日木曜日

ラズパイ(カメラ・22)

前回は、WebRTCを利用した pistreaming を
確認しました。
動画の再生はスムーズに表示されてます。
ただ、難点を上げるとすると、音声データを
送信することを想定されてないので、
音はありません。

まとめると
HLSと比べると、HLSは音声も含めて
H264形式の動画で保存しながら
ぶつ切りのファイルを再生する感じなので、
映像+音声ですが、ファイルを作成するために、
10秒程度の遅延がありました。

pistreamingは、jpegを圧縮して一枚づつ
配信後、順次表示するパラパラ
アニメのような形式ですので、
遅延は1秒未満ですが、音声に
対応していない感じとなります。

次回は、別のカメラも試します。
https://www.filetalk.info/index.html

2019年10月29日火曜日

ラズパイ(カメラ・21)

ライブストリーミングの第二弾として、
pistreaming のインストールを行います。

 $ sudo pip3 install ws4py
 $ git clone https://github.com/waveform80/pistreaming.git

簡単にインストールができますね
あとは、以下のコマンドから実行可能です。

 $ cd pistreaming
 $ python3 server.py

そして、ブラウザから以下のようにラズパイのIPに
ポート指定すると表示することができます。
http://192.168.1.28:8082

お~。今度は、前回のHLSと違って
かなりリアルタイムな感じで表示できますね。
遅延の時間としましては、おおみね1秒に
満たない程度な感じです。
これならば、遠隔操作のような利用でも
使えそうな感じです。
https://www.filetalk.info/index.html