modalsoul’s blog

これは“失敗”と呼べるかもしれないが、ぼくは“学習体験”と呼びたい

Raspberry Pi Zero WHとMPU9250で、9軸センサー(ジャイロ、加速度、磁気)を取得する

9軸センサーが安かったので、ポチってRaspberry Pi Zeroと繋いでみた1

MPU9250

はんだ付け

付属のピンをはんだ付け

小手とはんだ、はんだ吸取線、簡易こて台がセットになったものをポチった [asin:B0072QN66U:detail]

はんだ付けなんて小学校以来?くらいだけど、なんとかなった

配線

Raspberry Pi Zero MPU9250
3V3 VCC
GPIO02 SDA
GPIO03 SCL
GND GND

I2Cの有効化

sudo raspi-config
  • 5 Interfacing Options
  • P5 I2C
  • YES

でI2Cを有効化

バイスのチェック

I2Cデバイスとして認識されているかをチェック

i2ctoolsのインストール

sudo apt install i2c-tools

I2Cデバイス一覧の表示

sudo i2cdetect -y 1

これで68が表示されていればOK

pi@raspberrypi:~ $ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- 0c -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

プログラム

pipのインストール

sudo apt install python-pip python-smbus

ライブラリのインストール

github.com

sudo pip install FaBo9Axis_MPU9250

動作確認

import FaBo9Axis_MPU9250
import time

mpu9250 = FaBo9Axis_MPU9250.MPU9250()

while True:
  accel = mpu9250.readAccel()
  print('accel:' + str(accel))
  gyro = mpu9250.readGyro()
  print('gyro:' + str(gyro))
  magnet = mpu9250.readMagnet()
  print('magnet:' + str(magnet))
  time.sleep(0.1)
pi@raspberrypi:~$ python scan.py
accel:{'y': 0.101, 'x': -0.957, 'z': -0.111}
gyro:{'y': -0.488, 'x': 3.143, 'z': 0.771}
magnet:{'y': 50.33, 'x': -43.91, 'z': 36.58}
accel:{'y': 0.1, 'x': -0.958, 'z': -0.108}
gyro:{'y': -0.328, 'x': 3.059, 'z': 0.801}
magnet:{'y': 0, 'x': 0, 'z': 0}
...

これでセンサーの値が取得できた

Raspberry Pi Zero WHでGPIOを使って音声出力

Raspberry Pi Zeroにはオーディオジャックがないので、GPIOに繋いで使えそうなスピーカーを用意した


配線

スピーカーをそれぞれ、GPIO 18(右)とGPIO 13(左)とGroundへ接続

GPIO設定変更

pi@raspberrypi:~ $ gpio -g mode 18 ALT5
pi@raspberrypi:~ $ gpio -g mode 13 ALT0
pi@raspberrypi:~ $ gpio readall
 +-----+-----+---------+------+---+-Pi ZeroW-+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 |   IN | 1 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 |   IN | 1 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |   IN | 1 |  7 || 8  | 0 | IN   | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 1 | IN   | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 0 | ALT5 | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   IN | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   IN | 0 | 15 || 16 | 1 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI |   IN | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO |   IN | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK |   IN | 0 | 23 || 24 | 1 | IN   | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | IN   | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   IN | 1 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   IN | 1 | 31 || 32 | 0 | IN   | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 | ALT0 | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   IN | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   IN | 0 | 37 || 38 | 0 | IN   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 | IN   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+-Pi ZeroW-+---+------+---------+-----+-----+

Audio設定

pi@raspberrypi:~ $ sudo raspi-config
  • Advanced Options
  • Audio
  • Force 3.5mm ('headphone') jack

を選択して終了

動作確認

pi@raspberrypi:~ $ aplay /usr/share/sounds/alsa/Front_Center.wav
Playing WAVE '/usr/share/sounds/alsa/Front_Center.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono

このサンプルはモノラルなので、左右の動作確認をするには適当なステレオ音源で

