grbl1.1+Arduino CNCシールドV3.5+bCNCを使用中。
BluetoothモジュールおよびbCNCのPendant機能でスマホからもワイヤレス操作可能。
その他、電子工作・プログラミング、機械学習などもやっています。
MacとUbuntuを使用。

CNCマシン全般について:
国内レーザー加工機と中国製レーザー加工機の比較
中国製レーザーダイオードについて
CNCミリングマシンとCNCルーターマシンいろいろ
その他:
利用例や付加機能など:
CNCルーター関係:



*CNCマシンの制作記録は2016/04/10〜の投稿に書いてあります。


ラベル ワイヤレス の投稿を表示しています。 すべての投稿を表示
ラベル ワイヤレス の投稿を表示しています。 すべての投稿を表示

2017年11月10日金曜日

Google AIY Voice Kit(Pimoroni)到着

一ヶ月ほど前にPimoroniに注文していたGoogle AIY Voice Kitが到着(26.33ポンド/約4000円:送料込み)。いわゆる音声認識キットで、元々は雑誌MagPi(57号/2017年5月)の付録です。当初はあっという間に売り切れてしまい、その後Ebayなどでも1万円くらいの高値がついていました。以前、ESP8266とWeb Speech APIで音声認識スイッチをつくっていたときも入手困難となっており諦めていましたが、ちょっと前に再販したようで一ヶ月ほど待たなければいけない予約販売でしたがついつい買ってしまいました。
現在Pimoroniではもうすでに売り切れのようですが、日本のKSYでは11月末以降に入荷されるようです(3000円/税抜き)。

中身はこんな感じ。75ページもあるカラー小冊子マニュアル。Web上ではAIYのサイトであるここにマニュアルがあります。
1時間もあれば一通り試すことができそうですが、個人的には音声認識に対する興味はやや失せてしまっており、まだやる気が起こりません。あと、Googleを使わなければいけないというのも、やや面倒。

この丁寧に説明されているマニュアル(冊子)を見ると面白そうですが、最近ではGoogle Home mini(6480円)、Amazon Echo Dot(5980円)、Anker Eufy(4980円)という小型で安価なスマートスピーカーも登場してきたので、このAIY Voice Kitを買うまでもないかも。たしかに微妙なところ。しかし、いろいろ試したりするにはいいのかもしれません。製品よりはHackしやすいはず。

AIY Voice Kitの紹介動画。

基本的にはGoogle Cloudのアカウントをつくり、Google Assistant SDKを通して、ラズパイに書いたPythonスクリプトで動かすようです。以下のサイトにはサンプルなどがあるので、Pythonで任意のプログラムを書き換えればいろいろ応用はできそうです。
Googleにとっては、Google Cloudを使ってもらう切っ掛け(宣伝)としてはいいのかもしれません。
そのうち気が向いたら組み立ててみようと思います。

2017年7月31日月曜日

ワイヤレス充電器(USB:5V)

ワイヤレス充電器を購入してみました。あいかわらずAliExpressで数百円で売っていたので試してみたという感じです。

AliExpress.com Product - Ifavor Universal Qi Wireless Charging Kit Charger Pad Coil Receiver Charger Adapter For Samsung Xiaomi Android Phone Micro USB515円(送料無料)、受電(左)と送電(右:直径70mm)のセット
左側の受電用の薄っぺらいカードは厚さ1mm程度で、スマホケースを使えばスマホの裏側におさまります。ただ、MicroUSBの差し込む向き、あるいはタイプCやiPhone用のものなど、選択する必要があります。

AliExpress.com Product - Android Micro USB Universal Qi Wireless Charger Receiver Charging Adapter Receptor Receiver Pad Coil Chip For Samsung HUAWEI131円(送料無料)、受電用のカード(コイル)だけ。スマホ以外にも使えるかもしれないので、MicroUSBの向きが逆のタイプも購入してみました。

こんな感じで、受電用カード(右)は厚さ1mm程度なのでスマホケース内部に収まります。受電カード上のワイヤレスマーク(((●)))側が充電器(給電器)と向き合うように配置します。距離的には、8mmくらい離れていても給電できました。

受電用カードの裏には、5V/1000mAまでの出力と書いてあります。給電用の丸い充電器は5V/1500mAまで入力可能のようです。
Amazonでも購入できますが、やはり少し高い。

ESP8266への給電:実は、スマホ用充電器として購入したのではなく、以下のような使い方を想定していました。
ESP8266に接続して5Vをワイヤレスで給電。安定はしないかもしれませんがESP8266だと一応動くようです。


受電用カードとブレッドボードはほぼ同じサイズ。

間に電圧電流計をはさんで計測してみると、安定しているときでは0.11Aくらいでしたが、0.4〜0.25Aの間を上下していました。不安定なときは4.8Vに下がるなど、充電器の位置が多少ずれると電力が変動しているようでした。
Raspberry Pi Zero Wへの給電:試しにRaspberry Pi Zero Wも接続してみましたが、もっと電力が必要そうでダメでした。
たまに安定して起動するときもあるのですが、電圧が5.0V以下になることもあり不安定で途中で落ちてしまいます。もう少し強力な充電器が必要そうです。ちなみに、Raspberry Pi Zero Wの場合は、
18650(一本)のUSB電源であれば動きますが、右端の単3電池を2本使うタイプ(100均)のUSB電源だとやはり電力不足。ということから、18650の電源を使ったほうがよさそうですが問題はケーブルが邪魔。ケーブルを使わないで直接Raspberry Pi Zero Wに配線してしまったほうがコンパクトになりそうです。Raspberry Pi Zero Wの裏側に電源の端子があるのですが、そこに直接ケーブルをハンダつけするというよりは、以下のようなポゴピンコネクターを電気接点に使うとよさそうです。
AliExpress.com Product - 10pcs Spring Loaded Pogo Pin Connector Diameter 2.0 mm x 7.0 mm height SMT / SMD PCB brass Gold plate 1u 50g force Probe Pin505円(10個:送料無料)

