LoginSignup
0
0

More than 3 years have passed since last update.

サーマルカメラ(サーモ AI デバイス TiD) Python Form編

Last updated at Posted at 2021-01-10

  1. 紹介編
  2. センサ編
  3. センサケース編
  4. Raspberry Pi編
  5. Python編
    5.1 Form編
    5.2 オムロン 非接触温度センサ D6T-44L-06編
    5.3 Pololu 測距センサ VL53L0X編
    5.4 BOSCH 温湿度・気圧センサ BME280
    5.5 シャットダウン・再起動スイッチ編
    5.6 OpenCV編
    5.7 高速化編

Python

はじめに

MH ソフトウェア & サービスの製品にはPythonが多用されています。ライブラリが豊富で、管理しやすく、コンパイルが不要の為、スマホでもプログラミングできます。
WebサイトもPythonで書かれています。
Pythonのバージョンは、2020/10/25時点で3.8.6となっています。

GUI

Pythonで使用できるGUI環境は多数ありますが、標準でインストールされるtkinterを多用しています。

test_form.py

MH ソフトウェア&サービスのFormクラスを使用するサンプルです。
320x160のウィンドウが(100,50)の位置に表示されます。
test_form.py (ZIPで圧縮済)

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from form import Form


class Root(Form):
    def __init__(self):
        super().__init__(
            #dialog=True,
            border=0,
            relief=None,
            #icon=icon_file,
            #minsize=minimum_size,
            name='main',
            position=['320', '160', '100', '50'],
            title='タイトル')


def main():
    root = Root()
    root.mainloop()


if __name__ == '__main__':
    main()

こんな感じのウィンドウです。
form.jpg

form.py

MH ソフトウェア&サービスでtkinterを使用する際のFormクラスです。
マウスイベント、UDP送受信機能の準備など共通機能を盛り込んでいます。
同じフォルダ内にudp.pyも置いてください。
form.py (ZIPで圧縮済)

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import threading
import tkinter as tk
from tkinter import ttk

if __name__ == '__main__':
    from udp import UDP
else:
    from .udp import UDP


