MLX90640のセンサー情報をWebSocketでブラウザから確認できるようにする

公開日時
更新日時

先日、MLX90640のセットアップを行いサンプルプログラムを動かすことができたので、今回はセンサー情報をWebブラウザから確認できるようにする。

pythonのサンプルとして rgb-to-gif.py が用意されていたので、これと Flask-SocketIOのexample を組み合わせて、サーマルセンサーのRGBバイナリデータをWebSocket経由でブラウザに送信し、ブラウザでバイナリデータを画像に変換しcanvasに表示することにした。

最初、サーバ側で画像変換を行いブラウザは画像を表示するだけにしてみたが遅延がひどかったので画像変換をブラウザで行う方針に切り替えた。

ブラウザで画像変換する場合は遅延なくセンサー情報を表示することができた。

  • requirements.txt
flask
eventlet
gevent-websocket
flask-socketio
  • app.py
#!/usr/bin/python -u

import subprocess
import io
import os
import signal
from threading import Lock
from flask import Flask, render_template, session, request, \
    copy_current_request_context
from flask_socketio import SocketIO, emit, join_room, leave_room, \
    close_room, rooms, disconnect

fps = 4                   # Should match the FPS value in examples/rawrgb.cpp 1, 2, 4, 8, 16, 32, 64
RAW_RGB_PATH = "/home/pi/mlx90640-library/examples/rawrgb"

# Set this variable to "threading", "eventlet" or "gevent" to test the
# different async modes, or leave it set to None for the application to choose
# the best option based on installed packages.
async_mode = None

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app, async_mode=async_mode)
thread = None
thread_lock = Lock()
user_count = 0
loop = True

def background_thread():
    global loop
    with subprocess.Popen(["sudo", RAW_RGB_PATH, "{}".format(fps)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as camera:
        while loop:
            socketio.sleep(1.0 / fps)
            frame = camera.stdout.read(2304) # BMP image body, 32 pixels * 24 rows * 3
            socketio.send(frame)
        loop = True

@app.route('/')
def index():
    return render_template('index.html', async_mode=socketio.async_mode)

@socketio.event
def disconnect_request():
    @copy_current_request_context
    def can_disconnect():
        disconnect()
    can_disconnect()

@socketio.event
def connect():
    global user_count
    global thread

    with thread_lock:
        if thread is None:
            print('start_background_task')
            thread = socketio.start_background_task(background_thread)

    user_count += 1
    print('Client connect', user_count)

@socketio.on('disconnect')
def test_disconnect():
    global thread
    global user_count
    global loop
    user_count -= 1
    if user_count == 0 and thread is not None:
        loop = False
        thread = None
    print('Client disconnected', request.sid, user_count)

if __name__ == '__main__':
    socketio.run(app, debug=False, host='0.0.0.0')
  • templates/index.html
<!DOCTYPE HTML>
<html>
<head>
    <title>MLX90640 Websocket</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.4/socket.io.js" integrity="sha512-aMGMvNYu8Ue4G+fHa359jcPb1u+ytAF+P2SCb+PxrjCdO3n3ZTxJ30zuH39rimUggmTwmh2u7wvQsDTHESnmfQ==" crossorigin="anonymous"></script>
    <script type="text/javascript" charset="utf-8">
        $(document).ready(function() {
            const socket = io()

            socket.on('connect', function() {
                window.onbeforeunload = function(e) {
                    socket.emit('disconnect_request')
                }
                console.log('connected')
            })

            const urlCreator = window.URL || window.webkitURL
            const img = document.getElementById('image')
            const ctx = img.getContext("2d")

            socket.on('message', function(message) {
                // convert RBG to RGBA
                const rawArray = new Uint8Array(message)
                const length = 32 * 24 * 4
                const tmp = new Uint8ClampedArray(length)
                let i = 0
                let s = 0
                while (i < length) {
                    tmp[i++] = rawArray[s++]
                    tmp[i++] = rawArray[s++]
                    tmp[i++] = rawArray[s++]
                    tmp[i++] = 255
                }
                const data = new ImageData(tmp, 32, 24)
                ctx.putImageData(data, 0, 0)
            })
        })
    </script>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
    <div style="width:240px;margin:0px auto;">
      <canvas id="image" width="32" height="24" style="width:320px;height:240px;image-rendering: pixelated;transform: rotate(90deg);transform-origin: top left;margin-left: 240px;" />
    </div>
</body>
</html>

参考


Related #raspberry pi