ワイヤレス充電器の部品:再び、ワイヤレス充電器についてですが、以下のような5Vで2Aまで流せる部品もあります。これを使えば、Raspberry Pi Zero Wも起動するのかもしれません。
AliExpress.com Product - 5V2A Large current wireless charging module Wireless power supply module free shipping1332円(送料無料)ある程度容量があるので、これは何かに使えそうです。
AliExpress.com Product - Universal Charging Receiver Charger 3 Coils Qi Wireless Charger PCBA Circuit Board Coil Wireless Charging Micro USB Ap 25 472円(送料無料)、こちらは3個コイルがついているタイプで強力そうです。その分、横長になっています。ある程度安定して給電するには、これくらいのほうがよさそうです。
テーブルに給電器を埋め込んでおけば、ただそこに置くだけでちょっとした電化製品なら電源が入る仕組みになりそうです。ノートパソコンなどもそうなればいいとは思いますが、そこまで強力だと電磁波など問題にならないのでしょうか?

ワイヤレス充電器内蔵型家具:IKEAの家具の中には、ワイヤレス充電器を埋め込むためのキットが売っています。
IKEAワイヤレス充電ユニット:3000円直径9cm、5V/2000mAなので、スマホやタブレット程度なら充電が可能なのかもしれません。

このように既存のテーブルなどに穴を開けて埋め込むようです。

すでにワイヤレス充電器が埋め込まれているベッドサイドテーブル:6999円もあるようです。家具も電源が必要になってきたという感じです。

2017年7月26日水曜日

Raspberry Pi Zero W:IPカメラ実験(Picamera)

Raspberry Pi Zero Wでやりたかったことの一つにIPカメラがあります。Zero Wにカメラをつないで、他の端末からZero WのIPアドレスにアクセスして監視できるようにするものです。主には、レーザー加工時の監視用カメラ(直接目で確認するより安全なので)です。

カメラの種類:
カメラは以前AliExpressから以下のような赤外線カメラを購入しておりました。このタイプはカメラ両脇に赤外線LED照明がついており、それを照射して暗い部屋の中でもその赤外線反射光を頼りに撮影可能になるらしい。ただし、レンズには赤外線フィルターがついていないため、明るい状況下ではやや色味が実際とは異なってしまうと思います(やや赤紫っぽくなる)。

AliExpress.com Product - Raspberry Pi Camera RPI Focal Adjustable Night Version Camera + Acrylic Holder + IR Light + FFC Cable for Raspberry Pi 2 / 31677円(送料込み)、赤外線LED照明のついた暗視タイプ、5メガピクセル。
また通常のタイプのものであれば以下。こちらは赤外線フィルターがついているため色味は自然なほうかと。
AliExpress.com Product - Free Shipping raspberry pi camera 5mp pixels RASPBERRY PI CAMERA730円(送料込み)、5メガピクセル、これは比較的安価。でも国内で買うと3000円くらい。
どうやら5メガピクセルのカメラV1がV2へバージョンアップして8メガピクセルになったようで、以下。
AliExpress.com Product - Original RPI 3 Camera Raspberry pi Camera V2 Module Board 8MP Webcam Video 1080p 720p Official camera For Raspberry Pi 3 2206円(送料無料)、8メガピクセル。これは純正。こちらは国内価格が5000円くらい。
なぜかカメラが国内では高いのですが、試す程度なら730円のV1でもよさそうです。

カメラ用プログラム:カメラ撮影を可能にするプログラム/アプリケーションがいくつかあるようで、ざっと調べた限りでは、・Motion・MJPG-streamer・Picamera(python)があるようです。こちらの記事を参考にすると、Motionは遅延があるようでMJPG-streamerのほうがいいらしい。また、公式ドキュメンテーションを見ると、Picameraが標準インストールされているらしいので、すぐにコマンドを打てば使えるらしい。ということから、MJPG-streamerは後回しにして、まずはPicameraで軽く実験してみることに。
Picamera:公式ドキュメンテーションのカメラ使用については、RASPBERRY PI CAMERA MODULEというページ(英語)があります。さらに、Picameraのドキュメンテーションとしてはこちらのページに詳しく書かれています。
セッティングと動作チェック:Zero Wでカメラ使用を可能にするための設定として、以下の「Raspberry Piの設定」画面で「カメラ」を「有効」にチェックを入れておきます。そして一度再起動が必要らしいです。このあたりの設定については、ドキュメンテーションのGetting Startedに書いてあります。 

尚、パソコンからWifiを通してZero Wを遠隔操作しているため「VNC」も「有効」にしてあります。どうやら、その後「シリアル」も「有効」にしておいたほうがよさそうですが、いまのとこ無効のまま。Picameraを起動する前に、sudo apt-get updatesudo apt-get upgradeをしておいたほうがいいでしょう。数ヶ月使っていなかったため、10分以上アップグレードに時間がかかりました。
Zero Wを再起動後、ターミナルを開いて、
raspistill -o test.jpg

を入力すれば、test.jpgという名前のファイルで静止画を出力(保存)するようです。実際にやってみると、
このように、test.jpgが/home/piディレクトリ内に出力保存されています。これをクリックし写っていればカメラは問題なく機能しているということになります。

プレビュー画面:ビデオ映像をプレビューするには、raspivid -t 10000を入力すれば、10秒間プレビュー画面がでてくるようです。raspivid -t 0であれば、ctrl+cを押してプログラムを終了するまでずっとプレピューとなるようです。しかしながら、VNCを通してプレビューしようとするとプレビュー画面が現れません。これはVNCにプレビュー画面を転送表示する機能がないからのようです。その場合はでネットワーク表示させろと書いてあります

Webストリーミング:次に本題のWebストリーミングの方法に入っていきたいと思います。ちなみに、これらすべてはVNCを通してMacBook ProからWifi経由で操作しています。いろいろ探してみると、PicameraのドキュメンテーションのAdvanced Recipesに、そのまんま「4.10 Web Streaming」という項目があります。冒頭の説明で、Webを通してのビデオストリーミングは驚くほど複雑だと書いてあります。しかしながら、Pythonによる88行からなるサンプルソースが掲載されています。試しにコピペしてZero W上で確かめてみようと思います。


