「#ラズパイ」タグアーカイブ

【ラズパイ】【カメラ】プレビュー画面をラズパイから取得・表示(修正)

この記事のソースが余りにも冗長すぎるので、書き換えました。

こんな感じです。

CONTENT_TYPE = {'.html': 'text/html; charset=utf-8', '.txt': 'text/plain; charset=utf-8', '.js': 'text/javascript', '.json': 'application/json',
        '.jpeg': 'image/jpeg', '.jpg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif',
        '.css':'text/css'}



    def do_GET(self):
        parsed = urlparse(self.path)
        if parsed.path == '/Streaming':
            enc = sys.getfilesystemencoding()

            _, img = cap.read()
            resized_img = cv2.resize(img, (480, 320))
            _, encoded_img = cv2.imencode('.jpg', resized_img, [int(cv2.IMWRITE_JPEG_QUALITY), 30])
            dst_base64 = base64.b64encode(encoded_img).decode('utf-8')

            data = {
                'image': 'data:image/jpg;base64,' + dst_base64
            }

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

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

            self.wfile.write(encoded)
        elif parsed.path.endswith('/'):
            self.send_response(HTTPStatus.OK)
            with open('index.html',mode='br') as f:
                data = f.read()
            self.send_header("Content-type", "text/html; charset=utf-8")
            self.end_headers()
            self.wfile.write(bytes(data))
        else:
            self.send_response(HTTPStatus.OK)
            filepath = '.' + parsed.path
            with open(filepath, mode='br') as f:
                data = f.read()
            self.send_header("Content-type", CONTENT_TYPE[pathlib.Path(filepath).suffix])
            self.end_headers()
            self.wfile.write(bytes(data))

まず、URLが”/”の場合はindex.htmlを返します。

そして、URLが”/Streaming”の場合はカメラの映像をbase64で返します。

それ以外の場合(今回で言うと、jsファイル達)、URLで指定されたファイル名と同じファイルを返します。

ただ、その場合、content-typeを正しいものにしないとブラウザが正しく動いてくれないので、ファイル拡張子に対応する適切なcontent-typeを設定するようにしています。

それを対応づけているのがCONTENT_TYPEです。

こうすることで、今後jsファイル以外のものにも対応出来ます。

一般的なHTTPサーバに比べれば不十分ですが、今回目指しているのはそれではないので、こんなもんで十分でしょう。

ソースはgitHubに公開しました。

https://github.com/takishita2nd/RemoteCamera

【ラズパイ】【カメラ】プレビュー画面をラズパイから取得・表示

前回は、Webページをローカルで作成して、そこからブラウザに表示させていたのですが、

今回はWebページをラズパイに設置して、ブラウザからラズパイにアクセスすることによってプレビュー画面を表示させたいと思います。

動作はURLのパスによって変えます。

パスが”/”ならば、Webページを、”/Streaming”ならカメラの画像を返すようにします。

まず、Pythonのコードはこうなりました。

import base64
import cv2
import json
import os
import sys
import time
import threading

from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
from http import HTTPStatus
from urllib.parse import urlparse

PORT = 8000

cap = cv2.VideoCapture(0)

def __main__():
    thread = threading.Thread(target=httpServe)
    thread.start()
    
    try:
        while cap.isOpened():
            time.sleep(1)
    except KeyboardInterrupt:
        return

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):
        parsed = urlparse(self.path)
        if parsed.path == '/Streaming':
            enc = sys.getfilesystemencoding()

            _, img = cap.read()
            resized_img = cv2.resize(img, (480, 320))
            _, encoded_img = cv2.imencode('.jpg', resized_img, [int(cv2.IMWRITE_JPEG_QUALITY), 30])
            dst_base64 = base64.b64encode(encoded_img).decode('utf-8')

            data = {
                'image': 'data:image/jpg;base64,' + dst_base64
            }

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

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

            self.wfile.write(encoded)
        elif parsed.path == '/vue.min.js':
            self.send_response(HTTPStatus.OK)
            with open('vue.min.js',mode='br') as f:
                data = f.read()
            self.send_header("Content-type", "text/javascript")
            self.end_headers()
            self.wfile.write(bytes(data))
        elif parsed.path == '/jquery-3.5.1.slim.min.js':
            self.send_response(HTTPStatus.OK)
            with open('jquery-3.5.1.slim.min.js',mode='br') as f:
                data = f.read()
            self.send_header("Content-type", "text/javascript")
            self.end_headers()
            self.wfile.write(bytes(data))
        else:
            self.send_response(HTTPStatus.OK)
            with open('index.html',mode='br') as f:
                data = f.read()
            self.send_header("Content-type", "text/html; charset=utf-8")
            self.end_headers()
            self.wfile.write(bytes(data))

__main__()

無駄にでかくなった気がする。

というのも、使用しているjsファイルもラズパイからGETしようとブラウザが動くので、jsファイルを送るように作成しないといけないのです。

めんどくさいから、他のライブラリを使用することも考えないといかんなぁ。

続いて、html側。

こちらはそんなに難しくはない。

URLを変えるだけなので。

<!DOCTYPE html>
<html>
<head>
  <title>My first Vue app</title>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="vue.min.js"></script>
  <script src="jquery-3.5.1.slim.min.js"></script>
