SigfoxとRaspberry Piで、狩猟の罠通知装置を作成する。（その１）


# はじめに
　実はワタシ狩猟免許持ってます、猟友会の一員です猟師です。
といってもほとんどペーパー猟師です。
猟銃なんて持ってません、罠や檻で獲物を捕らえます。
　今年は親方に檻の見回りを仰せつかったのですが、ほとんど見廻り行けてません、見廻どころか勉強が大変で、そんな時間の余裕というか、ココロの余裕がありません。
しかも見回りは早朝です、オッチャンですけけど、朝に弱いです早起き苦手です。
　ならば、IT技術でカバーすりゃいいのです。ということで、作りましょう罠通知ガジェット。
罠に獲物がかかったことをITを使って、手元に知らせることができればいいのです。
早く作らないと、猟期が終わってしまいます。
　Qiitaにも参考記事上がってるし、その他のネットにもいろいろ参考になりそうなところがあるので、カンタンにできるだろうと高を括っていあたならば、とんでもないとんでもない、全然思い通りにいかんので諦めそうになりましたとさ。。。
# お品書き
1. 機器選択
2. Sigfox登録
3. ラズパイの設定
   - GPIOの接続
   - UARTの設定
4. プログラムの作成
	- スイッチの検出
	- ATコマンドの送出
の予定です。
# 機器選択
　まずは、機器の選択です。
山中に仕掛けた檻に獲物が掛かったら、何らかの方法で通知を受け取る仕組みを作りたいのです。
　通信機器はまさしくLPWA(Low Power Wide Area)じゃないといけません、山中にコンセントもありません、バッテリー駆動です、Wi-Fiなんて飛んでません、４G・５Gも電波届くか怪しいです。どうもSigfox一択のようです、他は免許が必要だったり、そもそもエリア外のようです、モジュールも入手しやすそうです、Sigfoxに決定です。日本では京セラさんがサービスしてます、エリアもバッチリでした。本家本元は破産申請しちゃったみたいです、ありゃりゃ、サービス中止にならなきゃいいけど、急ぎましょう。

　檻に獲物が掛かったことを通知するには、通信モジュールの制御とセンサーの検知が必要です、ワンボードコンピューターが必要です、手持ちはRaspberryPi・Arduino・obniz・ESP32です、使ってもしないのに転がってます、活用しないといけません。

　とりあえず、ネットで参考にできそうなのはArduinoですが、Wi-FiやBluetoothが使えることや次のステップでの拡張を考えて、RaspberryPiを選びました、しかも手持ちの3Bじゃなくてzeroを新規購入です、また肥やしを増やしてしまいました。
# Sigfox登録
　Sigfoxの通信モジュールは「Sigfox Breakout board BRKWS01 RC3」を使用します開発キットです、とういか去年の１２月に買ってました積んでありました、Sigfoxの管理サイトの１年間の利用権がついてます、多分コレ一択です。
登録はどうやったか忘れてしまいましたが、多分京セラさんの

https://qiita.com/ghibi/items/37154bad78d9bb57e335

を参考にしながらした筈です。ありがとうございます。
　登録にはDeviceIDとPACというのものを入力する必要があるのですが、パッケージのシールに印刷されています。何度も基盤の番号を入力しては撥ねられてを繰り返してた事を思い出しました。
# ラズパイの設定
## GPIOの接続
　これは、ラズパイのトゲトゲのことですね、通信モジュールとはジャンパーワイヤーで繋ぎます、京セラさんの記事ではArduinoだったので、ピンの配置や割り当てはラズパイの公式ドキュメントと、＠kataoka_seさんの

https://qiita.com/kataoka_se/items/21ee9cfbc27668980b5a

を参考にしました、大変助かりました、ありがとうございました。
　ラズパイ側のTx(GPIO8) とモジュールのRx、ラズパイ側のRx(GPIO9)とモジュールTxを繋ぎます、電源も繋ぎます。

<img src="https://www.kccs.co.jp/sigfox/blog/technical/2019/0213/img/img-20190213-technical-03.png" width=50%>