import io
import picamera
import logging
import socketserver
from threading import Condition
from http import server

PAGE="""\
<html>
<head>
<title>picamera MJPEG streaming demo</title>
</head>
<body>
<h1>PiCamera MJPEG Streaming Demo</h1>
<img height="480" src="stream.mjpg" width="640" />
</body>
</html>
"""

class StreamingOutput(object):
    def __init__(self):
        self.frame = None
        self.buffer = io.BytesIO()
        self.condition = Condition()

    def write(self, buf):
        if buf.startswith(b'\xff\xd8'):
            # New frame, copy the existing buffer's content and notify all
            # clients it's available
            self.buffer.truncate()
            with self.condition:
                self.frame = self.buffer.getvalue()
                self.condition.notify_all()
            self.buffer.seek(0)
        return self.buffer.write(buf)

class StreamingHandler(server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/':
            self.send_response(301)
            self.send_header('Location', '/index.html')
            self.end_headers()
        elif self.path == '/index.html':
            content = PAGE.encode('utf-8')
            self.send_response(200)
            self.send_header('Content-Type', 'text/html')
            self.send_header('Content-Length', len(content))
            self.end_headers()
            self.wfile.write(content)
        elif self.path == '/stream.mjpg':
            self.send_response(200)
            self.send_header('Age', 0)
            self.send_header('Cache-Control', 'no-cache, private')
            self.send_header('Pragma', 'no-cache')
            self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME')
            self.end_headers()
            try:
                while True:
                    with output.condition:
                        output.condition.wait()
                        frame = output.frame
                    self.wfile.write(b'--FRAME\r\n')
                    self.send_header('Content-Type', 'image/jpeg')
                    self.send_header('Content-Length', len(frame))
                    self.end_headers()
                    self.wfile.write(frame)
                    self.wfile.write(b'\r\n')
            except Exception as e:
                logging.warning(
                    'Removed streaming client %s: %s',
                    self.client_address, str(e))
        else:
            self.send_error(404)
            self.end_headers()

class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
    allow_reuse_address = True
    daemon_threads = True

with picamera.PiCamera(resolution='640x480', framerate=24) as camera:
    output = StreamingOutput()
    camera.start_recording(output, format='mjpeg')
    try:
        address = ('', 8000)
        server = StreamingServer(address, StreamingHandler)
        server.serve_forever()
    finally:
        camera.stop_recording()




とりあえず、デスクトップ上にテキストエディタでwebstream.pyなるファイルをつくって、そこにコピペです。どうやらこれは、Python3用のソースです(このままではPython2.7では動きません)。Zero WにPIXELをインストールしていれば、Python2.7も3も両方入っているので特に問題はありません。
保存したあと、ターミナルを開いて、まずはwebstream.pyのあるDesktopディレクトリへ移動するために、
cd Desktop
そして、Python3でこのスクリプトを起動するのですが、
python3 webstream.py
と打ち込みます。そうすると、特にモニタリング用の画面などは現れずに以下のような画面。
これは、Zero W上のターミナル画面です。サンプルが起動したら、Zero WのIPアドレス:8000/index.htmlに他の端末のブラウザでアクセスするとリアルタイムでの映像を見ることができます。


こんな感じで、193.168.3.2:8000/index.htmlにMacBook ProのChromeでアクセスしてみました。手を揺らしてみましたが、特に目立った遅延などなくスムースに動いています。コードを編集する必要もなく、すぐに動作確認することができました。画面上方の文字が邪魔ですが、コード11行目のHTMLを編集し直せばすぐに消しとることができます。
プログラム終了方法:ctrl+cを押すか、ターミナルを閉じてしまうと終了します。
Zero WのIPアドレス:
ちなみに、Zero WのIPアドレスは、画面右上のWifiアンテナマークにカーソルを重ねると出てきます。
またポート:8000に関しては、コード下から5行目に8000を指定してあるので、変更したい場合はその部分を任意のポートにすればいいと思います。


Python IDLEで起動:
ターミナルが苦手というのであれば、すでにPython 3 (IDLE)がインストールされているので、

コピペしたPythonスクリプトをデスクトップ上で右クリックして、
アプリケーションで開くを選択し、

Python 3(IDLE)を選択すれば、

先程のコードがIDLE上にでてきます。あとはRun Moduleすれば、このプログラムが起動します。
そして、別の端末のブラウザでIPアドレスにアクセスすれば撮影内容を見ることができます。

モバイルバッテリーにつなげばスタンドアロンのIPカメラとして使えるので便利そうです。バッテリーとUSBケーブルの間に電圧電流計をはさんでみましたが、カメラ起動中は約0.2〜0.3A程度の電流になっていました(以下)。
カメラ起動中は、赤いLEDがオンになります。

まとめ:
比較的簡単にWebストリーミングできました。このソースにHTMLの表示内容も書き込んであるので、見た目や配置を変更することも簡単にできそうです。
この実験のあと、Python2.7でも動かせるかどうか確かめてみました。Python3のsocketserverならびにserverモジュールが、python2.7ではSocketServer、SimpleHTTPServerに置き換わるのですが、そのために多少書き直す部分がでてくるので面倒。そのうちやろうと思います。
それから、かなりZero Wのチップが熱くなっていたので、ヒートシンクはつけたほうがよさそうです。

Posted at 2017.7.26
EasyWordMall アルミニウムヒートシンク Raspberry Pi用 6枚入り
Apple Trees E-commerce co., LT
販売価格 ¥198
(2017年7月26日4時48分時点の価格)

2017年3月24日金曜日

IoTその4:再度ESP8266+Blynk

もともとはCNCマシンのワイヤレス化(bCNCのPendant機能Bluetooth装備など)から始まったのですが、bCNCのカメラ機能をIPカメラでも撮影可能にしようとしたところ、
Pythonの再学習(bCNC自体がPythonで書かれているため)
・Wifiデバイス(ESP8266ESP32)の利用
ということになり、今まで使っていたArduino IDEだけではなく、
ESP-IDF
PlatformIOAtom Editor
もやってみようかと。
要はパソコンやスマホのブラウザからもいろんなものを操作可能にしたいということなので、
・HTML
・Javascript
も同時に学習していかなければいけない。やらなければいけないことが一気に増えてしまったという感じです。

その結果、ある程度はESP8266を使って、
・エアコンのオンオフ(赤外線信号)
・温度と湿度の読み込み
・照明器具のオンオフ(あるいは、CNCマシンのメイン電源のオンオフ)
などをスマホのChrome上からも操作できるようになりました。

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>

#define RelayPin 5
#define IRLedPin 15
#define DHTPin   2
#define LedPin   4

#define duty_high 8
#define duty_low 16
#define DHTTYPE DHT11

DHT_Unified dht(DHTPin, DHTTYPE);

uint32_t delayMS;
String temp;
String humid;

const char* ssid = "*****";
const char* password = "*****";

MDNSResponder mdns;
ESP8266WebServer server(5900);

String webPage = "";

unsigned long airOn[243] = {
 3400, 1400, 600, 250, 550, 250, 550, 1050, 550, 250, 550, 1050, 550, 250, 550, 250, 550, 250, 550, 1050, 550, 
 1050, 550, 250, 550, 300, 500, 250, 550, 1100, 500, 1100, 500, 300, 500, 300, 500, 300, 500, 300, 500, 300,
 500, 300, 500, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 400, 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350,
 450, 1150, 450, 1150, 450, 1150, 450, 1150, 450, 1150, 450, 1200, 450, 350, 450, 350, 450, 350, 450, 1150,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 1200, 450, 350,
 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 400,
 400, 1200, 400, 400, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 400,
 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400,
 400, 400, 400, 400, 400, 400, 400, 400, 450, 350, 450, 1150, 450, 1150, 450, 350, 450, 1200, 400, 400, 400,
 400, 400, 1200, 400, 400, 400};
unsigned long airOff[99] = {
 3400, 1450, 550, 250, 550, 250, 500, 1100, 550, 250, 550, 1050, 550, 250, 550, 250, 550, 250, 550, 1050, 550,
 1050, 550, 300, 500, 250, 550, 300, 500, 1100, 500, 1100, 500, 300, 500, 300, 500, 300, 500, 300, 500, 350,
 450, 300, 500, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 400, 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450};

void setup() {
  pinMode(IRLedPin, OUTPUT);
  pinMode(LedPin, OUTPUT);
  pinMode(RelayPin, OUTPUT);
  digitalWrite(LedPin, HIGH);
  digitalWrite(RelayPin, LOW);
  dht.begin();
  sensor_t sensor;
  dht.temperature().getSensor(&sensor);
  dht.humidity().getSensor(&sensor);
  temp_humid();
  webContents();
  
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  delay(1000);
  WiFi.begin(ssid, password);
  Serial.println("");
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }
  WiFi.config(IPAddress(192,168,3,10),IPAddress(192,168,3,1),IPAddress(255,255,255,0));
  
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  
  if (mdns.begin("mirror", WiFi.localIP())) {
    Serial.println("MDNS responder started");
  }

  server.on("/", []() {
    temp_humid();
    webContents();
    server.send(200, "text/html", webPage);
  });
  server.on("/index.html", []() {
    temp_humid();
    webContents();
    server.send(200, "text/html", webPage);
  });
  server.on("/on", []() {
    server.send(200, "text/html", webPage);
    digitalWrite(LedPin, LOW);
    digitalWrite(RelayPin, HIGH);
    delay(1000);
  });
  server.on("/off", []() {
    server.send(200, "text/html", webPage);
    digitalWrite(LedPin, HIGH);
    digitalWrite(RelayPin, LOW);
    delay(1000);
  });
  server.on("/airon", []() {
    temp_humid();
    webContents();
    server.send(200, "text/html", webPage);
    air_on();
    delay(1000);
  });

  server.on("/airoff", []() {
    temp_humid();
    webContents();
    server.send(200, "text/html", webPage);
    air_off();
    delay(1000);
  });

  server.on("/temphumid", []() {
    webContents();
    server.send(200, "text/html", webPage);
    delay(1000);
  });

    ArduinoOTA.onStart([]() {
    Serial.println("Start");
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.begin();  
  server.begin();
  Serial.println("HTTP server started");
  delayMS = sensor.min_delay / 1000;
}

void loop(void) {
  ArduinoOTA.handle();
  server.handleClient();
  yield();
}

void air_off() {
  int dataSize = sizeof(airOff) / sizeof(airOff[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = airOff[cnt];
    unsigned long us = micros();
    while (us + len > micros()) {
      digitalWrite(IRLedPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(IRLedPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

void air_on() {
  int dataSize = sizeof(airOn) / sizeof(airOn[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = airOn[cnt];
    unsigned long us = micros();
    while (us + len > micros()) {
      digitalWrite(IRLedPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(IRLedPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

void temp_humid() {
  delay(delayMS);
  sensors_event_t event;
  dht.temperature().getEvent(&event);
  if (isnan(event.temperature)) {
    temp = "Error reading!";
  } else {
    temp = String(event.temperature);
  }
  dht.humidity().getEvent(&event);
  if (isnan(event.relative_humidity)) {
    humid = "Error reading!";
  } else {
    humid = String(event.relative_humidity);
  }
}

void webContents() {
  webPage = "";
  webPage += "<html><header><title>WIFI SWITCH</title></header>";
  webPage += "<body style=\"text-align:center;font-size:48px\"><div>ESP8266 WEB SERVER</div><br/>";
  webPage += "<div><div>AC100V SWITCH:</div>";
  webPage += "<div><a href=\"on\"><button style=\"width:80%;font-size:60px\">AC100V:  ON</button></a></div><br/>";
  webPage += "<div><a href=\"off\"><button style=\"width:80%;font-size:60px\">AC100V: OFF</button></a></div><br/>";
  webPage += "<div>AIR-CON SWITCH:</div>";
  webPage += "<div><a href=\"airon\"><button style=\"width:80%;font-size:60px\">AIR-CON: ON</button></a></div><br/>";
  webPage += "<div><a href=\"airoff\"><button style=\"width:80%;font-size:60px\">AIR-CON: OFF</button></a></div>";
  webPage += "<div>Temperature: ";
  webPage += temp;
  webPage += " *C</div>";
  webPage += "<div>Humidity: ";
  webPage += humid;
  webPage += " %</div>";
  webPage += "</div></body></html>";
}


これは前回からの改良で、
・ESP8266
・赤外線LED(エアコン用)
・温度湿度センサー
・リレー(照明器具AC100V用)
を使っています。OTAのコードも含めたので、ワイヤレスでコードの書き換えが可能です。そのぶんコードも長く、メモリ消費量も多いプログラムとなりました。
基本的にはローカルネットワーク内でしか使えないのですが、
Wifi.config()を使えば固定IPアドレスにできるので、ルーターをポートフォワーディングしてみると、外部からもつながりました(スマホ4G通信で確認)。以下はブラウザ上の操作画面。


LANだけでなくWANからも操作できてよかったのですが、以前みつけたスマホアプリのBlynkでやってみたらどうなるかも試してみました。このアプリ見つけたのはいいのですが、しばらく使っていませんでした。どのくらい便利なのか?


以前、ざっと説明を見た限りでは、アプリ上でボタンやスイッチ部品を配置して、余計なコードは書かずにプログラムをアップロード(ワイヤレスでスマホから?)という感じでしたが、
実際手順に沿ってやってみると:
・スマホアプリをインストール+メールアドレスを登録
・New Project作成(使うハードウェアを指定:ESP8266など)
・セキュリティコードがメールへ届く
・Arduino IDEにBlynkライブラリをアップロードする
・Arduino IDEでセキュリティコードとWifiのIPアドレス+パスワードを専用プログラムに追記する
・専用プログラムをパソコンからUSB経由でESP8266へアップロード
・再度スマホアプリに戻り、好きなピンを割り当てながらボタンやスイッチを配置
・LANだけでなくWANからも操作可能
という感じです。


FirmataのようなBlynkファームウェア:
いちいちArduino IDEでプログラムを書かなくても済むというのが予想外に便利でした。最初は、スマホでボタンやスイッチへピンを割り当てて、それにあわせたプログラムをArduino IDEのほうでも書かなければいけないのかと思っていましたが、そうではなく、Blynkの専用プログラムがFirmataのようなファームウェアとして機能しているため、後からでも自由自在にBlynkアプリでESP8266のピンの機能割り当てができるという感じです。デジタル入力にするかデジタル出力にするか、それともPWM出力にするかなど、いちいちESP8266にプログラムをアップロードしなくても、その場で自由に変えられるということです。
ESP8266の場合は、
スケッチ例>Blynk>Boards_WiFi>ESP8266_Standalone
を選択し、
このプログラム内の、
YourAuthToken:メールで送られてきたセキュリティコード
YourNetworkName:自宅Wifiネットワーク名
YourPassword:自宅Wifiのパスワード
を書き込むだけでOKです。すべてのピンへの機能割り当てはBlynkのほうでしてくれるので、他のプログラムを書き込む必要はありません。
最初に送られてくるセキュリティコードはブロジェクトごとに使えるので、ボタンの配置や機能の割り当てを変えてもずっと使えます。

ということで、先ほどのプログラムをBlynkに適用させてみようかと。
しかし、赤外線信号の部分はBlynkではできなさそうなので、Blynkの応用的な機能であるVirtual Pinというものを使ってみました。このVirtual Pinは、実際のESP8266上のピンとは無関係で、Blynk上で使える変数やトリガーのようなものです。今回の赤外線信号の場合であれば、ボタンがオンで1、オフで0という値を割り当てておいて、Virtual Pinが1ならエアコンをオンにする赤外線信号を発する、0ならオフの信号を発するという感じにしておきます。そんな感じで、一度Virtual Pinを介して動作するプログラムにしてみました。

#define BLYNK_PRINT Serial
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>

#define IRPin  4
#define DHTPin  2
#define duty_high 8
#define duty_low 16
#define DHTTYPE DHT11
DHT_Unified dht(DHTPin, DHTTYPE);
int temp;
int humid;

char auth[] = "*****";
char ssid[] = "*****";
char pass[] = "*****";

unsigned long data_on[243] = {
 3400, 1400, 600, 250, 550, 250, 550, 1050, 550, 250, 550, 1050, 550, 250, 550, 250, 550, 250, 550, 1050, 550,
 1050, 550, 250, 550, 300, 500, 250, 550, 1100, 500, 1100, 500, 300, 500, 300, 500, 300, 500, 300, 500, 300,
 500, 300, 500, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 400, 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350,
 450, 1150, 450, 1150, 450, 1150, 450, 1150, 450, 1150, 450, 1200, 450, 350, 450, 350, 450, 350, 450, 1150,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 1200, 450, 350,
 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 400,
 400, 1200, 400, 400, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 400,
 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400,
 400, 400, 400, 400, 400, 400, 400, 400, 450, 350, 450, 1150, 450, 1150, 450, 350, 450, 1200, 400, 400, 400,
 400, 400, 1200, 400, 400, 400};
unsigned long data_off[99] = {
 3400, 1450, 550, 250, 550, 250, 500, 1100, 550, 250, 550, 1050, 550, 250, 550, 250, 550, 250, 550, 1050, 550,
 1050, 550, 300, 500, 250, 550, 300, 500, 1100, 500, 1100, 500, 300, 500, 300, 500, 300, 500, 300, 500, 350,
 450, 300, 500, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 400, 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450};

void setup(){
  Serial.begin(9600);
  Blynk.begin(auth, ssid, pass);
  pinMode(IRPin, OUTPUT);
  dht.begin();
  sensor_t sensor;
  dht.temperature().getSensor(&sensor);
  dht.humidity().getSensor(&sensor);
  dht.humidity().getSensor(&sensor);
}

BLYNK_WRITE(V0){
  int value = param.asInt();
  if (value) {
    air_on();
  } else {
    air_off();
  }
}

BLYNK_READ(V1){
  temp_humid();
  Blynk.virtualWrite(V1, String(temp)+" °C");
  Blynk.virtualWrite(V2, String(humid)+" %");
  Blynk.virtualWrite(V3, temp);
}

void air_on() {
  int dataSize = sizeof(data_on) / sizeof(data_on[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = data_on[cnt];
    unsigned long us = micros();
    while (us + len > micros()) {
      digitalWrite(IRPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(IRPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

void air_off() {
  int dataSize = sizeof(data_off) / sizeof(data_off[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = data_off[cnt];
    unsigned long us = micros();
    while (us + len > micros()){
      digitalWrite(IRPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(IRPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

void temp_humid(){
  sensors_event_t event;  
  dht.temperature().getEvent(&event);
  if (isnan(event.temperature)) {
    temp=0;
  }else {
    temp=int(event.temperature);
  }
  dht.humidity().getEvent(&event);
  if (isnan(event.relative_humidity)) {
    humid=0;
  }else {
    humid=int(event.relative_humidity);
  }
}

void loop(){
  Blynk.run();
}
こんな感じで、BLYNK_WRITE(V0)内に条件分岐をつくって赤外線信号のオンオフ操作をしています。BLYNK_READ(V1)でも、温度センサから読み取った値をBlynkアプリへ渡すようにしています。

スマホアプリのほうでは、こんな感じのボタン配置にしました。
一番上の黄色いスイッチが照明用、その下の白いスイッチがエアコン用、その下に温度と湿度表示、さらに温度のグラフもあります。一応4G通信で外部からアクセスも可能でした。
しかし、温度グラフに関しては、一旦このアプリを消してしまうとそれまでの記録も消えてしまってダメでした。もしかしたら配列を使って過去の記録を覚えさせていけないのかもしれません。このグラフ機能以外にもヒストリーグラフというウィジットがあって、それならいいのかもしれませんが、実はこのアプリ、各ウィジットにはポイント(コスト)があり、例えばボタン一個で200エナジー、グラフが400エナジーであり、合計の上限が決まっています。

ヒストリーグラフは何と900エナジーもします(赤い数値)。この段階で、ボタン2個なので400エナジー、温度と湿度数値表示で400エナジー、そしてグラフで400エナジー、このプロジェクトだけで合計1200エナジー消費しています。もう一つ別のプロジェクトもあって、そっちで500エナジー消費しているので、すべてのプロジェクトを合わせて1700エナジー使っていることになります。残り200エナジーしかありません(画面上部)。
そして、このエナジー量を増やすには課金が必要ということでした。すっかりオープンソースで無料なのかと思っていたら、なるほど、よくある一部有料というアプリでした。

ということで、温度グラフのかわりに消費エナジー500のビデオストリーミングも試してみました。
以前使ったIP WebCamというアプリでタブレットのカメラから配信させてみました。
LAN通信ですが、ちゃんと映ります。これはビデオなのでESP8266とは無関係。このビデオストリーミングの場合、カメラのIPアドレスの欄にhttp://192.168.3.2:8080/videoを入れると大丈夫でした。温度グラフよりも、こっちのほうがよさげ。
ただし、Blynkのサイトにも書いてありますが、Blynkサーバはストリーミングサーバを提供していないためWANからは見れないということです。その場合は他のサービスを使えと書いてあります。

とはいったものの、Blynkで当初予定していた内容はほぼできてしまいました。しかも設定なども簡単です。自前でHTMLやJavascriptまで書いてESP8266をサーバにしてもいいのですが、たしかに面倒。当然自分の思い描いているような内容にすることはできますが、Blynkだとあっというまにできてしまったので、これはこれでいいのかもしれません。この手のサービスはWANからもアクセス可能という部分が便利かもしれません。自宅のルーターをポートフォワーディングさせれば済むことですが、セキュリティなどのことも考えると面倒なので、外部サーバーを利用できるのであれば、それに越したことはありません。

次のステップとしては、ESP8266かESP32にカメラモジュールを接続して小型のIPカメラをつくろうと思っていますが、なかなか思うように進みません。その小型IPカメラをCNCマシンに搭載し、bCNCのカメラ機能をIP化するところまで行くというのがとりあえずの目標です。
ただ、そのついでにその他の照明や電源などもIP化しようとしているので、いろいろやることが増えているという現状です。あわよくば、スマホ上のボタン操作ではなく、音声入力で照明をオンにできないかとも考えていて、Python、HTML5、Javascript、場合によってはPHPなども同時進行という感じです。さらには、それらの開発環境となるAtom EditorやPlatformIOの使い方も学習中です。

2017年2月9日木曜日

IPカメラによる加工状況の監視(bCNCのIPカメラ化)

現在Bluetoothで、MacBook上のbCNCからCNCマシンを操作しています。同時にbCNCのPendant機能でスマホからも操作ができるので便利です。
あとは、加工中の状況(特にレーザー加工/目に危険なので)をカメラで監視できればいいと思って、Webカメラを設置できないかと考えていました。
USBケーブルでCNCマシンとMacBookをつなげていれば、Webカメラ(USB接続)で監視が可能です。例えば、以下のようなもの、

USBエンドスコープ:
AliExpress.com Product - Waterproof 5m Mini USB Endoscope Inspection Camera 6 White LEDs 1/9 CMOS 7mm Lens Borescope Snake Tube Camera with P2PUSBエンドスコープ(内視鏡):750円(送料込み)、直径7mm、ケーブル5m長いケーブル先端に直径7mmのカメラがついており、CNCマシンに取り付けやすいと思います。加工スポットをズームアップして監視したいので、こんな感じがいいのですが、ワイヤレスではないのが少し残念。そうなると、以下のようなもの。

AliExpress.com Product - Free shipping! 6LED HD 720P 1M / 2M / 5M WiFi Endoscope Waterproof Inspection Camera for ios and Android PCWifiエンドスコープ:2571円(1m)、2713円(2m)、2929円(5m)、送料無料。Wifiモジュールがついているので便利そうです。Wifiモジュールから外せばUSBカメラとしても使えると思います。
全体がコンパクトなWifiカメラなら、 AliExpress.com Product - 20pcs Mini DV Wifi Camera Q7 Cam 720P HD DVR Wireless IP Camera Video With IR LED Pocket-Size Remote By Phone Wholesale 1840円(送料込み)幅23mm、高さ43mmくらいなのでCNCマシンのどこにでも設置できそうです。
bCNCのカメラ機能:ひとつ気になるのは、bCNCにはOpenCVを使ったカメラ機能があり、パソコンとUSBカメラで接続していないと使えません。つまりWifiエンドスコープからbCNCには取り込めないということです。単なる監視として使うならWifiエンドスコープがとても便利そうですが、少し残念。

BluetoothカメラやワイヤレスUSB:そうなると、BluetoothカメラがあればワイヤレスかつbCNCにも取り込めるのではないかと探しましたが案外ない。Bluetoothも進化しているようですが、カメラなどのストリーミング映像には向いていないらしい。どちらかというとストリーミング映像はWifiが得意らしいです。通信速度の違いだと思います。Bluetooth3.0から通信速度が上がったので不可能ではないようですが、あまり機器類を見かけないし、当然安価にもならないはず。同様に、ワイヤレスUSBも数年前にはありましたが、下火になったようで、これも機器類を探すこと自体難しい。
やっぱりWifiカメラ:ワイヤレスのカメラといえば、現在はWifiが主流となってしまいます。例えば、スマホのカメラから、パソコンにwifiを通して映像を流すということも、アプリがあればすぐにできます。以前、スマホからパソコンに映像を送るだけなら、AirMoreというので簡単にできました。
パソコンのブラウザ上ではこんな画面。いわゆるスマホ画面のミラーリングという機能です。このほかにもスマホからいろんなデータ転送もできるので便利です。
スマホにアプリをインストールして、あとはパソコンのブラウザでWifi通信という感じです。

いくつかアプリを試しましたが、以下のIP Webcam(Android版)というアプリが便利そうでした。
これはタブレット(スマホも可)から流している映像をパソコンのブラウザ上で見ているところです。
操作画面は先ほどのAirMoreに比べるとシンプルです。このアプリはどちらかというとカメラに重点が置かれているようで、画質などいろんな設定が可能です。



解像度、前面背面カメラ切り替え、露出、ズーム、フォーカス、上下左右反転など様々なことがパソコンからも操作できます。また、ローカルネットワークだけでなく、外出先からも見る設定もできるようです。さらに便利なのは、

このように直接アクセス可能なアドレスが載っており、ストリーミング映像ならIPアドレス:ポート/videoというところへアクセスすればMJPEG、連続したJPEG画像取得するなら、IPアドレス:ポート/shot.jpgへアクセスということです。ブラウザで192.168.3.2:8080/shot.jpgにアクセスして見ると、
こんな感じで、シンプルに画像だけ取り出すことができます。これはJPEG画像ですが、画面を更新し続ければ動画にもなるようでした。おそらくスマホでの動画をブラウザからリクエストしてその都度最新画像を読み込んでいるということだと思います。
状況的にはこんな感じ。タブレットをバイスに立てかけています(はさみこんではいません)。ワイヤレスなので好きなところに置くことができますが、できれば本体に取り付けたい。映像については連続するJPEG画像なので、Pythonで読み込むプログラムも難しくなさそう。もしかしたらbCNCへも取り込めるかもしれないという可能性がアップしてきました。

Python-OpenCVで実験:bCNCはPythonで書かれているため、そしてbCNCのカメラ機能はOpenCVを使っているため、Python-OpenCVで先ほどのストーリミング画像を取り込めないか試して見ました。多少PythonもOpenCVも触ったことはあるのですが、基本的にどちらも初心者です。とりあえず、Python-OpenCVのGetting Startedから見てみることにしました。いくつかのチュートリアルがあり、まずは静止画像の読み込みと表示方法、そして動画の読み込みと表示方法という感じです。
import numpy as np
import cv2

img = cv2.imread('messi5.jpg',0)
cv2.imshow('image',img)
k = cv2.waitKey(0)
if k == 27:         # wait for ESC key to exit
    cv2.destroyAllWindows()
elif k == ord('s'): # wait for 's' key to save and exit
    cv2.imwrite('messigray.png',img)
    cv2.destroyAllWindows()
どうやらcv2.imread()とcv2.imshow()でできるようです。画像ソースをcv2.imread()に入れればいいだけのようで、そのまま先ほどのhttp://192.168.3.2:8080/shot.jpgを入れてcv2.imshow()で表示しようとしたのですがダメです。
import numpy as np
import cv2

cap = cv2.VideoCapture(0)

while(True):
    # Capture frame-by-frame
    ret, frame = cap.read()

    # Our operations on the frame come here
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # Display the resulting frame
    cv2.imshow('frame',gray)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()
VideoCaptureクラスの方でも試して見ると、やはりcv2.VideoCapture()にIPアドレスを入れてもエラーが出ます。Python2.7なのでOpenCV2.4を使用中。というのは、bCNCがPython3に対応していないため。どうやらOpenCV3にすれば、URLを入れても大丈夫らしい。惜しい。色々調べて見ると、OpenCV2.4ではネットワーク上の画像などを読み込むには、この方法ではダメらしい。

# coding: UTF-8
import cv2
import numpy as np
import urllib
import sys

ipAddress='http://192.168.3.2:8080/shot.jpg'

def readImg(ip):
   req=urllib.urlopen(ip)
   data=req.read()
   byteData=bytearray(data)
   arr=np.asarray(byteData, dtype=np.uint8)
   return cv2.imdecode(arr,1)

while True: 
   img = readImg(ipAddress)
   if len(img) > 0:
      h, w = img.shape[:2]
      halfImg = cv2.resize(img, (w/2,h/2))
      cv2.imshow('IMAGE',halfImg)
   else:
      print 'no image'
      sys.exit()
      break
   if cv2.waitKey(1) & 0xFF == ord('q'):
      break

cv2.destroyAllWindows()

その後、いろいろエラー続出でかなり悩みましたが、なんとかIPカメラから画像を取り込んでPythonで表示することができました(上記コード)。たったこれだけのコードですが、外部から取り込んだデータをデコードする必要があるみたいで、その方法に気づくのに時間がかかり、なおかつ普通の読み込みではしないような手順があったり、一行ずつ確かめては書き直したりしていました。
追記: その後もPythonをいじってましたが、Pythonではdefの外側の変数(グローバル変数)は、def内にはそのままでは通らないようで、def内でglobalを書かないといけないということがわかりました。Pythonはちょっと癖があっていちいち調べないといけないので難しいです。
これはPython-OpenCVによる映像の読込み/表示画面。シンプルに映像だけ取り出せることができました。

手順としては、VideoCapture()の代わりに、urllib.urlopen()でインターネットなどネットワーク上にあるデータをリクエストして受け取り、その配列データをバイトデータ配列へ、そして符号なし8ビット配列、さらにはデコードもして映像画素に合わせた行列に変換して、ようやく読み込める状態になるようです。普通ならimread()など読み込むための関数一発でいけそうですが、そうではないという面倒な手順になっています。しかしデコードする関数などはPythonにあるため、手順を踏めばそれほど難しくはありません。しかし、それぞれが何をしているのか、パラメータは何なのかなど、Documentationも見ながら理解していかないとエラーばかり出てしまいます。
*尚、このようなことを可能にするには、PythonのほかOpenCV、NumPyなどのインストールが必要です。


bCNCへ取り込み:
次は、これを元にbCNCのコードを改造していかなければなりません。bCNCのCamera.pyを開いて見ると、基本的にUSBカメラから映像を取り込んでいるためか、OpenCVのVideoCaptureを使っていました。これは先ほどの動画に関するチュートリアルでも見た方法ですが、今回はVideoCaptureを使わないで、映像(連続するJPEG画像)を取り込む方法(上のコード)でやりたいと思います。というか、その方法しかまだ知らないので。VideoCaptureを使ってMJPEGを取り込めれば簡単そうだけど、OpenCV2.4だと多分できない。 だいたいこの辺のQ&AサイトGitのここなどを参考に試行錯誤していました。
追記: MPEGの場合、IPアドレス:ポート/video?.mpegにアクセスすればいいようで、このサイトに書いてありました。mpegやjpegは1フレームごとに開始マーカー、終了マーカーがあり、それを手掛かりにデータを読み取るようです。

これが、bCNCのCamera.pyの中身です。いろいろいじっている最中。当然バックアップは取ってあります。IPアドレスからの画像取り込みのため、手順が違ってちょっと面倒です。すぐエラーが出ます。
数時間後、とりあえず画像を取り込む所まではできました(やや強引に)。
もともと、bCNCのPendant機能でカメラ映像を他の端末でモニタリングできるのですが、それも可能でした。つまり、IPカメラ(スマホ)の映像をbCNCを通してMacBookに橋渡しさせ、また別の端末(タブレット)からMacBookのIPアドレスにアクセスして、その映像を見るということです。当然、映像配信元のIPカメラ(スマホ)のIPアドレスに直接アクセスして見ることもできます。
まだ、bCNC内の画像調整(画像サイズや角度)のパラメータとはつなげられていないので、きちんとは機能していません。 bCNCのProbe Camera Alignment(OpenCVを使った位置決め機能)を試したことはないのですが、できればこの機能にUSBカメラのみならずIPカメラも接続可能にして使えればと思っています。もう少しプログラムの改造には時間かかりそうです。というか、Pythonに慣れていないので、かなり疲れてきたという感じです。
予定としては:

・bCNCにおいてIPカメラからの映像を読み込めるようにする(現在ここ)
・読み込んだ映像のサイズや角度の調整ができるようにbCNCのパラメータにつなげる
・USBカメラとIPカメラの両方を使えるようにする 
・USBカメラとIPカメラ切り替えボタンやIPアドレス入力欄をつくる

ここまでできれば便利だけど、かなりめんどくさそう。


IPカメラについて:
プログラムの方は少しずつ改造していきますが、IPカメラの方も考えています。とりあえず手っ取り早いのが、スマホやタブレットをCNCマシンのヘッド近くに置いておくことですが、できれば小型なカメラを設置したいと思っています。WifiといえばESP8266が安価なので、それにカメラモジュールをつけて、スピンドル先端付近に取り付けられるのが理想です。
AliExpress.com Product - V3 4 M bytes (32 Mbits) FLASH Lua NodeMcu placa de desarrollo de Redes WIFI Basado ESP8266 con firmwareESP8266:341円(送料込み)これに以下のようなカメラモジュールをつければ、Wifiカメラとして使えます。

AliExpress.com Product - New OV7670 VGA Camera Module Lens CMOS 640X480 SCCB w I2C Interface Auto Exposure Control Display Active384円(送料込み)これは前回調べましたが、AudCamのGitにArduino用ライブラリなどあります。あるいはInstructables追記:どうやらこの安価なOV7670はFIFOなし(バッファメモリなし)らしく、接続してもかなり遅いフレームレートになるようです。FIFOありの以下のようなモジュールが扱いやすいと思います。
AliExpress.com Product - High Quality with FIFO CMOS Camera Module OV7670 Sensor Module Microcontroller Collection Module 1374円(送料無料)、FIFOつきOV7670カメラモジュール。


  AliExpress.com Product - Free shipping ESP32-T Shield ESP32-Bit Development Board Compatible For ESP-32S Bluetooth WiFi Module ESP32S Wireless Board810円(送料込み)、追記:これはシールドだけでした。さらにこのESP32はWifiだけでなくBluetoothもついているようなので、もしかするとBluetoothカメラも可能かもしれません。ESP8266の上位機種といったところ。少し高価ですが、スペックに対してはかなり安い。秋月にもチップだけ700円で売っています
IPカメラ化についてはまた作業が進んだら報告します。

memo:Reading and Writing images and Video(OpenCV)python-opencv-ipcam.pyTo get mjpeg from "http://192.168.3.2:8080/video?.mjpeg"  To get images from "http://192.168.3.2:8080/video?action=stream"   pip install requests

人気の投稿