</head>
<body>
  <div id="app">
      <image id="camera" src="" />
  </div>

  <script>
    var app = new Vue({
      el: '#app',
      data: {
        timer: null,
      },
      created: function() {
        self = this;
        this.timer = setInterval(function() {self.onLoad()}, 50)
      },
      methods: {
        onLoad: function() {
            axios.get('http://pi4.local:8000/Streaming').then(function(response){
                $("#camera").attr('src', response.data.image);
            }).catch(function(error){
            });
        }
      }
    })
  </script>
</body>
</html>

これで動作しました。

【ラズパイ】リモートでカメラのプレビュー表示

いや、今回は結構ハマった。

これが半日頑張った成果だ。

まずはラズパイ側。

import base64
import cv2
import json
import os
import sys
import urllib.parse
import time
import threading

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

PORT = 8000

cap = cv2.VideoCapture(0)

def __main__():
    thread = threading.Thread(target=httpServe)
    thread.start()
    
    try:
        while cap.isOpened():
            time.sleep(1)
    except KeyboardInterrupt:
        return

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()

        _, img = cap.read()
        resized_img = cv2.resize(img, (480, 320))
        _, encoded_img = cv2.imencode('.jpg', resized_img, [int(cv2.IMWRITE_JPEG_QUALITY), 30])
        dst_base64 = base64.b64encode(encoded_img).decode('utf-8')

        data = {
            'image': 'data:image/jpg;base64,' + dst_base64
        }

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

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

        self.wfile.write(encoded)     

__main__()

そしてクライアント側。

<!DOCTYPE html>
<html>
<head>
  <title>My first Vue app</title>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="vue.min.js"></script>
  <script src="jquery-3.5.1.slim.min.js"></script>
</head>
<body>
  <div id="app">
      <image id="camera" src="" />
  </div>

  <script>
    var app = new Vue({
      el: '#app',
      data: {
        timer: null,
      },
      created: function() {
        self = this;
        this.timer = setInterval(function() {self.onLoad()}, 50)
      },
      methods: {
        onLoad: function() {
            axios.get('http://pi4.local:8000').then(function(response){
                $("#camera").attr('src', response.data.image);
            }).catch(function(error){
            });
        }
      }
    })
  </script>
</body>
</html>
https://taki-lab.site/bocci/wp-content/uploads/2020/10/PXL_20201001_054140711.mp4

まず、サーバ側の説明。

今回は、すでに使った実績のある、HTTPで通信を行います。

もっと下位層のプロトコルを使うと、もっと効率よくデータの送信ができるのですが、その分、扱いも難しくなります。

HTTPはTCP/UDPよりも、データのやりとりが多くなるので、少しモッサリ感がありますが、扱いが簡単になります。

実際、今回はブラウザで表示させているので!

カメラで撮影し、それを画像に落とすところまでは今まで通りですが、

今回はHTTPで送信するために、Base64に変換し、Jsonに載せて送信します。

Base64の頭にある「data:image/jpg;base64」というのは、このデータはJpegですよ、ということを示す文字列で、これがないと、受け手側は何のデータか判断できません。

「Access-Control-Allow-Origin」はCORS対策です。

例えばクロームなのでは、同じドメインでなければ画像が開けない、という制約がありまして、それを判断しているのが、リクエストヘッダーのOriginと、レスポンスヘッダーのAccess-Control-Allow-Origin。

この二つの値が一致しないと画像はエラーで表示されなくなります。(ただしブラウザによる)

今回はChrome側のOriginがnullだったので、それに合わせました。

FireFoxとかだったらまた話が変わってくるかもしれない。

続いて、クライアント側。

サクッと作成するために、Vue.jsを使用しました。

使用しているライブラリはaxios、jQueryです。

Laravelと同じような構成にしました。

画面には<div id=”app”>と<image id=”camera”>のみです。

やっていることは単純で、画面が作成されたらcreatedメソッドが実行されて、onLoad()を周期的にコールするようにしています。

50という数字を小さくすればヌルヌルになりますし(ただしラズパイ側の負荷が大きくなる)、大きくすればカクカクになります。

今回はラズパイにクーラーつけているから大丈夫だけど、ラズパイZeroでこの負荷はやばいと思う。

そして、レスポンスの中のBase64をimageのsrcに入れれば画像が表示されます。

これを早いサイクルで実行・画像更新することで、動画のように見せることが出来ます。

今回はWebカメラみたいに仕上がりましたが、HTTPが使えるならばクライアントは何だってできます!

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

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

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

やり方は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のリモートデスクトップで表示させています。

https://taki-lab.site/bocci/wp-content/uploads/2020/09/PXL_20200917_011830774_Trim.mp4

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

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

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

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

装着。

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

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

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

$ raspistill -o image.jpg

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

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

$ raspivid -t 5000 -o test.h264
https://taki-lab.site/bocci/wp-content/uploads/2020/09/test.mp4

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

$ 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されるまで処理に待ったがかかります。

https://taki-lab.site/bocci/wp-content/uploads/2020/09/VID_20200903_091705_Trim.mp4

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

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

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

調べてみると、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__()

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