ちなみにaplayは.mp3や.m4aは再生できなかったので、mplayerをインストール

sudo apt-get install mplayer

音量は控えめだけど、とりあえずOK

Raspberry Pi Zero WHにブート/シャットダウンの物理ボタンを付けた

Raspberry Pi Zeroをポチったので、ちょっとずついじってみる

OSはRaspbian stretch lite www.raspberrypi.org

初期設定諸々はここでは割愛


物理スイッチを取り付ける

シャットダウンするためにsshするのはダルいので、安全に起動・停止できる物理ボタンを付けます

こちらを参考にさせてもらいました

Raspberry Pi 3 にシャットダウン/リブート/再開ボタンを追加する - はむ!の空想具現化したいブログ

RaspberryPiにshutdownボタンを付けよう

配線

スイッチとGPIO18とGPIO3、GNDを接続

シャットダウンスクリプト

/home/pi/service/shutdownd.py

#!/usr/bin/python
# coding:utf-8
import time
import RPi.GPIO as GPIO
import os

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

while True:
    GPIO.wait_for_edge(18, GPIO.FALLING)
    sw_counter = 0

    while True:
        sw_status = GPIO.input(18)
        if sw_status == 0:
            sw_counter = sw_counter + 1
            if sw_counter >= 50:
        os.system("sudo shutdown -h now")
                break
        else:
            break

        time.sleep(0.01)

systemd設定

サービスファイル

/usr/lib/systemd/system/shutdownbuttond.service

[Unit]
Description=Shutdown Daemon

[Service]
ExecStart =/home/pi/service/shutdownd.py
Restart=always
Type=simple

[Install]
WantedBy=multi-user.target

サービス有効化

pi@raspberrypi:~ $ systemctl enable shutdownbuttond.service

デーモン再起動

pi@raspberrypi:~ $ sudo systemctl daemon-reload

これで起動時にスイッチ長押しでシャットダウン、停止時にスイッチで起動できるようになった

国際フレンドシップ・デーで、Intellij IDEAのライセンスを50%offで買った

タイトルの通りです

前回の割引のときに買いそびれて後悔してたんで、心残りをひとつ成仏させてやれた、よかったよかった

国際フレンドシップ・デーや、なんで割引なのか?について詳しくは↓を blog.jetbrains.com

↓の紹介リンクから買うと-¥500になります。よろしかったらどうぞ secure.samuraism.com

新規サブスクリプションだけじゃなく、継続にも適用されるらしいですね

Seleniumスクリプトをdocker上のHeadless Chromeで動かす

Seleniumを使った時間のかかるPythonスクリプトがあり、こいつを動かすとフォーカス取られて仕事にならないので、docker化し、ヘッドレス環境で実行するようにした

Dockerfile

ChromeDriverとSeleniumをインストール

FROM python:3.6-alpine3.7
ENV APP_ROOT=${APP_ROOT:-/opt/app}

# update apk repo
RUN echo "http://dl-4.alpinelinux.org/alpine/v3.7/main" >> /etc/apk/repositories && \
    echo "http://dl-4.alpinelinux.org/alpine/v3.7/community" >> /etc/apk/repositories

# install chromedriver
RUN apk update
RUN apk add chromium chromium-chromedriver

# install selenium
RUN pip install selenium

WORKDIR $APP_ROOT/scripting

Makefile

DOCKER=docker

scripting:
    $(DOCKER) build -t scripting -f Dockerfile .

イメージをビルドする

make scripting

docker-compose.yml

version: '2'
services:
  script:
    image: scripting
    volumes:
      - .:/opt/app/scripting:rw

script.py

ヘッドレス環境で実行するテスト用スクリプト

from selenium import webdriver

URL = 'https://www.google.co.jp/'

