生産設備の稼働データを外部から取ってみよう(ネットワーク編)

# 生産設備の稼働データを外部から取ってみよう(ネットワーク編)
### 
京セラコミュニケーションシステムの大山です。

前回、[**生産設備の稼働データを外部から取ってみよう[ローカル編]**](https://qiita.com/kccs_susumu-oyama/items/c5edb04952eed22f13ef)で点灯と点滅を京セラIoTゲートウェイ[**KC4-C-100A**](https://www.kyocera.co.jp/prdct/telecom/office/iot/products/kc4-c-100a.html)(以降、**KC4**と称します)で1/0認識させました。今回はLTE通信でSORACOM経由で[**GoogleCloudPlatform**](https://cloud.google.com/gcp/?hl=ja)(以降、**GCP**と称します)に稼働データを送信してみたいと思います。

## この記事の対象者
設備稼働データを取りたいけど設備の中から線を引き出すのはちょっといやだな・・・という方  
設備稼働データ取りたいけどPLCのプログラムはいじりたくないよ・・・という方  
メンテナンスの停止ができない生産設備をお持ちの方  
デジタル化担当になったが何から始めていいか分からない方  

## 積層信号灯から設備稼働データをとるまでの経路
今回から実際に積層信号灯から光センサーを使用して点灯/消灯/点滅のステータスをデジタルデータ化してみたいと思います。
まずは大まかなデータの流れについては以下となります。

![流れ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748038/055707b4-ef78-2cf8-53b7-070eebf088e8.png)

今回は上図の赤線で囲っている部分光センサでとらえた積層信号灯の点灯/消灯の情報をSORACOM経由でGCPのBigQueryに送るところまで作ってみます。

## KC4のブロックプログラム
前回記事で作成したブロックプログラムのディスプレイ表示部分をデータ取得、JSON形式の文字列変換、HTTPReqestにブロックを置き直したのが以下となります。

![信号灯ST送信ブロック.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748038/24d7d762-8c9d-bf3b-b5c0-5400cdcfca12.png)

流れとしては
1秒ごとにGPI/GPIO端子の状態を見に行き元の状態と比較を行い変化があれば
①時刻を取得
②変化したセンサーを特定してセンサーに合わせた色を取得
③マップ変数に時刻・色・端子の状態を格納
④文字変数にマップ変数をJSON形式の文字列でセット
⑤HTTPRequest
として信号灯の変化点に合わせて送信の動作を行います。
※HTTPRequestのブロックについては[**通信プロトコル編①**](https://www.kyocera.co.jp/prdct/telecom/office/iot/development/download/start_programing_protocol_1.pdf) と [**KC4のサンプルレシピ**](https://www.kyocera.co.jp/prdct/telecom/office/iot/recipe/)を参考に作成してます。

## CloudFunctions
CloudFunctionsが今回GCP側の窓口となるのでSORACOM Beamで設定した内容と合わせる必要があります。
関数の作成ボタンを押して関数の作成に入ります。
関数名は任意の関数名を入力リージョンは任意のリージョンを選択(今回はasia-northeast1(東京)を選択)他はデフォルト設定です。

![CF_基本設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748038/d65cd2cc-314b-001d-c607-f7bb6f403589.png)

上記基本設定をすると
`https://リージョン名-プロジェクト名.cloudfunctions.net/関数名`でトリガーとなるURLが生成されます。
こちらのURLがSORACOM Beam設定の際の転送先となります。
このトリガーによってKC4からの送信されたJSONデータをPub/Subに送る関数を実行させます。
続いて関数の内容としては以下です。

```python:CloudFunctionsソース
from google.cloud import pubsub_v1
from flask import Response
import json
def main(req):
    try:
        # json取得
        request_json = req.get_json()
        # ヘッダー認証情報取得
        header_auth = ''
        if '任意のヘッダー名' in req.headers:
            header_auth = req.headers['任意のヘッダー名']
            print(header_auth)
        # 認証情報
        auth_string = "任意の値"
        
        if header_auth != auth_string:
            return Response(response=json.dumps({'message': 'Unauthorized'}), status=401)
        # jsonの中身
        print(request_json)
        
        # プロジェクトID
        PROJECT_ID = "任意のプロジェクトID"
        client = pubsub_v1.PublisherClient()
        # トピックID
        topic_id = "任意のトピックID"
        # Pathの設定
        topic_path = client.topic_path(PROJECT_ID, topic_id)
        print(topic_path)
        message = request_json
        
        #　↓↓↓メッセージの名前はKC4ブロックコードのマップ変数のkey名と合わせる
        device_code = message['device_code']
        timestamp = message['timestamp']
        color = message['colorST_send']
        ls1 = message['ls1_send']
        ls2 = message['ls2_send']
        ls3 = message['ls3_send']
        pubsub_message = {'device_code': device_code, 'inserted_at': 'AUTO', 'values': [{'timestamp': timestamp, 'color': color, 'ls1': ls1, 'ls2': ls2,  'ls3': ls3,}]}
        # エンコード
        data_str = json.dumps(pubsub_message)
        print("JSON-encoded message:\n{data_str}")
        data = data_str.encode("utf-8")
        # プッシュする
        client.publish(topic_path, data)
        return "Success!"
    except Exception as e:
        print('Exception Error' + e)
        return 
```
JSONデータ取得後ヘッダー情報を比較し問題なければPublisherが受け取れる形にエンコードしてPUSH送信する内容となります。

:::note warn
今回はお試しということでソースコード内に決め打ちのパスワードを書いてますが本来はパスワードとなる値をソースコード内にベタ書きするのはNGです。ハッシュ化してパスワード管理する等、考慮が必要となります。
:::

## Cloud Dataflow
テンプレートからジョブを作成ボタンを押して任意のジョブ名を入力、リージョンは"asia-northeast1(東京)"を選択してます。
Dataflowテンプレートは"Pub/Sub Subscription to BigQuery"を選択
Pub/Sub input subscriptionはprojects/**プロジェクトID**/subscriptions/**任意の名前**を設定
BigQuery output tableは任意のテーブルを設定
※BigQueryで設定するプロジェクト名、データセット名、テーブル名と合わせる必要があります。
一時的な場所はgs://**任意の名前**/temp/で設定

![DF_設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748038/77527b09-b913-4e50-1181-11cd3268d36c.png)

## Pub/Sub
サブスクリプションを作成ボタンを押して登録名とトピックの名前を入力し他はデフォルト設定でCREATEボタンで作成します。
登録名はprojects/**プロジェクトID**/subscriptions/**任意の名前**
※登録名はCloud DataflowのPub/Sub input subscriptionと合わせます。
トピックの名前はprojects/**プロジェクトID**/topics/**トピックID**
※CloudFunctionsのソース内のPROJECT_IDとtopic_idに合わせます。

![PubSub.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748038/7abe4f1c-a2cb-ceca-a1df-c33edee0617e.png)

## BigQuery
プロジェクト名右横の「︙」縦三点リーダーを押してデータセット作成画面にて任意のデータセットIDとデータのロケーション(東京リージョン)を設定しデータセットを作成します。

![BQ_データセット.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748038/136c5645-edac-5e63-8e66-a53524b0ddb6.png)

データセット作成後プロジェクト名左横の▼を押してノードを展開すると作成したデータセットID名が現れます。同様に「︙」を押してテーブル作成画面に移ります。送信先のテーブルに任意のテーブル名を入力します。他はデフォルトです。

![BQ_テーブル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748038/5352a6a0-87e0-366e-b2eb-11df83bdc791.png)

次にスキーマの設定ですが、CloudFunctionsのソースコードに記述した名称に合わせてフィールド名を入力します。タイプは送られてくるデータがどういった型のデータかによって設定します。KC4からは文字と数値と時間が送信されるので文字はSTRING、数値はNUMERIC、時間はTIMESTAMPで設定しています。

![BQ_テーブル_フィールド.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748038/1e235a37-6069-430d-518b-6e19f6fb5863.png)

最後にパーティションとクラスタの設定ですがパーティションは"取り込み時間により分割"と"フィールドにより分割"から選べます。フィールドはスキーマ設定の時のタイプがDATE、DATETIME、TIMESTAMP、INTEGERの型の中から選択します。DATE、DATETIME、TIMESTAMPは年月日時間単位、今回使用しておりませんがINTEGERは開始、終了、間隔の項目が出るので整数の範囲で分割されるものと思います。
今回は取り込み時間により分割で1日ごとを選んでいます。
お試しでデータ量が多くないのが分かっているのでパーティショニングフィルタの”データのクエリでWHERE句を必須にする”のチェックを外してますが、
誤って範囲指定していないクエリを実行して大量のデータを要求して分析の利用料金が上がる事を防ぐために基本はチェックを入れておいた方がいいです。

BigQueryの利用料金について詳しくは以下の記事を参照ください。
[**BigQueryの料金体系をサクッと解説**](https://qiita.com/kccs_hiromi-nishida/items/00ddcfe1873df8f5ca60)

![BQ_パーティション.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748038/410047d0-f51d-04d1-25a9-24f7a2d86d6f.png)

詳細オプションはデフォルトのままでテーブル作成ボタンを押して設定完了です。
作成したテーブルをクリックしてスキーマタブを見ると以下のような感じになります。

![BQ_設定後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748038/974de572-57ff-8b4f-d72d-3553c7bbb1ac.png)

## SORACOM Beamの設定
GCPにデータを送信する際SORACOMのSIMを使用します。詳しくはKC4側の設定については[**KC4のSIM設定**](https://www.kyocera.co.jp/prdct/telecom/office/iot/guide/kc4-c-100a_kc4-c-101a/#sec6-2)を、SORACOM側の設定については[**SORACOMの利用を始める**](https://users.soracom.io/ja-jp/guides/getting-started/)を参照ください。
最後にSORACOM Beamの設定ですがGCPのCloudFunctionで設定したトリガーURLを転送先、ソースコードの任意のヘッダー名・任意の値をそれぞれの欄に入力します。

![SORACOMBeam設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748038/8eff4c6d-12cb-febe-0fb4-0b7492e51934.png)

KC4から送信を行うと以下のようにBigQueryにデータが入ります。

![BQ_プレビュー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748038/d2c2a2c0-a7c2-a07e-73d6-657fe0ee4950.png)

## まとめ
各モジュール間で名前を合わせる必要がありこの部分で少しややこしく感じましたがそれさえクリアすればGCP初心者の私でもクラウドサーバにデータを送ることができました。
ということであと少し続きますが、次回はBigQueryに送ったデータをクエリで抽出してLooker Studio(旧DataPortal)に表示させるところを作ってみたいと思います。

※記事に掲載しているスクリーンショットは2022年12月時点となります。