「Raspberry Pi」カテゴリーアーカイブ

【ラズパイ】プログラムからカメラを操作する

前回はコマンドでカメラを操作しましたが、

今回はプログラムからカメラを使用します。

やり方は2種類あるみたいで、

python-picameraを使用する方法と、

openCVを使用する方法です。

今回はopenCVを使用してみます。

なぜなら、openCVは画像解析なんかもできるので、今後も応用が利くかも、と思いましたので。

openCVのインストール

$ sudo apt-get install python3-opencv

pip3を使用するインストール方法もネットにありましたが、

こちらはインストールの過程でビルド処理がありますので、

実行すると、一日コースです。

apt-getを使用したほうが短時間で完了します。

(それでも数分かかります。)

早速実行してみる

from datetime import datetime
import cv2, os

def main():

  cam = cv2.VideoCapture(0)
  if cam == None:
    return False

  while True:
    # カメラから映像を読み込む
    _, img = cam.read()

    cv2.imshow("preview", img)

    # キーを入力した際に撮影を終了する
    if cv2.waitKey(1) < 255: break

  # 事後処理
  cam.release()
  cv2.destroyAllWindows()

if __name__ == '__main__':
  main()
  

cam.read()を実行すると、処理結果と、frameデータが取得できます。

ここでは処理結果は使用しないので、_ と書いています。

imshow()を実行するとframeのデータを画面に表示されます。

それをwhileループしているので、プレビューのように映像が表示されます。

※ラズパイの画面をVNCのリモートデスクトップで表示させています。

最終的には、このframeデータをリモートに飛ばして表示させたい。

openCVは調べてみると、いろいろな事ができるみたいなので、もうちょっと調べてみます。

【ラズパイ】カメラモジュールを使ってみる。

カメラモジュール買いました。

装着。

今回は足つきで固定できるタイプの物を買いました。

フラットケーブルで接続したので、ラズパイの設定でカメラを有効にすれば使用できるはずです。

カメラで静止画を撮影するには以下のコマンドを入力します。

$ raspistill -o image.jpg

モニターにプレビューが表示され、それが閉じるのと同時に、カレントディレクトリにimage.jpgが保存されます。

動画を撮影するときは以下のコマンドを入力します。

$ raspivid -t 5000 -o test.h264

プレビューのみの時は以下です。

$ raspivid --demo 5000

ただこれだけできても面白くない。

次回はプログラムから画像データを扱ってみたいと思います。

【ラズパイ】【いろいろ計測モニター】Windows側からも操作しちゃう

前回までの状況はこちら。

最新ソースはこちら。

ラズパイ https://github.com/takishita2nd/RaspiDisplayMonitor

Windows https://github.com/takishita2nd/IroiroMonitor

Windows側からラズパイの情報を取得できるなら、

Windows側からでもラズパイを操作する事も出来ます。

なので、Windows側からスイッチ操作を行う処理を作ってみます。

Windowsにボタンを設置。

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Label Grid.Row="0" Grid.Column="0" Content="時刻" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10" Width="239" Height="86" FontSize="48"/>
        <Label Grid.Row="1" Grid.Column="0" Content="温度" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10" Width="239" Height="86" FontSize="48"/>
        <Label Grid.Row="2" Grid.Column="0" Content="湿度" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48" Margin="10" Width="239" Height="87"/>
        <Label Grid.Row="3" Grid.Column="0" Content="CPU温度" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48" Margin="10" Width="239" Height="87"/>
        <Label Grid.Row="4" Grid.Column="0" Content="GPU温度" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48" Margin="10" Width="239" Height="87"/>
        <Label Grid.Row="0" Grid.Column="1" Content="{Binding DateTime}"  HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="24"/>
        <Label Grid.Row="1" Grid.Column="1" Content="{Binding Temperature}"  HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48"/>
        <Label Grid.Row="2" Grid.Column="1" Content="{Binding Humidity}" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48"/>
        <Label Grid.Row="3" Grid.Column="1" Content="{Binding CpuTemp}" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48"/>
        <Label Grid.Row="4" Grid.Column="1" Content="{Binding GpuTemp}" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48"/>
        <Button Grid.Column="0" Grid.Row="5" Grid.ColumnSpan="2" Content="ボタン" Command="{Binding ButtonClickCommand}" FontSize="48" />
    </Grid>

