pythonでQRコードを読み取ってHTMLで表示する

# 目的
[こちらの記事](https://expy-style.net/python/websocket-qrcodereader/)を参考に自分のPC環境でも動くものを作りたい。
html上に表示し、QRコードを読み取り、リアルタイムで状態を変化させたい。

# イメージ
![スクリーンショット 2022-06-03 20.34.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176158/85316e94-6c56-169b-940a-2c5339c90e38.png)

htmlで表示された表の状況の部分は、NGとなっています。QRコードをwebカメラで読み込むことでは、　データベースのstatusを書き換えます。すると、状況は、OKに変化します。

# プログラム
手順としては、　QRコードの作成、データベースの作成、pythonのプログラムの作成になります。プログラムは、githabにあげていますので、参考にしてください。
https://github.com/yutti/study/tree/main/QR_code

## QRコードの作成
今回作成した　QRコードは、github上においていますので、そちらを使用してください。QRコードの作成は、[文字表示付きQRコードの作成](https://qiita.com/yutaka_m/items/9ce55f82c9c0d42b0c3c)にまとめましたので、こちらを参考にしてください。

## データベースの作成
github上においてある```make_db.py```を実行するとデータベースを作成できます。データベースの作成方法について、[pythonでのsqlite3データベース作成](https://qiita.com/yutaka_m/items/adfa8ffdb88320151d70)にまとめましたので、こちらを参考にしてください。

## pythonのプログラムの作成
プログラムのベースは、最初に紹介した記事になります。プログラムを実行させると、webカメラが起動します。QRコードを読み取っていただくと、表の状況の部分が変わります。読み込みが終わりましたら、キーボードのqを押すと抜けることができます。表の状況を元の状態に戻したい場合は、```make_db.py```を実行していただくと、戻るようにしています。

```code_reader_websocket.py
import os.path
import json
import sqlite3
import webbrowser
import cv2
import winsound
import threading
from pyzbar.pyzbar import decode
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from websocket_server import WebsocketServer

class OrenoServer:

    def __init__(self):
        self.HOST = 'localhost'
        self.HTTP_PORT = 8080
        self.WS_PORT = 8081
        self.client = None     
        self.wss = WebsocketServer(host=self.HOST, port=self.WS_PORT)
        self.wss.set_fn_new_client(self.new_client)
        self.https = ThreadingHTTPServer((self.HOST, self.HTTP_PORT), HttpHandler)
        self.codes = []

    def start(self):
        threading.Thread(target=self.wss.run_forever).start()
        threading.Thread(target=self.https.serve_forever).start()
        webbrowser.open(f'http://{self.HOST}:{str(self.HTTP_PORT)}')

    def shutdown(self):
        self.wss.shutdown()
        self.https.shutdown()

    def new_client(self, client, server):
        if self.client is None:
            self.client = client
            threading.Thread(target=self.cam_capture).start()

    def cam_capture(self):
        cap = cv2.VideoCapture(0)
        font = cv2.FONT_HERSHEY_SIMPLEX
        while cap.isOpened():
            ret, frame = cap.read()

            if ret:
                d = decode(frame)

                if d:
                    for barcode in d:
                        barcode_data = barcode.data.decode('utf-8')

                        if barcode_data not in self.codes:
                            self.codes.append(barcode_data)
                            winsound.Beep(2000, 50)
                            font_color = (0, 0, 255)
                            self.wss.send_message(self.client, barcode_data)
                        else:
                            font_color = (0, 154, 87)

                        x, y, w, h = barcode.rect
                        cv2.rectangle(frame, (x, y), (x + w, y + h), font_color, 2)
                        frame = cv2.putText(frame, barcode_data, (x, y - 10), 
                                            font, .5, font_color, 2, cv2.LINE_AA)

            cv2.imshow('QRCODE READER  press q -> exit', frame)

            if cv2.waitKey(1) & 0xFF == ord('q'):
                db = OrenoDataBase()
                db.set(self.codes)
                db.close()
                self.shutdown()
                break


class OrenoDataBase:

    def __init__(self):
        self.conn = sqlite3.connect(r"./book_list.db", check_same_thread=False)
        self.conn.row_factory = sqlite3.Row
        self.cur = self.conn.cursor()

    def get(self):
        self.cur.execute('SELECT * FROM bookitems ORDER BY code')
        rows = []

        for r in self.cur.fetchall():
            rows.append({'name': r['name'], 'code': r['code'], 'status': r['status']})

        return rows

    def set(self, codes):
        place_holder = ','.join('?'*len(codes))
        values = tuple(codes)
        self.cur.execute(
            f'UPDATE bookitems SET status = TRUE WHERE code in ({place_holder})', values)
        self.conn.commit()

    def close(self):
        self.cur.close()
        self.conn.close()


class HttpHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        with open(r"./template.html", mode='r', encoding='utf-8') as html:
            response_body = html.read()
            self.send_response(200)
            self.send_header('Content-type', 'text/html; charset=utf-8')
            self.end_headers()
            self.wfile.write(response_body.encode('utf-8'))

    def do_POST(self):
        db = OrenoDataBase()
        rows = db.get()
        self.send_response(200)
        self.send_header('Content-type', 'application/json')
        self.end_headers()
        response_body = json.dumps(rows)
        self.wfile.write(response_body.encode('utf-8'))
        db.close()


server = OrenoServer()
server.start()
```

htmlは、下記のように設定しています。table classの設定が悪いのか、表の背景色を黒色に設定しても反映されなかったので、表の枠線が見えるものを用いてます。

```template.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <title>本のリスト</title>
</head>
<body>
<div class="container">
        <table class="table mt-5 table-bordered border-primary " id="tb">
        </table>
</div>
    <script>
        const HTTP_PORT = 8080
        const WS_PORT = 8081

        let wss = new WebSocket('ws://localhost:' + WS_PORT)
        wss.onmessage = function (e) {
            $('#'+ e.data).removeClass('bg-danger').addClass('bg-success').text('OK')
        }

        $.ajax({
            url: 'http://localhost:' + HTTP_PORT,
            type: 'POST',
            dataType: 'json',
        }).then(
            function (data) {
                let elem = '<tr><th>タイトル</th><th>識別コード</th><th>状況</th></tr>'

                
                $.each(data, function (key, item) {
                    let bc
                    let status
                    if(item.status === 1){
                        bc = 'bg-success'
                        status = 'OK'
                    }else{
                        bc = 'bg-danger'
                        status = 'NG'
                    }
                    elem += '<tr>'
                    elem += '<td>' + item.name + '</td>'
                    elem += '<td>' + item.code + '</td>'
                    elem += '<td class="' + bc + '" id="' + item.code +'">' + status + '</td>'
                    elem += '</tr>'
                })

                $('#tb').html(elem)
            })
    </script>
</body>
</html>
```