まだこの辺までは理解できます、ついていけます、さぁ次は最初の難関です。
## UARTの設定
　UART？　なんだソレ？　シリアル通信？聞いてないよぉ〜
Sigfoxの通信モジュールとラズパイとのやりとりは、シリアル通信で行うとあります。
USBで繋ぐとすぐ使えることに慣れすぎてて、もう拒絶反応でてきました。
なになに、ATコマンドでやりとりするだって‼︎、ATコマンドって昔ニフティサーブにモデムで接続するときの「おまじない」のこと？だな。。。懐かしい懐かしすぎる、ピーガラガラゴロゴロガラゴロガァーーって、モデムって知ってます？

　UARTの設定の理解には
[ラズパイでUART通信を行う](https://masaeng.hatenablog.com/entry/2019/08/25/234002)と[Raspberry Pi OS で Bluetooth を使える状態にしたまま GPIO で UART を使うための設定](https://hassiweb.gitlab.io/memo/docs/memo/raspberry-pi/raspberry-pi-os/mini-uart/) が参考になりました。
　ラズパイにUARTという回路が２つあって、Bluetoothとシリアルコンソールの２つが使えるようになってるよ、その２つには早い方と遅い方があって、早い方はBluetoothに割り振ってあるよ、Sigfoxのモジュールと通信するためにどっちを使うにも設定を変える必要があるよ、なおかつ遅い方を使うには動作を安定させる必要があるよ、いずれにせよ　/boot/cmdline.txt　に書き込んで設定を変える必要があるよ、てなことです。
　で、シリアルコンソールって何？ってなるんですが、[wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%B3%E3%82%BD%E3%83%BC%E3%83%AB#%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%B3%E3%83%B3%E3%82%BD%E3%83%BC%E3%83%AB)　読んでも、いまいちピンとこなくて、「シリアルコンソールを使えるように」というところは「GPIOをシリアルポートと使用して、ラズパイをシリアルコンソールとして使う場合には」っていう意味なんだろうなと理解してます。
　今回は、ラズパイの3Bの方で検証してから、本番運用はzeroで行う予定です。
とりあえず検証中は、UARTの設定はそのままにしておこうと思います。
　まずは、シリアルコンソールの無効化です、これはラズパイの設定画面から設定します、カンタンです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2253252/87d29ce3-885b-17b9-436b-679424613c80.png" width="80%">
出来ました。
　次は、遅い方のUARTをモジュールの通信に使うための設定を/boot/cmdline.txt　に書き込みます。
```bash:ターミナル
pi@B3pi(52.6'C):~ $ cat /boot/cmdline.txt
console=tty1 root=PARTUUID=bc8fd8f7-02 rootfstype=ext4 fsck.repair=yes
 rootwait quiet splash plymouth.ignore-serial-consoles
```
enable_uart=1 と
core_freq=250　を追加します。

```bash:ターミナル
pi@B3pi(53.7'C):~ $ cat /boot/cmdline.txt
console=tty1 root=PARTUUID=bc8fd8f7-02 rootfstype=ext4 fsck.repair=yes
 rootwait quiet splash plymouth.ignore-serial-consoles enable_uart=1
 core_freq=250
```
確認できました。
　接続のテストもしてみます。
上記の@kataoka_se さんのコードモロパクリです、ただUARTを入れ替えてないので、portのところだけ違います。

```python:uarttest.py
import serial

port = '/dev/ttyS0'

s = serial.Serial(port, 9600, timeout=10)
# Device ID を取得する AT コマンドを送付
s.write(b'AT$I=10\r');

# Sigfox Module からのレスポンスを読み込む
resp = s.readline()

print('Device ID : {}'.format(resp))
```
```bash:ターミナル
pi@B3pi(45.6'C):~ $ python uarttest.py
Device ID : b'00ﾁｮﾒﾁｮﾒ\r\n'
```
確認できました。
　ちゃんとID返って来ました。例によって伏せ字はﾁｮﾒﾁｮﾒです。

# プログラムの作成
　１時間おきにスイッチの状況とラズパイのCPU温度とCPU電圧を送るようにしようと思います。
## スイッチの検出
　まずは、スイッチの検出です。
これはもうネットに溢れてますね。ちょうど「Raspberry　Pi クックブック　第２版」が変色した状態で転がっていたので参考にしました。
Amazonさん曰く、２０１８年の１１月購入だそうです、やっと日の目を見ました。
　プルアップ・プルダウンの説明は、[ココ](https://monomonotech.jp/learning/contents_trial/raspi_okigashi/raspi_okigashi/index.html#7)がわかりやすかったです。
　罠の検知は、獲物が罠にかかり扉が閉まると、扉に取り付けた紐が引っ張られて、マイクロスイッチが入るか切れるかする仕組みにするつもりなので、それを検知します。
１時間ごとに検知するのでtimesleep()で間隔をとりますが、とりあえず押しボタンスイッチでテストです。
　押しボタンを押したときに、マイクロスイッチのポジションを検知します。スイッチのレバーが押されている状態と解放されている状態と、どっちを検出するのかまだ決めてないので、どっちも使えるようにマイクロスイッチにはオムロン製のD2FW-Gを使いました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2253252/be11548d-202a-61f5-b7f0-01edd03afb24.jpeg" width="40%">

```python:switch.py
import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)

top_input = 18
bottom_input = 23
check_input = 24

GPIO.setup(top_input, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(bottom_input, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(check_input, GPIO.IN, pull_up_down=GPIO.PUD_UP)


while True:
    while GPIO.input(check_input) == False:
        top_state = GPIO.input(top_input)
        bottom_state = GPIO.input(bottom_input)
        switch_position = "unknown"
    
        if top_state == True:
            switch_position = "top"
        elif bottom_state == True:
            switch_position = "down"
        else:
            switch_position = "center"

        print(switch_position)
        time.sleep(0.2)
```
　サンプルなんでお行儀わるいです、GPIO.cleanup()してないです、Ctrl+Cで終了です。
スイッチの状態はフツーはFalseで押されているのを検知するのですが、レバーが上がっているか降りているかの見た目と、返り値を合わせたかったので、逆にしてあります。
```bash:ターミナル
pi@B3pi(56.9'C):~ $ python switch.py
top
down
down
top
^CTraceback (most recent call last):
  File "/home/pi/switch.py", line 16, in <module>
    while GPIO.input(check_input) == False:
KeyboardInterrupt

pi@B3pi(59.1'C):~ $
```
ちゃんと思い通りに動いています。

## ATコマンドの送出
では、CPUの温度と電圧を取得してATコマンドで送ることにします。
　まずは、Sigfoxに届くかどうか試してみます。
これも、@kataoka_se さんのコードモロパクリです。
```python:sendtest.py
import serial

port = '/dev/ttyS0'
s = serial.Serial(port, 9600, timeout=10)
message = '0123456789abcdef01284567'

at_command = 'AT$SF={0}\r'.format(message)
s.write(at_command.encode())

while True:
    resp = s.readline()
    if resp != b'':
        break

print('Response: {}'.format(resp))
```

```bash:ターミナル
pi@B3pi(55.8'C):~ $ python sendtest.py
Response: b'OK\r\n'
pi@B3pi(54.2'C):~ $
```
Sigfoxのサイトで確認します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2253252/c240be9c-dd00-7242-0172-f715933c4a01.png" width=80%>
届きました。
<font color="Red"> さて、ここから本番なんですが躓きます、初心者の過ちにお付き合いください。</font>
　CPUの温度と電圧はvcgencmdで取得できることは知りましたが、それはシェルスクリプトの話しで、今回はpythonです。
検索すると、pythonからシェルスクリプトを呼び出せるのですね

https://qiita.com/girlfellfromsky/items/ffb96765a56ccab9b62d

を参考にさせていただきました、助かります。

```python:sendmsg.py
import RPi.GPIO as GPIO
import time
import subprocess
import serial

port = '/dev/ttyS0'
top_input = 18
bottom_input = 23
i=0

GPIO.setmode(GPIO.BCM)
GPIO.setup(top_input, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(bottom_input, GPIO.IN, pull_up_down=GPIO.PUD_UP)

while i<1:#true
    top_state = GPIO.input(top_input)
    bottom_state = GPIO.input(bottom_input)
    switch_position = "unknown"
    
    if top_state == True:
        switch_position = "top"   
    elif bottom_state == True:
        switch_position = "down"
    else:
        switch_position = "center"
        
    temp = subprocess.run('vcgencmd measure_temp', shell=True, encoding='utf-8', stdout=subprocess.PIPE).stdout.split('=')
    volt = subprocess.run('vcgencmd measure_volts', shell=True, encoding='utf-8', stdout=subprocess.PIPE).stdout.split('=')
    temp = temp[1].replace("'C",'C').replace('\n','')
    volt = volt[1].replace('\n','')
    message = switch_position+";"+ temp +";" + volt
    print(message)
    
    s = serial.Serial(port, 9600, timeout=10)
    at_command = 'AT$SF={0}\r'.format(message)
    s.write(at_command.encode())
    resp = s.readline()
    print('Response: {}'.format(resp))
    
    i+=1
    time.sleep(6)#1h=3600
    
GPIO.cleanup()
```
```bash:ターミナル
pi@B3pi(48.3'C):~ $ python sendmsg.py
top;48.9C;1.2813V
Response: b'ERROR: parse error\r\n'
pi@B3pi(48.3'C):~ $ 
```
 なんですか？parse errorって、Sigfoxと繋がってないのかしら？
色々疑って、接触悪いのかとハンダ付けにしてみたり。。アンテナを握って送信してみたり。。。テストで文字列変えて送ってみたり。。。。
　え”ッ‼︎もしかして小数点とか送れないの、いやいや"V"も"v"すら送れない、なんで"c"は送れるのに、アレ"C"は送れない。。。って
そりゃそうです、
>AT$SFは16進表記のデータを送る

ってことだそうで、１６進数に変換してから送らないといけなくて、しかも１２バイトまでで偶数じゃないとダメと京セラさんの[FAQ](https://www.kccs.co.jp/sigfox/faq/)にありました。

>上り通信は12バイト（16進数表記で24桁）、下り通信は8バイト固定長（16進数表記で16桁）になります。なお、上り通信は偶数桁であれば、12バイト以下のメッセージを送信できます。

ふぅ〜
 そもそもATコマンドってベンダーごとに少し違ってるのですのね、知らなかった。。。
そりゃそうですよね、初心者というか、それ以前に仕組みが解ってなくてコピペで済まして来れば、そうなるわなって事ですよね。
これで、数日つぶれました、気持ちも萎えました。間に試験を挟んだので、もう１週間経ってしまってしまいました。

 メッセージ生成部分を書き直します。
クラウドサービスと連携させる場合には、後々手直しの必要がありますが、とりあえず今はデータを１０進法で判読できるようにしました。
初めの４桁でスイッチの状態を、つぎの４桁（実数）＋４桁（小数点以下）でCPUの温度を、つぎの４桁（実数）＋４桁（小数点以下）でCPU電圧を表示させています。
```python
temp = subprocess.run('vcgencmd measure_temp', shell=True, encoding='utf-8', stdout=subprocess.PIPE).stdout.split('=')
    volt = subprocess.run('vcgencmd measure_volts', shell=True, encoding='utf-8', stdout=subprocess.PIPE).stdout.split('=')
    temp = temp[1].replace("'C",'').replace('\n','')
    print(temp)
    volt = volt[1].replace("V",'').replace('\n','')
    print(volt)
    message = switch_position+temp.split(".")[0].zfill(4)+ temp.split(".")[1].ljust(4,'0') + volt.split(".")[0].zfill(4)+ volt.split(".")[1].ljust(4,'0')
```

```bash:ターミナル
pi@B3pi(52.1'C):~ $ python sendmsg.py
52.6
1.2813
01000052600000012813
Response: b''
pi@B3pi(52.1'C):~ $ 
```
Sigfoxのサイトで確認します。

<img src= "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2253252/ab558063-09e3-8cef-42b8-34252fca3371.png" width=80%>

ちゃんと送れています。
あぁやれやれです。
# ここで、力尽きました。
次回は
- zeroへの移植
- 自動起動
- Sigfox側設定
- バッテリー電源結線
- 屋外での確認
- 実戦投入
- クラウドへの接続
- SMSメッセージの送出
の中から、
出来たとこまでをと考えています。

もうすでに、猟期はあと数日となってしまったので、実戦投入は来季に持ち越しですね。
来年のその時までに機能を充実させていきます。