def main():  
  options = webdriver.ChromeOptions()
  options.add_argument('--headless')
  options.add_argument('--no-sandbox')
  
  driver = webdriver.Chrome(chrome_options=options)
  
  driver.get(URL)
  print(driver.title)
  
  driver.close()
  driver.quit()

main()

--headlessオプションでヘッドレスモード

--no-sandboxオプションについては↓を参照 github.com

実行

> docker-compose run scripting python3 script.py
Google

動いた

日本語

日本語を表示するためにフォントを入れます

FROM python:3.6-alpine3.7
ENV APP_ROOT=${APP_ROOT:-/opt/app}

# update apk repo
RUN echo "http://dl-4.alpinelinux.org/alpine/v3.7/main" >> /etc/apk/repositories && \
    echo "http://dl-4.alpinelinux.org/alpine/v3.7/community" >> /etc/apk/repositories

# install chromedriver
RUN apk update
RUN apk add chromium chromium-chromedriver
RUN apk add ttf-freefont

# install selenium
RUN pip install selenium

# install font
RUN mkdir /noto

ADD https://noto-website.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip /noto

WORKDIR /noto

RUN unzip NotoSansCJKjp-hinted.zip && \
    mkdir -p /usr/share/fonts/noto && \
    cp *.otf /usr/share/fonts/noto && \
    chmod 644 -R /usr/share/fonts/noto/ && \
    fc-cache -fv

WORKDIR $APP_ROOT/scraping

任意の桁数のゾロ目を生成する

n = 1のとき、f(n) = 1
n = 2のとき、f(n) = 11
n = 3のとき、f(n) = 111
n = kのとき、f(n) = 11...11 (k桁の1のゾロ目)

となるf(n)の実装を考える

数式で表すと { \displaystyle
f_n = \sum_{i=0}^{n} 10^{i}
}


ぱっと思いついた再帰を使うパターン

def fn(n, acc=0):
  return acc if(n == 0) else fn(n-1, acc*10+1)

部分和{ \displaystyle10^{i}} の総和だから、

再帰を使わないパターン

sum(10**k for k in range(n))

任意の値xをかければ、xの任意の桁数のゾロ目が生成できる

Pythonicなリストのfilter処理

Pythonicにリストをfilterするにはどうするか?といい話題がでたので、ちょっと考えてみた


話のもとになったslackの投稿(とその擬似コード

  • パターンA
names = [x for x in names if x is CONDITION]

↑だとxに何が入ってくるのかがわからない

  • パターンB
names = [name for names in names if name is CONDITION]

↑長い

  • パターンC
names = list(filter(lambda x: x is CONDITION, names))

lambda x:の部分はフィルタの関数が定義されているだけなので、xが何かわからなくてもいい?

どれがPythonicで良いコードなのか?


自分個人の見解としてはfilterを使いたい

理由としては、filterのほうがフィルターしていることが明示的だから。

フィルターしていることがわかると、引数にとったリストの要素の型と同じ型の要素を持つリストが帰ってくることが読み取れる

擬似的にScalaで型を表記すると、

val list:List[A]なら、filter(f, list)List[A]であるということ


それと、filterに渡すフィルタの関数の引数が何であるかは気にしなくて良いと思う

フィルターであれば、何が入ってくるかより、何になればフィルターの条件を満たすのかがわかればよくて、極論変数名もいらないと思う

Scalaの場合、関数のすべての引数が1回のみ使われる場合、引数宣言を省略して_で記述できる

ex)

names.filter( _ == HOGE)

なので、Pythonでもこんな風に書きたいくらい

names = list(filter(lambda _: _ is HOGE))

実際これでも動く


最終的にSlack上の議論では、内包表記がよいということになった

理由としては、可読性が良いから

可読性がよいと判断する理由としては、内包表記のほうがより一般的であるため

複数人でメンテナンスするコードであれば、尚更大事なポイント


それと、Effective Pythonでも内包表記を薦めているし

qiita.com

内包表記のほうがfilterの10倍くらい速いらしい