class Form(tk.Tk):
    def __init__(self, *args, **kwargs):
        """
        Argument:
            dialog=<class 'bool'>
            icon=full_path
            hasmenu=True or False: Change height.
            master=object
            minsize=(width, height)
            name=<class "str"> 
            position=[width, height, left, top]
            title=<class "str">
            topmost=False
            udp_port=4000
            udp_bufsize=4096
        """
        if kwargs.get('toplevel', False):
            # Use for dialog new window.
            tk.Toplevel.__init__(self)
            self.resizable(0, 0)
            self.grab_set()
            # Focus to me.
            self.focus_set()
            # Not show taskbar.
            self.transient(self.master)
        else:
            super().__init__(
                #border=0,
                #relief=None,
            )

        if len(kwargs.get('minsize', ())) == 2:
            self.minsize(
                kwargs.get('minsize')[0],
                kwargs.get('minsize')[1])

        self.border_style = {'borderwidth': 0, 'relief': 'solid'}

        self._scaling = self._get_dpi(self) / 72
        self.tk.call('tk', 'scaling', self.scaling)
        #self.tk.call('tk', 'scaling', 0.5)

        # 96 is development PC's DPI. This value is default.
        self._screen_ratio = 96 / self._get_dpi(self)

        self.master = kwargs.get('master', None)
        self.title(kwargs.get('title', ''))
        self.name = kwargs.get('name', '')

        self._hasmenu = kwargs.get('hasmenu', False)
        if len(kwargs.get('position', [])) == 4:
            width = int(kwargs.get('position')[0])
            if self.minsize()[0] > width:
                width = self.minsize()[0]

            height = int(kwargs.get('position')[1])
            if self.minsize()[1] > height:
                height = self.minsize()[1]

            x = kwargs.get('position')[2]
            y = kwargs.get('position')[3]

            self.position = [width, height, x, y]

        if kwargs.get('icon', '') != '':
            self.icon = kwargs.get('icon', '')

        if kwargs.get('topmost', False):
            self.grab_set()

        if kwargs.get('dialog', False):
            self.resizable(0,0)

        self._attach_events()

        if kwargs.get('udp_port', '') != '':
            self.udp = UDP(self, *args, **kwargs)
            self.udp.receive.trace('w', self.udp_receive_changed)

        self.resize_event = True

    @property
    def host(self):
        result = socket.gethostbyname_ex(socket.gethostname())
        result = result[2][0]
        return result

    @property
    def icon(self):
        return self._icon
    @icon.setter
    def icon(self, value):
        self._icon = value
        self.tk.call('wm', 'iconphoto', self._w, tk.PhotoImage(file=self._icon))

    @property
    def position(self):
        return [
            self.winfo_width(),
            self.winfo_height(),
            self.winfo_x(),
            self.winfo_y()]
    @position.setter
    def position(self, value):
        """
        geometry=[width, height, left, top]
        """
        if self._hasmenu:
            value[1] += 20
        self.geometry(f'{value[0]}x{value[1]}+{value[2]}+{value[3]}')

    @property
    def resize_event(self):
        return self._resize_event
    @resize_event.setter
    def resize_event(self, value):
        self._resize_event = value
        if self._resize_event:
            self.bind('<Configure>', self._on_resize)
        else:
            self.unbind('<Configure>')

    @property
    def scaling(self):
        return self._scaling

    @property
    def screen_ratio(self):
        return self._screen_ratio

    def _attach_events(self):
        self.protocol('WM_DELETE_WINDOW', self.on_exit)

        self.bind('<Configure>', self._on_resize)
        self.unbind('<Configure>')

        self.bind('<MouseWheel>', self._mouse_wheel)
        self.bind('<Motion>', self._mouse_move)

        self.bind('<ButtonPress-1>', self._button1_press)
        self.bind('<ButtonRelease-1>', self._button1_release)

        self.bind('<ButtonPress-2>', self._button2_press)
        self.bind('<ButtonRelease-2>', self._button2_release)
        self.bind('<B2-Motion>', self._button2_move)

        self.bind('<ButtonPress-3>', self._button3_press)
        self.bind('<ButtonRelease-3>', self._button3_release)

        self.bind('<Expose>', self._expose)

    def _button1_press(self, *args):
        pass

    def _button1_release(self, *args):
        pass

    def _button2(self, *args):
        pass

    def _button2_move(self, event):
        pass

    def _button2_press(self, *args):
        pass

    def _button2_release(self, *args):
        pass

    def _button3(self, *args):
        pass

    def _button3_press(self, *args):
        pass

    def _button3_release(self, *args):
        pass

    def _expose(self, *args):
        pass

    def _get_dpi(self, window):
        MM_TO_IN = 1/25.4
        pxw = window.winfo_screenwidth()
        inw = window.winfo_screenmmwidth() * MM_TO_IN
        return int(pxw/inw)

    def _mouse_move(self, *args):
        pass

    def _mouse_wheel(self, event):
        sf = 1.0
        if event.delta < 0:
            sf = 0.9
        else:
            sf = 1.3
        pass

    def on_exit(self):
        self.destroy()

    def _on_resize(self, event):
        """
        override from child.
        This event is hook <"Configure">
        If size is not changed, through the function.
        """
        #if (self.geometry != self.geometry()):
        #    self.geometry = self.geometry()
        self.on_resize(event)

    def on_resize(self, event):
        # override from child.
        pass

    def udp_receive_changed(self, *args):
        """
        When UDP use, then override this function .
        """
        pass

def main():
    root = Form()
    #root.wm_attributes("-transparentcolor", "white")
    root.mainloop()


if __name__ == '__main__':
    main()

udp.py

UDP通信の共通クラスです。
threadで通信を監視します。
udp.py (ZIPで圧縮済)

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import socket
import threading
import tkinter as tk


