「AE2100」のGPIO制御ライブラリーをpythonで呼び出して接点制御してみよう！

# 要約
* 本記事では、AIエッジコンピューター「AE2100」の標準コンテナ付属のGPIO制御ライブラリーをpythonで呼び出し、接点でパトランプを制御する方法を説明します。

# はじめに
AE2100には、産業用向けの物理インターフェースとして、接点(IN ×1、OUT ×1)が標準で搭載されております。接点を利用することで、外部のIoT機器からの信号を受け取ることをトリガーにAIの推論を実行したり、AIの推論の結果によって外部のIoT機器を制御することができます。
AE2100上で接点を利用するためには、標準コンテナに付属されているGPIO制御ライブラリーを呼び出す必要があります。しかし、GPIO制御ライブラリーはC言語で作成されているため、pythonで呼び出すためには、ライブラリーを読み込む必要があります。

本記事では、標準コンテナに付属されているGPIO制御ライブラリーをpythonで呼び出し、接点(パトランプ)を制御する方法を説明します。

# 環境
【AE2100】
本体ファーム：V3.6.0（HDDL Daemon：OpenVINO2021.4.1 LTS）
標準コンテナ：OpenVINO有（Ubuntu20.04版）2021年10月版　(python：V3.8.10付属)
※OpenVINO無（Ubuntu版）2021年10月版の標準コンテナでも動作可能です。

