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>