class UDP():
    def __call__(self):
        return self.receive.get()

    def __init__(self, master, *args, **kwargs):
        self.binded = False

        self.error_message_enable = kwargs.get('error_message_enable', True)
        self.error = self.Error(self)

        self._port_changing = False

        self._port = int(kwargs.get('udp_port', '4000'))
        self._bufsize = int(kwargs.get('udp_bufsize', '4096'))

        self.receive = tk.StringVar(value='')

        self._sock = None
        self._start()

    def __new__(cls, master, *args, **kwargs):
        cls.master = master

        if hasattr(cls.master, 'root'):
            cls.root = cls.master.root
            if hasattr(cls.root, 'ini'):
                cls.ini = cls.root.ini

        if hasattr(cls.master, "parent"):
            cls.parent = cls.master.parent

        return super().__new__(cls)

    @property
    def bufsize(self):
        return self._bufsize
    @bufsize.setter
    def bufsize(self, value):
        self._bufsize = value

    class Error():
        def __init__(self, master):
            self.master = master
            self.root = self.master.root

            self.id = tk.IntVar(value=0)
            self.id.trace('w', self._error_changed)
            self.message = ''
            self.title = ''

        def _error_changed(self, *args):
            if self.master.error_message_enable:
                if self.id.get() > 0:
                    tk.messagebox.showwarning(self.title, self.message)

    def _start(self):
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self._sock.settimeout(0.1)
        self._socket_bind()

        self.thread = threading.Thread(
            name='udp{}{}'.format(self.port, self.binded),
            target=self._thread,
            )
        self.thread.setDaemon(True)
        self.thread.start()

    def _socket_bind(self):
        try:
            # host is ''.
            self._sock.bind(('', self.port))
        except Exception as e:
            if e.errno == 10048:
                title = (
                    lambda: 
                        'UDPポート異常'.format(self.port)
                    if lang() == 'ja_JP' else 
                        'Failed UDP port'.format(self.port)
                    )()
                msg = (
                    lambda: 
                        'ポート {} は使用されています'.format(self.port)
                    if lang() == 'ja_JP' else
                        'Port {} was used.'.format(self.port)
                    )()
            else:
                title = (
                    lambda: 
                        'その他のエラー'.format(self.port)
                    if lang() == 'ja_JP' else 
                        'Other error'.format(self.port)
                    )()
                msg = (
                    lambda: 
                        'ポート {} は使用されています'.format(self.port)
                    if lang() == 'ja_JP' else
                        'Port {} was used.'.format(self.port)
                    )()
            #tk.messagebox.showwarning(title, msg)
            self.error.message = msg
            self.error.title = title
            self.error.id.set(1)
            self.binded = False
        else:
            self.error.message = ''
            self.error.title = ''
            self.error.id.set(0)
            self.binded = True

    def _thread(self):
        while True:
            data, addr = None, None
            while self.binded:
                try:
                    if self._port_changing:
                        break
                    data, addr = self._sock.recvfrom(self._bufsize)
                    data = data.decode()
                    if (data is not None) & (addr is not None):
                        self.receive.set('{};{};{}'.format(
                            data, addr[0], addr[1]))
                        data, addr = None, None
                except Exception as e:
                    #print(e)
                    pass

            if self._port_changing:
                self._sock.close()
                del(self._sock)
                self._sock = None

                self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                self._sock.settimeout(0.1)
                self._socket_bind()

                self.thread.name = 'udp{}{}'.format(self.port, self.binded)
                self._port_changing = False

    def udp_reset(self):
        if type(self._sock) == socket.socket:
            self._sock.close()
        del(self._sock)
        self._sock = None

    @property
    def port(self):
        return self._port
    @port.setter
    def port(self, value):
        value = int(value)
        if value != self._port:
            self._port = value
            self._port_changing = True

def lang():
    """
    Sometime os don't has os.environ['LANG'].
    This function can not be use...
    """
    result = ""
    if ('LANG' in os.environ):
        result = os.environ.get('LANG')
        result = result.split('.')
        result = result[0]
        if result != 'ja_JP':
            result = 'other'
    else:
        result = 'other'
    return result

YouTube: サーマルカメラ(サーモ AI デバイス TiD) Python編
web: サーモ AI デバイス TiD Python Form編 (URLが変更されました)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0