ボタンを押すと、ラズパイ側にPOSTリクエストを送信するようにします。

今後の拡張性を考えて、コマンド番号みたいなものを送れるようにしましょうか。

    [JsonObject("CommandModel")]
    class Command
    {
        [JsonProperty("number")]
        public int number { get; set; }
    }
    public class MainWindowViewModel : BindableBase
    {
        private const int CommandSwitch = 1;

        public DelegateCommand ButtonClickCommand { get; }

        public MainWindowViewModel()
        {
            ButtonClickCommand = new DelegateCommand(async () =>
            {
                Command cmd = new Command();
                cmd.number = CommandSwitch;
                var json = JsonConvert.SerializeObject(cmd);
                try
                {
                    HttpClient client = new HttpClient();
                    var content = new StringContent(json, Encoding.UTF8);
                    await client.PostAsync("http://192.168.1.15:8000/", content);
                }
                catch (Exception ex)
                {
                }
            });

これで、ラズパイ側にPOSTリクエストを送れるようになりました。

次はラズパイ側のコードを書いていきます。

class StubHttpRequestHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        content_len = int(self.headers.get('content-length'))
        requestBody = json.loads(self.rfile.read(content_len).decode('utf-8'))

        if requestBody['number'] == 1:
            lock.acquire()
            GLCD.GLCDDisplayClear()
            lock.release()
            pushButton()

        response = { 'status' : 200 }
        self.send_response(200)
        self.send_header('Content-type', 'application/json')
        self.end_headers()
        responseBody = json.dumps(response)

        self.wfile.write(responseBody.encode('utf-8'))

HttpRequestHandlerクラスにdo_POSTメソッドをオーバーライドします。

これを実装することで、POSTリクエストを受診して処理することが出来ます。

受信したデータがボタン操作ならば、ラズパイ側で行っているボタン操作と同じ処理をおこないます。

def pushButton():
    global mode
    mode += 1
    if mode > 4:
        mode = 1

しかし、ここで考えなければならないのは、ラズパイ側の周期処理とHTTPサーバ処理は非同期処理を行っていると言うこと。

はい、処理が競合しちゃいます。

なので、スレッド間の待ち合わせ処理を行う必要があります。

方法はいろいろあるのですが、今回は一番簡単な方法を使用します。

Lockを使用する方法です。

lock = threading.Lock()
    try:
        while True:
            lock.acquire()
            Humidity = AM2320.GetHum()
            Temperature = AM2320.GetTemp()

            if sw == True:
                GLCD.GLCDDisplayClear()
                pushButton()
                sw = False

            if mode == 1:

-中略-

            lock.release()
            time.sleep(1)
    except KeyboardInterrupt:
        GLCD.GLCDDisplayClear()
        GPIO.cleanup()

lock = threading.Lock()を定義し、同じlockで周期処理全体と、HTTPのスイッチ処理をlock/releaseで囲みました。

これで、一方がlockされている場合、もう一方はlockがreleaseされるまで処理に待ったがかかります。

これを使用すれば、ラズパイの遠隔操作も可能になります。

【ラズパイ】【いろいろ計測モニター】あれから改造。

とりあえず、少し改造しました。

調べてみると、GPU温度も測定できるみたいなので、それも入れてみました。

ラズパイのGPU温度を確認するコマンドは、

sudo /opt/vc/bin/vcgencmd measure_temp

sudoなので、pythonプログラム実行時もsudoで管理者権限で実行しなければなりませんが、

サービスで実行させているので、基本的に管理者権限で動作しているので、問題無いでしょう。(実際、動いた。)

時刻というのは現在時刻じゃなくて、最後に測定した時刻です。

HTTPでラズパイからデータを取得していますが、間隔を短くすると、ラズパイZeroでもCPU温度が上がってしまうので、少し間隔を空けています。(1分ぐらい)

次どうしようか。

新しい部品があればネタに出来そうだけど、今はお金が無いので。

グラフ化させてみる?

【ラズパイ】【いろいろ計測モニター】CPU温度をWindows側で表示する

前回の記事で、ラズパイのCPU温度を取得できることが分かったので、

これをいろいろ計測モニターに表示させるところまでやっていきたいと思います。

PythonでLinuxコマンドを実行し、結果を得るには以下の様にプログラミングします。

import subprocess

args = ['vcgencmd', 'measure_temp']
res = ""
try:
    res = subprocess.run(args, stdout=subprocess.PIPE)
except:
    print("Error.")

print(res.stdout)

参考記事

https://qiita.com/tdrk/items/9b23ad6a58ac4032bb3b

これを実行すると出力はこうなります。

これを色々と加工して、Windows側に渡すパラメータにします。

実際に組み込んだコード。

def getCPUTemp():
    args = ['vcgencmd', 'measure_temp']
    res = ""
    try:
        res = subprocess.run(args, stdout=subprocess.PIPE)
    except:
        return "Error."
    return res.stdout.decode().split('=')[1].strip().replace('\'C', '℃')
        data = {
            'datetime' : datetime.datetime.now().strftime('%Y:%m:%d %H:%M:%S'),
            'temperature': Temperature,
            'humidity': Humidity,
            'cputemp' : getCPUTemp()
        }

えっと、何をしているかというと、

まずは、res.stdoutの値はバイト文字なので、これをdecode()で文字列に変換します。

その後、split(‘=’)で=で文字列を分割し、後方の文字列([1])を取得します。

しかし、その文字列の中には不要な改行文字(\n)が入っているので、strip()で削除。

そして、「’C」と表示させているところをreplace()で「℃」に置き換えています。

これをJsonのパラメータに追加します。

ここまで出来ればWindows側は表示するだけなので、Windows側コードは割愛。

【ラズパイ】raspberry PI4には冷却用ファンを取り付けよう!

人が触れない温度。

https://qiita.com/BearcubThaw/items/070be8be8dc14e5837f4

$ vcgencmd measure_temp

というコマンド、ラズパイ専用コマンドがありまして、

このコマンドでCPUの温度を出力することが出来ます。

上のスクショはファン無し、アイドル状態で放置した状態でコマンド打った結果。

この温度、お風呂だったら入れないね。

この状態のまま重たい処理を実行させると、さらにCPU温度が上がって、ラズパイは確実に壊れます。

なので、ラズパイ専用のファンを買いました。

ネジが一本どっか行ったけど気にするな。

こっちは老眼で小さい部品見えないんだよ。

部品の中にはヒートシンクも入っていました。

これをCPUに貼り付けると、熱を吸い取って、表面積の多い凸凹部分に熱を伝え、これをファンの風で冷却します。

ファンを回した結果はこちら。

37℃まで下がりました。

人肌レベルですね。

まぁ、ラズパイ4はお勉強用のマシンなので、常時起動しておくことは少ないと思いますが、これで安心です。

ちなみに。

ラズパイZeroの方ですが、

ちょっと温度高め?

触っても熱くないですが。

(ラズパイ4は明らかに手で触れないくらい熱かった。)

うーん、今走らせてるサービス処理、もう少し軽くするか。

こういった温度をWindows側でモニタリングするツールを作ってもいいね。

【ラズパイ】【いろいろ計測モニター】HTTPサーバを実装する(修正)

すいません、

昨日のこれ、修正します。

何が問題なのかというと、複数のスレッドが同じデバイスにアクセスしている、と言うことです。

デバイスにアクセスするときには「一連のアクセス処理」が発生します。

温湿度計にアクセスする場合は、I2Cインターフェースを使用しているので、データの要求・応答に2つの端子にパルスパターンを送受信しているはずです。

この処理を複数のスレッドで同時に行った場合、正しくアクセス処理が行われない場合があります。

アクセス処理が競合しちゃうんですね。

これを防ぐために、こういったマルチスレッドのプログラミングでは「待ち合わせ処理」(一方の処理が完了するまで、もう一方の処理を待つ)というのが必要なのですが、

今回は規模が小さいので、アクセスを行うスレッドを一つだけ(メインのスレッドのみ)にして、他のスレッドからはデバイスにアクセスしないようにします。

Humidity = 0
Temperature = 0

def __main__():
    global sw
    global Humidity
    global Temperature

    GPIO.setmode(GPIO.BCM)
    GPIO.setup(22,GPIO.IN) 
    GPIO.add_event_detect(22, GPIO.FALLING, callback=callback, bouncetime=300)
    GLCD.PinsInit(20, 7, 8, 9, 18, 19, 10, 11, 12, 13, 14, 15, 16, 17)
    GLCD.GLCDInit()
    GLCD.GLCDDisplayClear()

    roop = 10 * 60 * 60
    mode = 1
    
    thread = threading.Thread(target=httpServe)
    thread.start()
    
    try:
        while True:
            Humidity = AM2320.GetHum()
            Temperature = AM2320.GetTemp()

            if sw == True:
                GLCD.GLCDDisplayClear()
                mode += 1
                if mode > 4:
                    mode = 1
                sw = False

            if mode == 1:
                if roop >= 10 * 60 * 60:
                    GLCD.GLCDDisplayClear()
                    Weather.RequestAPI()
                    weather = Weather.GetWeather()
                    temp = Weather.GetTemp()
                    roop = 0

                GLCD.GLCDPuts(1, 0, "Date :")
                GLCD.GLCDPuts(5, 8, datetime.datetime.now().strftime('%Y:%m:%d %A '))
                GLCD.GLCDPuts(1, 16, "Weather :")
                GLCD.GLCDPuts(10,24, weather)
                GLCD.GLCDPuts(10,32, "Temp : " + format(temp) + 'C')
                GLCD.GLCDPuts(1, 40, "Time : " + datetime.datetime.now().strftime('%H:%M'))
                GLCD.GLCDPuts(1, 48, "Humidity    : " + Humidity + '%')
                GLCD.GLCDPuts(1, 56, "Temperature : " + Temperature + 'C')

                roop += 1

            #中略

            time.sleep(1)
    except KeyboardInterrupt:
        GLCD.GLCDDisplayClear()
        GPIO.cleanup()

#中略

class StubHttpRequestHandler(BaseHTTPRequestHandler):
    server_version = "HTTP Stub/0.1"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def do_GET(self):
        enc = sys.getfilesystemencoding()

        data = {
            'datetime' : datetime.datetime.now().strftime('%Y:%m:%d %H:%M:%S'),
            'temperature': Temperature,
            'humidity': Humidity,
        }

        encoded = json.dumps(data).encode()

        self.send_response(HTTPStatus.OK)
        self.send_header("Content-type", "text/html; charset=%s" % enc)
        self.send_header("Content-Length", str(len(encoded)))
        self.end_headers()

        self.wfile.write(encoded)     

__main__()

これで競合は回避できるはず。

【ラズパイ】【いろいろ計測モニター】HTTPサーバを実装する

測定した温度、湿度をWindows側で表示させたいと思います。

前回HATでやったときはラズパイ側をクライアント、Windows側とサーバとして動作させましたが、

今回は逆で、ラズパイ側をサーバ、Windows側をクライアントとして実行させます。

常に稼働している方をサーバにする方が何かと都合が良い。

こちらの記事を参考にしました。

https://qiita.com/linxuesong/items/8ac98102c24b8f587a16

ただし、このままサーバとして稼働させると、他の処理が出来なくなってしまいます。

なので、HTTPサーバの処理を、既存のループ処理とは別スレッドで実行する必要があります。

なので、スレッドの扱いについて、こちらの記事を参照。

https://qiita.com/tchnkmr/items/b05f321fa315bbce4f77

最終的に組み上がったコードはこちら。

import os
import sys
import urllib.parse
import json
import RPi.GPIO as GPIO
import time
import datetime
import calendar
import GLCD
import AM2320
import Weather
import threading

from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
from http import HTTPStatus

PORT = 8000
sw = False

def __main__():
    global sw
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(22,GPIO.IN) 
    GPIO.add_event_detect(22, GPIO.FALLING, callback=callback, bouncetime=300)
    GLCD.PinsInit(20, 7, 8, 9, 18, 19, 10, 11, 12, 13, 14, 15, 16, 17)
    GLCD.GLCDInit()
    GLCD.GLCDDisplayClear()

    roop = 10 * 60 * 60
    mode = 1
    
    thread = threading.Thread(target=httpServe)
    thread.start()
    
    try:
        while True:

            #既存のループ処理

            time.sleep(1)
    except KeyboardInterrupt:
        GLCD.GLCDDisplayClear()
        GPIO.cleanup()

def callback(channel):
    global sw
    sw = True

def httpServe():
    handler = StubHttpRequestHandler
    httpd = HTTPServer(('',PORT),handler)
    httpd.serve_forever()

class StubHttpRequestHandler(BaseHTTPRequestHandler):
    server_version = "HTTP Stub/0.1"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def do_GET(self):
        enc = sys.getfilesystemencoding()

        data = {
            'datetime' : datetime.datetime.now().strftime('%Y:%m:%d %H:%M:%S'),
            'temperature': AM2320.GetTemp(),
            'humidity': AM2320.GetHum(),
        }

        encoded = json.dumps(data).encode()

        self.send_response(HTTPStatus.OK)
        self.send_header("Content-type", "text/html; charset=%s" % enc)
        self.send_header("Content-Length", str(len(encoded)))
        self.end_headers()

        self.wfile.write(encoded)     

__main__()

Windows側からHTTPのGETリクエストを受信すると、時刻、温度、湿度をJson形式で応答を返します。

今日の北海道は暑いぜ。

日照センサーで遊ぼう。

発光ダイオードって、抵抗噛まさないと壊れるんだね。

今回は日照センサーを仕入れたので、これで遊んでみたいと思います。

センサー部分に光を当てると、抵抗値が変わるという物です。

それを利用すればスイッチ処理に応用できたりします。

電流を測定すれば光量を数値化できると思いますが、アナログデータの扱いは難易度が高いので、今回はやりません。

今回は明るさによってLEDが点灯・消灯する回路を作成していきます。

回路図はこんな感じ。

明るいときは日照センサーの抵抗値が低いため、

この方向に電流が流れるため、LEDは点灯しません。

暗くなると、日照センサーの抵抗値が高くなるため、

この方向に電流が流れ、トランジスタのベースに入力が発生し、

この様にコレクタからエミッタ方向に電流の流れが発生してLEDが点灯します。

スマホのフラッシュで日照センサーに光りを当てています。

元々部屋の中が暗いので、通常状態でLEDが点灯しています。

ただ、今回は、あり合わせの抵抗を使用しているため、抵抗値は適当です。

ちゃんとした、計算された抵抗を使用すれば、もっと利便性の良い日照スイッチが出来るかもしれません。

【ラズパイ】【いろいろ計測モニター】時計表示を大きくする。

フォントをもっとシンプルなものに変更しました。

複雑なフォントにすると、データ化がめんどくさい。

こういうシンプルなデザインにした方が、数字表示用8LEDディスプレイみたいで(厳密には違うが)データ化が1時間程度で完了しました。

さらに、横幅も少し小さくして、時間と分のセパレータ「:」も入れることが出来ました。

このサイズだったらかなり見やすいでしょ。

    [ #1
        [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        ],
        [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        ],
        [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        ],
        [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        ],
        [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        ],
        [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        ]
    ],

データもこんな感じなので(というか、縦に線を引いているだけなので)サクッと作れました。

具体的にはgitHubのソースを見て欲しい。

https://github.com/takishita2nd/RaspiDisplayMonitor

表示する時刻データを、

GLCD.drowLargeClock(datetime.datetime.now().strftime('%H:%M'))

こんな感じで文字列化して、

def drowLargeClock(time):
    position = 0
    val = 0
    for s in time:
        if s == ':':
            val = 10
        else:
            val = int(s)

        for page in range(6):
            for addr in range(24):
                if position + addr < 64:
                    SelectIC(1)
                    SetPage(page)
                    SetAddress(position + addr)
                else:
                    SelectIC(2)
                    SetPage(page)
                    SetAddress(position + addr - 64)
                WriteData(LFont.Array[val][page][addr])
        position += addr

このように処理させることで、大きいフォントで時刻表示できます。

これでさらに使い物になりましたな。