４桁７セグメントLEDにデータを出力するPython用制御クラスを作る

前回の投稿でHT16K33ドライバーモジュールを使ってユニバーサル基板に4桁7セグLEDを4個接続する方法を解説しました。
[18x6 LEDドライバーモジュールを使って4桁7セグメントLEDを4個接続する](https://qiita.com/pipito-yukio/items/ae27dd4d9e31a78b0b49)

今回はHT16K33ドライバーモジュールを使った4桁7セグメントLEDへの出力を pythonで制御する方法を解説します。

### 開発環境

+ OS: Ubuntu 22.04
+ python3.7: ソースコードからビルドしたpythonで仮想環境を作る
※1 実行環境がRaspberry Pi OS なので、そのOS上のpythonバージョンに合わせる
※2 インストールするライブラリは実行環境と同じ
+ Python IDE: Pycharm Community Edition for Linux
※1 実行環境は Headless OS のため Thonny Pyton IDE による開発はできない
※2 pythonスクリプトを直接実行することはできないが関数補完は可能

### 実行環境
+ Rasbpebrry Pi Zero WH
+ OS: Raspbian GNU/Linux 10 (buster) ※Headless OS
+ python: 3.7.3
    + 使用するライブラリ: pigpio
    ```pip install pigpio```

### データシート

#### (1) システムセットアップレジスタ(内部システム発振器)
+ 起動時 ON: 0x20 + 1
+ シャットダウン時 OFF: 0x20 + 0

![ht16k33_02_system_setup.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/de836789-890a-bab3-c87e-7d6dc4d92f73.jpeg)

#### (2) ディスプレーセットアップレジスタ
+ 起動時 ON: 0x80 + 1
+ シャットダウン時 OFF: 0x80 + 0

![ht16k33_04_display_setup.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/040883f2-d710-0716-d6b6-0a953ccc8868.jpeg)

#### (3) デジタル調光データ入力

![ht16k33_06_digital_dimming_data_input.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/8cdda695-b2a7-9101-2de1-e37bc220ce9a.jpeg)

#### (4)メモリレイアウト
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/7f4085d7-78d8-3e03-33c2-fd095e637915.jpeg">


## 制御クラスの構成

+ HT16K33モジュールを制御するベースクラス
GPIO制御用のPythonライブラリとして pigpio を使用
[pigpio library Python interface](https://abyz.me.uk/rpi/pigpio/python.html#i2c_open)
+ ４桁７セグメントLED表示制御クラス
ベースクラスを継承する

<img width="550" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/30396b5f-3d5b-3d51-b7b4-199a2385c4b5.jpeg">

## 1. HT16K33ドライバーモジュール制御クラス

データシートに示された各種設定を操作するクラス

(1) 定数、列挙クラス

+ パス番号 (BUS_NUM): 1
最近のラズパイのデフォルト。※この番号は変更可能なため定数として定義
+ デジタル調光の最低値 (BRIGHTNESS_BASE): 0xe0
    + Brightness (Enum)クラス
    ※4階調を定義

```ht16k33.py
BUS_NUM = 1
BRIGHTNESS_BASE = 0xe0


class Brightness:
    HIGH = 0x0f
    MID = 0x08
    LOW = 0x04
    DIM = 0x02
```

(2) コンストラクタ

+ 引数
    + pi: pigpio オブジェクト
    + slave_addr: I2Cデバイス (スレーブ) アドレス
    + brightness: 接続デバイスの初期調光値
+ 処理内容
    + I2Cデバイス(キャラクタ・デバイス)ハンドルのオーブン
    //```crw-rw---- 1 root i2c      89,   1 12月 24 14:29 /dev/i2c-1```
    オープンしたI2Cデバイスハンドルをオブジェクトに保持する
    + システムセットアップ **ON** (**0x21**)
    + ディスプレーセットアップ **ON** (**0x81**)
    + 接続デバイスの初期調光値を設定

```python
class HT16K33:
    MEM_LAST = 0x0f

    def __init__(self, pi, slave_addr, bus_number=BUS_NUM, brightness=Brightness.MID):
        self.pi = pi
        self.i2c_handle = pi.i2c_open(bus_number, slave_addr)
        self.opened = True
        # System setup: Turn on System oscillator(発振器) [0x20 + 1(on)]
        self.pi.i2c_write_byte(self.i2c_handle, 0x21)
        # Display setup: Display on [0x80 + 1(on)]
        self.pi.i2c_write_byte(self.i2c_handle, 0x81)
        # Brightness: [0xe0 + f{1,0,0,0}]
        self.pi.i2c_write_byte(self.i2c_handle, (BRIGHTNESS_BASE | brightness))
```

(3) クリーンアップ

+ 処理内容
    + メモリクリア
    + ディスプレーセットアップ **OFF** (**0x80**)
    + システムセットアップ **OFF** (**0x20**)
    + オブジェクトのI2Cデバイスハンドルをクローズ

```python
    def cleanup(self):
        # Poweroffでもメモリーの値は残っている
        self.clear_memory()
        # Display setup: Display off
        self.pi.i2c_write_byte(self.i2c_handle, 0x80)
        # System setup: Turn on System oscillator off
        self.pi.i2c_write_byte(self.i2c_handle, 0x20)
        self.pi.i2c_close(self.i2c_handle)
        self.opened = False
```

(4) 調光値の変更

任意の時点でデバイスの調光値を変更する

```python
    def set_brightness(self, brightness):
        self.pi.i2c_write_byte(self.i2c_handle, (BRIGHTNESS_BASE | brightness))
```

(5) メモリクリア
メモリ(バイト単位)に [0x0] を設定する
※デバイスが表示器の場合は消灯

```python
    def clear_memory(self):
        for m_addr in range(self.MEM_LAST + 1):
            self.pi.i2c_write_byte_data(self.i2c_handle, m_addr, 0x0)
```

(6) データ転送関数

指定されたメモリの開始アドレスに N 桁分のデータを設定する
※1 想定する最大桁数は４桁分
※2 前回送信したアドレスに**２バイト分加算したアドレス**が次の送信アドレスになる

```python
    def send_data(self, start_reg, datas):
        # 送信データを下位メモリから送信する
        reg = start_reg
        for i in range(len(datas)):
            val = datas[i]
            self.pi.i2c_write_byte_data(self.i2c_handle, reg, val)
            reg += 0x02
```

(7) 全ソースの再掲

※ログ出力処理を含んでいます。

```lib/ht16k33.py
import logging

"""
LEDドライバモジュール(HT16K33)制御クラス for pigpio
"""

BUS_NUM = 1
BRIGHTNESS_BASE = 0xe0


class Brightness:
    HIGH = 0x0f
    MID = 0x08
    LOW = 0x04
    DIM = 0x02


class HT16K33:
    MEM_LAST = 0x0f

    def __init__(self, pi, slave_addr, bus_number=BUS_NUM, brightness=Brightness.MID, logger=None):
        self.pi = pi
        # i2c_handle = /dev/self.i2c_bus-1
        self.i2c_handle = pi.i2c_open(bus_number, slave_addr)
        self.logger = logger
        self.debug_once = logger is not None and (logger.getEffectiveLevel() <= logging.DEBUG)
        if logger is not None:
            logger.info("i2c_handle: {}".format(self.i2c_handle))
        self.opened = True
        # System setup: Turn on System oscillator(発振器) [0x20 + 1(on)]
        self.pi.i2c_write_byte(self.i2c_handle, 0x21)
        # Display setup: Display on [0x80 + 1(on)]
        self.pi.i2c_write_byte(self.i2c_handle, 0x81)
        # Brightness: [0xe0 + f{1,0,0,0}]
        self.pi.i2c_write_byte(self.i2c_handle, (BRIGHTNESS_BASE | brightness))

    def clear_memory(self):
        for m_addr in range(self.MEM_LAST + 1):
            self.pi.i2c_write_byte_data(self.i2c_handle, m_addr, 0x0)

    def debug_memory(self):
        for m_addr in range(0x0f + 1):
            r_data = self.pi.i2c_read_byte_data(self.i2c_handle, m_addr)
            if self.debug_once:
                self.logger.debug("r_data[{:#04x}]: {:#04x}".format(m_addr, r_data))

    def cleanup(self):
        # Poweroffでもメモリーの値は残っている
        self.clear_memory()
        # Display setup: Display off
        self.pi.i2c_write_byte(self.i2c_handle, 0x80)
        # System setup: Turn on System oscillator off
        self.pi.i2c_write_byte(self.i2c_handle, 0x20)
        self.pi.i2c_close(self.i2c_handle)
        self.opened = False

    # クリーンアップ途中でクローズに失敗した場合に単独で実行
    def force_close(self):
        if self.opened:
            self.pi.i2c_close(self.i2c_handle)

    def send_data(self, start_reg, datas):
        # 送信データを下位メモリから送信する
        reg = start_reg
        for i in range(len(datas)):
            val = datas[i]
            self.pi.i2c_write_byte_data(self.i2c_handle, reg, val)
            reg += 0x02

    def set_brightness(self, brightness):
        self.pi.i2c_write_byte(self.i2c_handle, (BRIGHTNESS_BASE | brightness))
```

## 2. ４桁７セグメントLED出力制御

### 2-1. 出力仕様

#### 2-1-1. 出力文字に対応する16進表現

![7Seg_16numbers.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/b8431d87-f9c0-2cd3-b1b9-9f68fd48dc10.jpeg)

#### 2-1-2. ４桁７セグメントLEDの配線位置と出力する桁に対応するメモリ構造

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/38b82005-01db-50c4-1d91-00d552eed1a8.jpeg">

### 2-2. ４桁７セグメントLED出力制御クラス

+ カソードコモンのみを対象とする
+ 数値出力: マイナスを含め４桁
    + 整数: -999 〜 9999
    + 実数 (小数点第1位固定): -99.9 〜 999.9 

※ デバック用ログ出力は省略

#### 2-2-1. インポートと他のクラス定義

+ LEDのカソード・アノード区分 Enumクラス定義 (LEDCommon)
+ 出力する４桁７セグメントLEDの番号 Enumクラス定義 (LEDNumber)

```led4digit7seg.py
import logging
from enum import Enum
from .ht16k33 import HT16K33, BUS_NUM


class LEDCommon(Enum):
    CATHODE = 0
    ANODE = 1


class LEDNumber(Enum):
    N1 = 0
    N2 = 1
    N3 = 2
    N4 = 3
```

#### 2-2-2. クラス外の関数定義

(1) 整数値を数字の逆順リストに変換する処理

```python
DIGIT = 4

def _make_digits(number):
    """
    数値を数字のリストに変換する (リストは逆順になる)
    (例) 123 ->  [3, 2, 1]
    :param number: 数値(最大4桁の整数 or マイナスの場合は3桁)
    :return: 数値を逆順にした数字のリスト
    """
    digits = []
    num, mod = number, 0
    for _ in range(DIGIT):
        num, mod = divmod(num, 10)
        digits.append(mod)
        if num == 0:
            break
    return digits
```

上記処理で生成される数値リストのイメージは下記のようにになります。

![makeData_1_int_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/9bca3062-f963-f696-7a68-e503400f139d.jpeg)

#### 2-2-3. ４桁７セグメントLED制御クラス

(1) クラスの定数定義

+ 数字の16進数表現配列: SEG_CHAR
+ ハイフン(マイナス)の16進数表現: SEG_MINUS
+ エラー表示用('EEEE')の16進数表現: SEG_ERRORS
+ 初期表示用('----')の16進数表現: SEG_OUTOFRANGE
+ 各4桁7セグメントLEDに出力するメモリ開始アドレス
REG_LED1 〜 REG_LED4
+ カソードコモンLEDのメモリアドレスマップ: STAREG_CATHODE

```python
class LED4digit7Seg(HT16K33):
    # 表示文字定義: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    SEG_CHAR = [0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x67]
    # ハイフン:'-'
    SEG_MINUS = 0x40
    # エラー:'EEEE'
    SEG_ERRORS = [0x79] * DIGIT
    # 測定不能:'----'
    SEG_OUTOFRANGE = [SEG_MINUS] * DIGIT
    # メモリ開始アドレス
    REG_LED1 = 0x00
    REG_LED2 = 0x08
    REG_LED3 = 0x01
    REG_LED4 = 0x09
    # メモリ開始アドレスマップ
    STAREG_CATHODE = {LEDNumber.N1: REG_LED1, LEDNumber.N2: REG_LED2, LEDNumber.N3: REG_LED3, LEDNumber.N4: REG_LED4}
```

(2) コンストラクタ

+ 引数
    + pi: pigpio オブジェクト
    + slave_addr: I2Cデバイス (スレーブ) アドレス
    + brightness: 接続デバイスの初期調光値
    + ７セグメントLEDのコモン種別
+ 処理内容
    + スーパークラス(HT16K33)のコンストラクタ呼び出し
    + オブジェクトに７セグメントLEDのコモン種別を保持

```python
    def __init__(self, pi, slave_addr, bus_number=BUS_NUM, brightness=None,
                  common=LEDCommon.CATHODE):
        super().__init__(pi, slave_addr, bus_number, brightness=brightness)
        self.common = common
```

(3) メモリに転送するデータの初期化
※整数データ、実数データ共通

```python
    datas = [0] * DIGIT // DIGIT=4
```

※４桁７セグメントLEDの全ての桁が消灯となる
![makeData_1_int_2_data_init.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/774ed39b-06ef-0b15-3286-a23cac25c957.jpeg)


##### 2-2-3-1. 整数データの転送用データ変換処理

(1) 数字の逆順リストから転送用データを生成

+ 処理手順
    + 転送用データの初期化
    + 正の数値リストをメモリ転送用データの4桁目から格納
    + 元の整数値がマイナスの場合
        + 最後のデータの１つ前 (先頭側) にマイナスの16進表現を設定 

```python
    def _make_int_datas(self, digits, has_minus=False):
        # カソードコモンLED用
        datas = [0] * DIGIT
        # 数値列(入力値の逆順で4桁目が先頭)を送信データの4桁目から格納する
        elem = DIGIT - 1
        for i in range(len(digits)):
            datas[elem] = self.SEG_CHAR[digits[i]]
            elem -= 1
        # 整数値がマイナスのケースに対応
        if has_minus:
            # 正数部分は１桁少ない
            elem -= 1
            datas[elem] = self.SEG_MINUS
        return datas
```

(2) 整数値を転送用データに変換する

+ 処理手順
    + 整数値がマイナスの場合
        + マイナスフラグをONにセット
        + 正の整数値に変換 
    + 正の整数値を数字の逆順リストに変換
    + 数字の逆順リストからメモリ転送用データを生成する

```python
    def _generate_int_datas(self, number):
        is_minus = False
        if number >= 0:
            val = number
        else:
            # マイナスなら絶対値にしてマイナスフラグをTrueに設定
            val = abs(number)
            is_minus = True
        digits = _make_digits(val)
        datas = self._make_int_datas(digits, has_minus=is_minus)
        return datas
```

この処理で生成される転送用データの表示イメージは以下のようになります。

<img width="280" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/f5c50b07-2617-f534-fa0e-4bba937af81c.jpeg">

<img width="580" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/f4e32ff6-fdb8-11e4-e2f2-d59e27ed1cc2.jpeg">

<img width="580" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/329bfcd4-c222-6845-8b68-57c797df2225.jpeg">

(3) 整数値の出力処理

メインのアプリケーションから呼び出される

+ 引数
    + 整数データ: int_val
    + 出力するLED番号: led_num 
+ 処理内容
    + 数値チェック
        + 範囲外の数値
            + 範囲外(オールハイフン)のメモリ転送用データを生成
        + 有効な数値
            + 数値データをメモリ転送用データに変換する
    + LED番号に対応する開始アドレス (カソードコモン用) を取得する
    + 開始アドレスとメモリ転送用データをHT16K33オブジェクトに設定
    ※指定したLED番号の４桁７セグメントLEDに数値が表示される

```python
    def printInt(self, int_val, led_num=LEDNumber.N1):
        if int_val < -999 or int_val > 9999:
            datas = self.SEG_OUTOFRANGE
        else:
            datas = self._generate_int_datas(int_val)
        stareg = self.STAREG_CATHODE[led_num]
        self.send_data(stareg, datas)
```

##### 2-2-3-2. 実数データの転送用データ変換処理

+ 処理手順
    + 空の仮データリストを生成
    + 数値がマイナスの場合
        + 仮データリストにマイナスの16進数表現をセット
        + 正の実数に変換 
    + 数値を小数点第１位で四捨五入してから文字列に変換
    + 文字列の長さ分処理を繰り返す
        + ドットが出現した場合
            + 前の数値 (16進数表現) に128をプラスする
            + 次の文字を処理する
        + 数値の文字列に対応する16進数表現を仮データリストに追加する
    + ドットが出現しなかった場合
        + 仮データリスト末尾の16進数表現に128をプラスする
        + 小数点以下のゼロを補う
    + 転送用データの初期化
    + 仮データリストを転送用データに逆順で格納しなおす

```python
    def _generate_float_datas(self, float_val):
        t_datas = []
        # 数値が負なら先頭にマイナスのLED表現をセット
        if float_val < 0:
            t_datas.append(self.SEG_MINUS)
            # マイナス符号を除去
            float_val *= -1
        # 浮動小数点の場合は文字列に変換 ※小数点1桁で4捨5入
        s_val = str(round(1.0 * float_val, 1))
        has_dot = False
        for s in list(s_val):  # 数値文字列をリストに変換
            if s == '.':
                # ドットが出現したら前の数値に128をプラス
                t_datas[len(t_datas) - 1] += 128
                has_dot = True
                continue

            # ドット以外は数値
            t_datas.append(self.SEG_CHAR[ord(s) - 48])  # '0': 48, '1':49, ...

        # ドットの出現有無判定
        if not has_dot:
            # 出現しなかったら配列末尾の数値表現+128
            t_datas[len(t_datas) - 1] += 128
            # 小数点以下のゼロを補う
            t_datas.append(self.SEG_CHAR[0])

        # 出力用4桁バッファに逆順に格納しなおす
        datas = [0] * DIGIT
        pos = DIGIT
        for i in range(len(t_datas) - 1, -1, -1):
            pos -= 1
            datas[pos] = t_datas[i]
        return datas
```

この処理で生成される転送用データの表示イメージは以下のようになります。

<img width="280" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/d66b5c53-27b9-3590-a828-9dc403fb2075.jpeg">

<img width="580" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/35bd0ff3-28e5-b1ab-7739-949ed530085d.jpeg">

<img width="580" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/7b4e555a-4946-5625-ac06-4e0913588de8.jpeg">


(2) 実数値の出力処理

メインのアプリケーションから呼び出される

+ 引数
    + 実数データ: float_val ※小数点第1位固定
    + 出力するLED番号: led_num 
+ 処理内容
    + 数値チェック
        + 範囲外の数値
            + 範囲外(オールハイフン)のメモリ転送用データを生成
        + 有効な数値
            + 数値データをメモリ転送用データに変換する
    + LED番号に対応する開始アドレス (カソードコモン用) を取得する
    + 開始アドレスとメモリ転送用データをHT16K33オブジェクトに設定
    ※指定したLED番号の４桁７セグメントLEDに数値が表示される

```python
    def printFloat(self, float_val, led_num=LEDNumber.N1):
        if float_val < -99.9 or float_val > 999.9:
            datas = self.SEG_OUTOFRANGE
        else:
            datas = self._generate_float_datas(float_val)
        stareg = self.STAREG_CATHODE[led_num]
        self.send_data(stareg, datas)
```


(3) 全ソース再掲

※1 ログ出力処理を含んでいます。
※2 アノードコモン用のLED出力 (最大2個) にも対応してます。

```lib/led4digit7seg.py
import logging
from enum import Enum
from .ht16k33 import HT16K33, BUS_NUM

"""
LEDドライバモジュール(HT16K33)を使った4桁7セグメントLED表示ライブラリ for pigpio
最大4個まで出力
[出力仕様]
(1) 整数値: -999 〜 9999
(2) 実数値: -99.9 〜 999.9 ※小数点第1位
[部品]
 4桁7セグLED x 1〜4個
 16x8マトリクスLEDドライバモジュール(HT16K33) x 1個
"""

DIGIT = 4


def _make_digits(number):
    """
    数値を数字のリストに変換する (リストは逆順になる)
    (例) 123 ->  [3, 2, 1]
    :param number: 数値(最大4桁の整数 or マイナスの場合は3桁)
    :return: 数値を逆順にした数字のリスト
    """
    digits = []
    num, mod = number, 0
    for _ in range(DIGIT):
        num, mod = divmod(num, 10)
        digits.append(mod)
        if num == 0:
            break
    return digits


def _to_bins(val):
    """
    数値の2進数計算 ※アノードコモンのみ
    :param val:数値
    :return: 2進数のリスト
    """
    # 配列は8bit分初期化
    bins = [0] * 8
    num, mod = val, 0
    for i in range(8):
        num, mod = divmod(num, 2)
        bins[i] = mod
        if num == 0:
            break
    # ビット計算用に逆順にする
    return bins[::-1]


class LEDCommon(Enum):
    CATHODE = 0
    ANODE = 1


class LEDNumber(Enum):
    N1 = 0
    N2 = 1
    N3 = 2
    N4 = 3


class LED4digit7Seg(HT16K33):
    # 表示文字定義: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    SEG_CHAR = [0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x67]
    # ハイフン:'-'
    SEG_MINUS = 0x40
    # エラー:'EEEE'
    SEG_ERRORS = [0x79] * DIGIT
    # 測定不能:'----'
    SEG_OUTOFRANGE = [SEG_MINUS] * DIGIT
    # [LED1] DIG1:COM0(0x00),DIG2:COM1(0x02),DIG3:COM2(0x04),DIG4:COM3(0x06)
    #        ROW7(dp[3]), ROW6(g[5]), ROW5(f[10]), ROW4(e[1]), ROW3(d[2]), ROW2(c[4]), ROW1(b[7]), ROW0(a[11])
    REG_LED1 = 0x00
    # [LED2] DIG1:COM4(0x08),DIG2:COM5(0x0A),DIG3:COM6(0x0C),DIG4:COM7(0xEH)
    #        ROW7, ROW6(D6),..., ROW1, ROW0
    REG_LED2 = 0x08
    # [LED3] DIG1:COM0(0x01),DIG2:COM1(0x03),DIG3:COM2(0x05),DIG4:COM3(0x07)
    #        ROW15(dp[3]), ROW14(g[5]), ROW13(f[10]), ROW12(e[1]), ROW11(d[2]), ROW10(c[4]), ROW9(b[7]), ROW8(a[11])
    REG_LED3 = 0x01
    # [LED4] DIG1:COM4(0x09),DIG2:COM5(0x0B),DIG3:COM6(0x0D),DIG4:COM7(0xFH)
    #        ROW15, ROW14,..., ROW9, ROW8
    REG_LED4 = 0x09
    # アノードコモン
    REG_ANODE_LED1 = REG_LED1
    REG_ANODE_LED2 = REG_LED3
    # メモリ開始アドレスマップ
    STAREG_CATHODE = {LEDNumber.N1: REG_LED1, LEDNumber.N2: REG_LED2, LEDNumber.N3: REG_LED3, LEDNumber.N4: REG_LED4}
    STAREG_ANODE = {LEDNumber.N1: REG_ANODE_LED1, LEDNumber.N2: REG_ANODE_LED2}

    def __init__(self, pi, slave_addr, bus_number=BUS_NUM, common=LEDCommon.CATHODE,
                 brightness=None, logger=None):
        super().__init__(pi, slave_addr, bus_number, brightness=brightness, logger=logger)
        self.common = common
        self.logger = logger
        self.debug_once = logger is not None and (logger.getEffectiveLevel() <= logging.DEBUG)

    def _make_int_datas(self, digits, has_minus=False):
        # カソードコモンLED用
        datas = [0] * DIGIT
        # 数値列(入力値の逆順で4桁目が先頭)を送信データの4桁目から格納する
        elem = DIGIT
        for i in range(len(digits)):
            elem -= 1
            datas[elem] = self.SEG_CHAR[digits[i]]
        # 整数値がマイナスの場合は先頭側にマイナスの16進数表現を追加
        if has_minus:
            elem -= 1
            datas[elem] = self.SEG_MINUS
        return datas

    def _convert_anode(self, cathode_datas):
        # カソードコモンLED用データをアノードコモンLED用に変換
        datas = [0] * 8
        for row in range(len(cathode_datas)):
            val = cathode_datas[row]
            val_bins = _to_bins(val)
            if self.debug_once:
                self.logger.debug("cathode_datas[{0}]: ({1}) - {1:#04x}".format(row, val))
                self.logger.debug("val_bins: {}".format(val_bins))
            # 8bit分: D7,D6,D5,D4, D3, D2, D1, D0
            #          0, 0, 0, 0, <<3,<<2,<<1,<<0
            for com_j in range(8):
                datas[com_j] = datas[com_j] | (val_bins[com_j] << (3 - row))
        return datas

    def _generate_int_datas(self, number):
        is_minus = False
        if number >= 0:
            val = number
        else:
            # マイナスなら絶対値にしてマイナスフラグをTrueに設定
            val = abs(number)
            is_minus = True
        # 数値を数字の逆順配列に変換する
        digits = _make_digits(number)
        if self.debug_once:
            self.logger.debug("number: {} -> digits: {}".format(number, digits))
        # ドライバーモジュールのメモリ形式に変換
        datas = self._make_int_datas(digits, has_minus=is_minus)
        if self.debug_once:
            self.logger.debug("datas: {}".format(datas))
        if self.common == LEDCommon.ANODE:
            # アノードコモンLEDに変換
            datas = self._convert_anode(datas)
        if self.debug_once:
            for i, data in enumerate(datas):
                self.logger.debug("datas[{}]: {:#04x}".format(i, data))
        return datas

    def _generate_float_datas(self, float_val):
        if self.debug_once:
            self.logger.debug("float_val: {}".format(float_val))

        t_datas = []
        # 数値が負なら先頭にマイナスのLED表現をセット
        if float_val < 0:
            t_datas.append(self.SEG_MINUS)
            # マイナス符号を除去
            float_val *= -1
        # 浮動小数点の場合は文字列に変換 ※小数点1桁で4捨5入
        s_val = str(round(1.0 * float_val, 1))
        has_dot = False
        for s in list(s_val):  # 数値文字列をリストに変換
            if s == '.':
                # ドットが出現したら前の数値に128をプラス
                t_datas[len(t_datas) - 1] += 128
                has_dot = True
                continue

            # ドット以外は数値
            t_datas.append(self.SEG_CHAR[ord(s) - 48])  # '0': 48, '1':49, ...

        # ドットの出現有無判定
        if not has_dot:
            # 出現しなかったら配列末尾の数値表現+128
            t_datas[len(t_datas) - 1] += 128
            # 小数点以下のゼロを補う
            t_datas.append(self.SEG_CHAR[0])

        if self.debug_once:
            for i in range(len(t_datas)):
                self.logger.debug("t_datas[{}]: {:#04x}".format(i, t_datas[i]))
        # 出力用4桁バッファに逆順に格納しなおす
        datas = [0] * DIGIT
        pos = DIGIT
        for i in range(len(t_datas) - 1, -1, -1):
            pos -= 1
            datas[pos] = t_datas[i]
        if self.debug_once:
            for i in range(len(datas)):
                self.logger.debug("datas[{}]: {:#04x}".format(i, datas[i]))
        return datas

    def printFloat(self, float_val, led_num=LEDNumber.N1):
        if float_val < -99.9 or float_val > 999.9:
            datas = self.SEG_OUTOFRANGE
            if self.logger is not None:
                self.logger.warning("float value is out of range: {}".format(float_val))
        else:
            datas = self._generate_float_datas(float_val)
            if self.common == LEDCommon.ANODE:
                # アノードコモン: アノードコモン用のデータに変換
                datas = self._convert_anode(datas)
        stareg = self.STAREG_CATHODE[led_num] if self.common == LEDCommon.CATHODE else self.STAREG_ANODE[led_num]
        self.send_data(stareg, datas)

    def printInt(self, int_val, led_num=LEDNumber.N1):
        if int_val < -999 or int_val > 9999:
            datas = self.SEG_OUTOFRANGE
            if self.logger is not None:
                self.logger.warning("int value is out of range: {}".format(int_val))
        else:
            datas = self._generate_int_datas(int_val)
        stareg = self.STAREG_CATHODE[led_num] if self.common == LEDCommon.CATHODE else self.STAREG_ANODE[led_num]
        self.send_data(stareg, datas)

    def printOutOfRange(self, led_num=LEDNumber.N1):
        """
        範囲外: '----'
        :param led_num: 出力先LED
        """
        stareg = self.STAREG_CATHODE[led_num] if self.common == LEDCommon.CATHODE else self.STAREG_ANODE[led_num]
        self.send_data(stareg, self.SEG_OUTOFRANGE)

    def printError(self, led_num=LEDNumber.N1):
        """
        測定値エラー: 'EEEE'
        :param led_num: 出力先LED
        """
        stareg = self.STAREG_CATHODE[led_num] if self.common == LEDCommon.CATHODE else self.STAREG_ANODE[led_num]
        self.send_data(stareg, self.SEG_ERRORS)
```

## 3. メインアプリケーション

下記のように出力する Ptyhonアプリケーションのコードを示します。
※1 １分後に全ての４桁７セグメントLEDが消灯して終了
※2 エラー処理は省略

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/a8cadfc8-c93e-42ce-c396-3322cf6bff58.jpeg">

```python
import logging
from lib.led4digit7seg import LED4digit7Seg, LEDCommon, LEDNumber
from log import logsetting


if __name__ == '__main__':
    logging.basicConfig(format='%(levelname)s %(message)s')
    app_logger = logging.getLogger(__name__)
    app_logger.setLevel(level=logging.DEBUG)

    # pigpioオブジェクト生成
    pi = pigpio.pi()
    # 本来はここで pigpioサービスが起動されているかチェックする
    
    i2c_slave_addr = 0x70
    # カソードコモンを指定
    led_common = LEDCommon.CATHODE
    led_driver = LED4digit7Seg(pi, i2c_slave_addr, common=led_common, logger=logger)
    try:
        led_driver.printFloat(26.7, led_num=LEDNumber.N1)
        led_driver.printFloat(20.5, led_num=LEDNumber.N2)
        led_driver.printFloat(56.7, led_num=LEDNumber.N3)
        led_driver.printInt(1002, led_num=LEDNumber.N4)
        
        # 1分後に終了
        time.sleep(60.0)
    except KeyboardInterrupt:
        pass
    finally:
        led_driver.cleanup()
    logger.info("Finished!")    
```

参考までに上記画面のpythonアプリケーションのソースを下記GitHubで公開しています。

気象データ表示板の制御プログラム (Pythonソースコード)
 [Github@pipito-yukio/home_weather_sensors/raspi_zero/bin/pigpio](https://github.com/pipito-yukio/home_weather_sensors/tree/master/raspi_zero/bin/pigpio)