また、[OKIの公式HP](https://www.oki.com/jp/AIedge/support/)または、[購入者限定のAIエッジユーザーサイト](https://www.oki-partner.net/aieu/Login)

[パトランプ](https://www.patlite.co.jp/support/Discontinued%20Product.html)(LES-02AW)は、リード線を以下のように配線します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/61cb53ad-b353-de4c-9f4b-e9a60f1456db.png)
なお、[OKIの公式HP](https://www.oki.com/jp/AIedge/support/)または、[購入者限定のAIエッジユーザーサイト](https://www.oki-partner.net/aieu/Login)から「「AE2100　SDK取扱説明書（共通編）(2022年4月版)」をダウンロードし、マニュアル記載のサンプルコード(C言語プログラム)は正常に動作していることを前提とします。
サンプルコードが動作しない場合は、[AIエッジユーザーサイト](https://www.oki-partner.net/aieu/Login)のFAQをご確認いただくか、お問い合わせください。




# 準備
TeraTermでAE2100にログインし、標準コンテナを起動します。
また、標準コンテナ上に「gpio」ディレクトリをホームディレクトリ直下に作成します。
```
root@ae2100:~#docker start ubuntu-openvino
root@ae2100:~#docker exec -it ubuntu-openvino /bin/bash
root@254b1cabd362:~#cd
root@254b1cabd362:~#mkdir gpio
root@254b1cabd362:~#cd gpio
```




# サンプルコードの用意
[OKIの公式HP](https://www.oki.com/jp/AIedge/support/)または、[購入者限定のAIエッジユーザーサイト](https://www.oki-partner.net/aieu/Login)から「AE2100　SDK取扱説明書（共通編）(2022年4月版)」をダウンロードして、「5.1 接点入力 > 接点入出力処理サンプルコード」に記載されているサンプルコードを参考に、[viコマンド](https://qiita.com/m-m00/items/a042bf9d5c7c85a64895)で「sample.c」を作成します。


<details><summary><font color="red">ここをクリックしてサンプルコード(sample.c)を表示</font></summary><div>

```diff_c
/* -------------------------------------------------------------------------------- */
/*! gpioSample1.c
 * All rights reserved, Copyright (c) 2022-2022 Oki Electric Industry Co., Ltd.
 * サンプルプログラムです。 自己責任でご利用ください。
 */
/* -------------------------------------------------------------------------------- */
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <ae_gpiolib_sdk.h>
#include "./Sample.h"

#define  OK  (0)
#define  ERR (-1)
#define  LOGE( ... )  fprintf( stderr, __VA_ARGS__ ); fputc( '\n', stderr ); fflush( stderr )
#define  LOGD( ... )  fprintf( stderr, __VA_ARGS__ ); fputc( '\n', stderr )

/* ============================================================
 * mutex & cond
 * ============================================================ */
typedef struct tag_Sync
{
    pthread_cond_t   cond;
    pthread_mutex_t  mutex;
} Sync;

void syncInit( Sync* sync )
{
    pthread_condattr_t  condAttr;
    pthread_condattr_init( &condAttr );
    pthread_condattr_setclock( &condAttr, CLOCK_MONOTONIC );
    pthread_cond_init( &sync->cond, &condAttr );
    pthread_mutex_init( &sync->mutex, NULL );
}

void syncFin( Sync* sync )
{
    pthread_mutex_destroy( &sync->mutex );
    pthread_cond_destroy( &sync->cond );
}

#define  syncLock( sync )           pthread_mutex_lock     ( &((sync)->mutex))
#define  syncUnlock( sync )         pthread_mutex_unlock   ( &((sync)->mutex))
#define  syncWait( sync )           pthread_cond_wait      ( &((sync)->cond), &((sync)->mutex) )
#define  syncTimedWait( sync, ts )  pthread_cond_timedwait ( &((sync)->cond), &((sync)->mutex), (ts) )
#define  syncSignal( sync )         pthread_cond_signal    ( &((sync)->cond) )
#define  syncBroadcast( sync )      pthread_cond_broadcast ( &((sync)->cond) )

/* ============================================================
 * Variables
 * ============================================================*/
static Sync          g_sync;
static volatile int  g_connected  = -1;
static volatile int  g_din1       = -1;

/* ============================================================
 * Functions
 * ============================================================*/

// GPIO初期化完了コールバック
static void initCallback( uint8_t v )
{
#if 1
    if ( v == 0 ) {
        // AE2100 本体側の GPIO デーモンが再起動するなどにより、切断が発生するかもしれません。
        // このサンプルでは切断時はアプリを強制終了して、アプリを再起動させます。
        LOGE( "<initCallback> v=%d (thread=%p) ... abort()", (int)v, (void*)pthread_self() );
        abort();
        return;
    }
#endif

    syncLock( &g_sync );
    LOGD( "<initCallback> v=%d (thread=%p)", (int)v, (void*)pthread_self() );
    g_connected = (int)v;
    syncSignal( &g_sync );
    syncUnlock( &g_sync );
}

// GPIO接点読取り完了コールバック
static void din1Callback( uint8_t v )
{
    syncLock( &g_sync );
    LOGD( "<din1Callback> v=%d (thread=%p)", (int)v, (void*)pthread_self() );
    g_din1 = (int)v;
    syncSignal( &g_sync );
    syncUnlock( &g_sync );
}

// GPIO モジュールの終了処理
void sampleFinalize ( void )
{
    gpiolib_dereg_input_cb( GPIOLIB_INPUT_TYPE_DIN1 );
    gpiolib_dereg_input_cb( GPIOLIB_INPUT_TYPE_DIN2 );
    ae_gpiolib_close();
    syncFin( &g_sync );
    g_connected = -1;
    LOGD("sampleFinalize OK.");
}

// GPIO と接続状態になるまで待機する。
//   locked : 呼び出し側で syncLock() している／いない。
// NOTE : 実際のアプリケーションでは timedwait を使い、
//        「一定時間経過でエラーとする」などを検討してください。
static int waitForConnected( bool locked )
{
    int ret = 0;

    if ( !locked ) { syncLock( &g_sync ); }
    while ( g_connected != 1 ) {
        ret = syncWait( &g_sync );
        if ( ret != 0 ) { break; } // 割り込みなどのケース
    }
    bool connectOk = ( ret == 0  && g_connected == 1 );
    if ( !connectOk ) { LOGE("waitForConnected Error : ret=%d, con=%d", ret, g_connected ); }
    if ( !locked ) { syncUnlock( &g_sync ); }
    return ( connectOk ? OK : ERR );
}

// GPIO モジュールの初期化
int sampleInitialize ( void )
{
    int ret;
    assert( g_connected == -1 );

    LOGD("sampleInitialize ... (thread=%p)", (void*)pthread_self() );
    syncInit( &g_sync );

    // gpiolib を初期化
    ret = ae_gpiolib_init( &initCallback );
    if ( ret != 0 ) { LOGE("Error! ae_gpiolib_init %d", ret );  return ERR; }

    // 初期化完了を待機
    ret = waitForConnected( false );
    if ( ret != 0 ) { LOGE("Error! waitForConnected %d", ret );  goto ERROR_LABEL; }

    // 接点 の読み取りをする場合：
    // 接点1 の read 関数のコールバックを設定。
    //  - 必要ならば、接点2 (GPIOLIB_INPUT_TYPE_DIN2) も同様に設定してください。
    ret = gpiolib_reg_input_cb( GPIOLIB_INPUT_TYPE_DIN1, din1Callback );
    if ( ret != 0 ) { LOGE("Error! gpiolib_reg_input_cb %d", ret );  goto ERROR_LABEL; }

    LOGD("sampleInitialize OK.");
    return OK;

ERROR_LABEL:
    sampleFinalize();
    return ERR;
}

// 接点入力1の読み取り
int sampleReadDin1( int* o_val )
{
    int ret;
    int din = -1;

    LOGD("sampleReadDin1 ..." );
    syncLock( &g_sync );

    // GPIO read 関数呼び出し
    //  - ENOTCONN, EFAULT は GPIO サービスと切断された場合です。
    //    再度接続されるのを待つか、エラーとして扱うか、アプリケーションの実装により判断してください。
    g_din1 = -1;
    ret  = gpiolib_read_din1();
    if      ( ret == ENOTCONN || ret == EFAULT ) { goto EXIT_LABEL; }
    else if ( ret != 0 ) { goto EXIT_LABEL; }

    // コールバック待ち（「接続中」でなくなった場合も中断）
    while ( g_din1 < 0  &&  g_connected == 1 ) {
        // NOTE : 実際のアプリケーションでは timedwait を使い、
        // 一定時間経過でエラーとして扱う、などを検討してください。
        ret = syncWait( &g_sync );
        if ( ret != 0 ) { break; } // 割り込みなどのケース
    }
    din = g_din1;

EXIT_LABEL:
    syncUnlock( &g_sync );
    if ( ret != 0 || din < 0) { LOGE("Error! %d", ret ); return ERR; }

    *o_val  = din;
    LOGD("sampleReadDin1 OK.  din1=%d", din );
    return OK;
}

// 接点出力への書き込み
int sampleWriteDout( int val )
{
    int ret;
    assert( 0 <= val && val <= 1 );

    // GPIO set 関数呼び出し
    //  - ENOTCONN, EFAULT は GPIO サービスと切断された場合です。
    //    再度接続されるのを待つか、エラーとして扱うか、アプリケーションの実装により判断してください。
    ret = gpiolib_set_dout( val );
    if      ( ret == ENOTCONN || ret == EFAULT ) { return ret; }
    else if ( ret != 0        ) { return ret; }

    LOGD("sampleWriteDout OK. val=%d", val );
    return ret;
}
```
</div></details>

GPIO制御ライブラリーのAPI関数についての詳細な説明は、マニュアルをご確認ください。

# ラッパーファイルの作成
C言語で作成されたサンプルコードの関数をpythonで呼び出すためには、サンプルコードの[ラッパー](https://www.fsi-embedded.jp/kumico/column/1543/#:~:text=%E6%B5%81%E3%82%8C%E3%81%AF%E3%81%84%E3%81%9F%E3%81%A3%E3%81%A6%E5%8D%98%E7%B4%94%E3%81%A7,%E9%96%A2%E6%95%B0%E3%82%92%E5%91%BC%E3%81%B6%E3%81%A0%E3%81%91%E3%81%A7%E3%81%99%E3%80%82)を作成する必要があります。
本記事はパトランプを制御しますので、接点(OUT)に関わるサンプルコードの関数「sampleInitialize」、「sampleWriteDout」、「sampleFinalize」について、pythonで呼び出せるようにヘッダーファイル(関数宣言)とラッパーファイルを作成します。
なお、ラッパーファイルの作成方法については、[参考の記事](https://tanakatarou.tech/261/)が別にありますので、詳しい説明は割愛しますが、主に行っていることは「Pythonのオブジェクト作成」、「ローカル変数の定義」、「ｃからpythonへ型変換」の3点です。

<details><summary><font color="red">ここをクリックしてソースコード(wrap_sample.c)を表示</font></summary><div>

ヘッダーファイル(Sample.h)

```diff_c
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
int  sampleWriteDout( int val );
int  sampleInitialize ( void );
void sampleFinalize ( void );
#ifdef __cplusplus
}
#endif
```

<br>
ラッパーファイル(wrap_sample.c)

```diff_c
#include "Python.h"
#include <stdint.h>
#include "./Sample.h"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"


PyObject* Initialize(PyObject* self, PyObject* args)
{
    int result00;
	
    result00 = sampleInitialize();
    return Py_BuildValue("i", result00);
}

PyObject* WriteDout(PyObject* self, PyObject* args)
{
	int val;
	int result01;

	if (!PyArg_ParseTuple(args, "i", &val)){
		return NULL;
	}
	
    result01 = sampleWriteDout(val);
    return Py_BuildValue("i", result01);
}

PyObject* Finalize(PyObject* self, PyObject* args)
{
    sampleFinalize();
    return Py_BuildValue("i", 0 );
}


static PyMethodDef methods[] = {
    {"sampleInitialize", Initialize, METH_VARARGS},
    {"sampleWriteDout", WriteDout, METH_VARARGS},
    {"sampleFinalize", Finalize, METH_VARARGS},
    {NULL}
};

static struct PyModuleDef libsampleGpio =
{
    PyModuleDef_HEAD_INIT,
    "libsampleGpio",
    "",
    -1,
    methods
};

PyMODINIT_FUNC PyInit_libsampleGpio(void) 
{
    return PyModule_Create(&libsampleGpio); 
}

```

</div></details>





# コンパイル
ラッパーの作成が完了したら、作成したコードをコンパイルします。

サンプルコード(sample.c)
```
root@254b1cabd362:~/gpio# gcc -fPIC -Wall -c -o sample.o sample.c
```

<br>
ラッパーファイル(wrap_sample.c)

```
root@254b1cabd362:~/gpio# gcc -I/usr/include/python3.8 -fPIC -Wall -Wno-unused-parameter -c -o wrap_sample.o wrap_sample.c
```

<br>
続いて、サンプルコード(sample.c)とラッパーファイル(wrap_sample.c)のコンパイルで作成した.oファイルを使用して、共有ライブラリを作成します。

この時、標準コンテナ付属のGPIOライブラリ(/usr/local/lib64/libae_gpio.so)を読み込めるように「-L./usr/local/lib64 -lae_gpio」(リンクオプション)を末尾に付ける点を注意してください。

```
root@254b1cabd362:~/gpio# gcc -fPIC -Wall -shared -o /usr/local/lib64/libsampleGpio.so sample.o wrap_sample.o -lpthread -L./usr/local/lib64 -lae_gpio
```


<br>
以下のコマンドを実行して、/usr/local/lib64 配下に共有ライブラリ(libsampleGpio.so)が作成されていれば問題ありません。

```
root@254b1cabd362:~# cd /usr/local/lib64
root@254b1cabd362:/usr/local/lib64# ls
libae_gpio.so  libae_gpio.so.1  libae_gpio.so.1.0  libsampleGpio.so
```


# pythonによる共有ライブラリのインポート
続いて、先ほど作成した共有ライブラリ(libsampleGpio.so)をpythonへインポートしていきます。
共有ライブラリをインポートするには、pythonの[「ctypes」](https://docs.python.org/ja/3/library/ctypes.html)というライブラリを使用します。

viコマンドで「sampleGpio.py」を作成して、以下のコードを記載してください。

```
root@254b1cabd362:/usr/local/lib64# cd /root/gpio
root@254b1cabd362:~/gpio# vi sampleGpio.py
```

<details><summary><font color="red">pythonコード(sampleGpio.py)の確認は、ここをクリック</font></summary><div>

```python
import ctypes
import time

#「libsampleGpio.so」のライブラリの読み込み
sampleGpio=ctypes.cdll.LoadLibrary("/usr/local/lib64/libsampleGpio.so")

print("sampleInitialize()　GPIO制御ライブラリーの初期化を実行します")
sampleGpio.sampleInitialize()
print("sampleWriteDout(1)　接点HIGHを実行します")
sampleGpio.sampleWriteDout(1)
print("3秒点灯状態で待機")
time.sleep(3)
print("sampleWriteDout(0) 接点LOWを実行します")
sampleGpio.sampleWriteDout(0)
print("sampleFinalize()　GPIO制御ライブラリーの終了処理を実行します")
sampleGpio.sampleFinalize()
```
</div></details>



# 実行
最後にライブラリのパスを通して、実行します。
```
root@254b1cabd362:~/gpio# export LD_LIBRARY_PATH="/usr/local/lib64:${LD_LIBRARY_PATH}"
root@254b1cabd362:~/gpio# python3 sampleGpio.py 
sampleInitialize()　GPIO制御ライブラリーの初期化を実行します
sampleInitialize ... (thread=0x7f434ddce740)
<initCallback> v=1 (thread=0x7f434dbb9700)
sampleInitialize OK.
sampleWriteDout(1)　接点HIGHを実行します
sampleWriteDout OK. val=1
3秒点灯状態で待機
sampleWriteDout(0) 接点LOWを実行します
sampleWriteDout OK. val=0
sampleFinalize()　GPIO制御ライブラリーの終了処理を実行します
sampleFinalize OK.
```
<br>

パトランプが点灯しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/5e35e95d-26a5-b74c-5955-41968895ae62.png)





# 参考
本記事では接点を利用してパトランプを点灯させましたが、「パトランプ(接点制御機器)がない」、「ラッパーファイルを作成せずに動作確認をしたい」という方は、AE2100のSTS(LED)ランプを点灯されるGPIO制御ライブラリーを活用することも可能です。
```
root@254b1cabd362:~/gpio# vi sampleGpio2.py
```

<details><summary><font color="red">pythonコード(sampleGpio2.py)の確認は、ここをクリック</font></summary><div>

```python
import ctypes
import time

#sampleGpio=ctypes.cdll.LoadLibrary("/usr/local/lib64/libsampleGpio.so")
ae_Gpio=ctypes.cdll.LoadLibrary("/usr/local/lib64/libae_gpio.so")

#sampleGpio.sampleInitialize()
ae_Gpio.ae_gpiolib_init (0)
time.sleep(1) 

ae_Gpio.gpiolib_set_STS_led(1)
time.sleep(3)
ae_Gpio.gpiolib_set_STS_led(0)

ae_Gpio.ae_gpiolib_close()
```
7行目の「time.sleep(1)」は、GPIO制御ライブラリーの初期化を待機しています。初期化の成功の可否を正確に確認したい場合は、「#」(コメントアウト)を削除して「sampleGpio.sampleInitialize()」を使用してください。
<br>
</div></details>

```
root@254b1cabd362:~/gpio# export LD_LIBRARY_PATH="/usr/local/lib64:${LD_LIBRARY_PATH}"
root@254b1cabd362:~/gpio# python3 sampleGpio2.py 
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/4c4aa785-7b21-7754-515c-df0a8be607f0.png)



# まとめ
本記事では、マニュアルのサンプルコード(C言語)に記載されている関数3つのラッパーファイルを作成して、pythonで呼び出せるようにしました。pythonでc言語のプログラムを呼び出すことによって、プログラムの可読性が良くなったり、処理の一部をc言語で高速に処理をさせることでパトランプなどの警報器で異常をリアルタイムに通知することができます。
ぜひ、ご自身のプログラムのAI処理のアウトプットとして、AE2100のGPIO制御ライブラリーを呼び出していただき、AE2100の接点制御を活用してみてください。