t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBA × Network学習:プレフィックス長からサブネットマスクに変換する

今回は久々にVBA。
ネットワーク学習で苦手なサブネット回りの計算をしてみようと思う。

IPアドレスとサブネットマスクを表記する方法として、プレフィックス長表記(またはCIDR表記)という方法があり、たとえば192.168.1.0/24と書くとIPアドレスが192.168.1.0でサブネットマスクは255.255.255.0という意味になる。

IPアドレスやサブネットマスクの実態は32ビットのデジタルデータ(二進数)で、これを8ビットずつに区切って10進数で表されている。
255.255.255.0はそれぞれ2進数に直すと、11111111.11111111.11111111.00000000となる。このとき1が先頭が24個並んでいるので/24という表記になっているわけだ。

この表記は簡潔に書けて良いんだけど、ネットワークの学習をしているとパッとサブネットに直したいときがある。

これが/8、/16、/24など、都合よくドットの区切り目でわかれているようなケースはナチュラルマスクといって、255か0で構成されるので覚えるのも簡単だが、中途半端な位置で切れていると変換するのもなかなか大変。

ということでVBAコードを書いてみた。

Sub PrefixLengthToSubnetMask()
    Dim i
    For i = 0 To 32
        Debug.Print "Prefix /" & i, Subnet(i)
    Next
End Sub

Function Subnet(prefix) As Variant
    '最後にJoin使うためにあえて数値ではなくVariantで配列宣言
    Dim subnet_(1 To 4) As Variant
    
    Dim octet As Integer
    Dim bit_in_octet As Integer
    Dim current_bit As Integer
    
    For octet = 1 To 4
        'Variantなので0で初期化しないとブランク文字になってしまう。
        subnet_(octet) = 0
        For bit_in_octet = 0 To 7
            current_bit = current_bit + 1
            If current_bit <= prefix Then
                subnet_(octet) = subnet_(octet) + 2 ^ (7 - bit_in_octet)
            End If
        Next
    Next
    Subnet = Join(subnet_, ".")
End Function

余談だけど最近はラズパイとかLinuxでのpythonコーディングが増えて変数名はスネークケースの方が読みやすく感じてしまう。
どっぷりVBAに漬かってた頃はキャメル・パスカルが読みやすいと考えていたんだけど、今書いてみると単なる思い込みだった可能性が高いと思い始めた。

当時VB.Netのコーディングガイドライン本だったり、Microsoftのコードだったりを参考にガイドラインを作ったんだけど、現時点ではキャメル・パスカルについては先例を踏襲したという以外にそうする理由を見いだせない。自分で過去に書いたガイドラインから思い切り違反しているのでガイドラインの方を改定してしまいたい気持ちもありつつ、VBAコミュニティはキャメルケースとかパスカルケースが主流だと思われるので悩ましいところ。

さて、実行結果は次のとおり。

Prefix /0  0.0.0.0
Prefix /1  128.0.0.0
Prefix /2  192.0.0.0
Prefix /3  224.0.0.0
Prefix /4  240.0.0.0
Prefix /5  248.0.0.0
Prefix /6  252.0.0.0
Prefix /7  254.0.0.0
Prefix /8  255.0.0.0
Prefix /9  255.128.0.0
Prefix /10  255.192.0.0
Prefix /11  255.224.0.0
Prefix /12  255.240.0.0
Prefix /13  255.248.0.0
Prefix /14  255.252.0.0
Prefix /15  255.254.0.0
Prefix /16  255.255.0.0
Prefix /17  255.255.128.0
Prefix /18  255.255.192.0
Prefix /19  255.255.224.0
Prefix /20  255.255.240.0
Prefix /21  255.255.248.0
Prefix /22  255.255.252.0
Prefix /23  255.255.254.0
Prefix /24  255.255.255.0
Prefix /25  255.255.255.128
Prefix /26  255.255.255.192
Prefix /27  255.255.255.224
Prefix /28  255.255.255.240
Prefix /29  255.255.255.248
Prefix /30  255.255.255.252
Prefix /31  255.255.255.254
Prefix /32  255.255.255.255

ポイントはコード中の以下の部分。

If current_bit <= prefix Then
    subnet_(octet) = subnet_(octet) + 2 ^ (7 - bit_in_octet)
End If

ちょっと分かりにくいと思うのでPrefix /29の場合を例に最後の8オクテットを図示したものを張っておこうと思う。

今後の展望

今回ご紹介したのはIPアドレス・サブネットマスク回りの様々な計算を実行できるクラスモジュールを作りたいという目的の一部分である。
今後記事にするかどうかは分からないけど、とりあえず自分用の計算クラスを作るところまではやってみようと思う。

以上

IT技術全般において概念モデルは比喩ではなく本質だという考察

最近ネットワークの学習をしていて思うのが、10年前の私はよく概念モデルで躓いていたなということ。たとえばOSI参照モデル等。
学習が遅々として進まなかった原因として、概念モデルが登場したときにいつも腑に落ちず苦痛を伴っていたことを思い出した。

当時の私は実装こそが真実であり、概念モデルはそれを分かりやすく説明するための単なる比喩表現に過ぎないと勘違いしていた。
しかしそんなものを見せられても一向に実装がイメージできない。実際にどうなっているのか、どのように実装されているのかを理解できなければ分かったことにならないと思い込んでいたのだ。

たとえばTCP/IPモデルでいえば、層という概念が実際にどう実装されているのか、まさか図の通り積みあがっている何かがあるわけではあるまいし、だとすればどうなってるんだ。層ってなんなんだ!分からん、何も分からん!といった具合。


この考え方が変わったのは、ブログでVBAを説明し始めた影響が大きい。
ちょうど以下の記事を書いた頃から、実装は単なる手段なので本質とは違うのではないかということに気づき始めた。
thom.hateblo.jp

新しいIT技術を考えだすプロセスとしては、(1)まず目的があり、(2)それを達成するための仕組みを考え、(3)それを実装に落とし込むという順序になると思う。

つまり(2)で考えられた仕組みの本質を抜き出したものが概念モデルであり、仕組みを学習して理解するという点に置いては概念モデルの理解がゴールなのだ。
これに気づいたことで、まずは与えられた説明を受け入れて学習を前に進めるということが出来るようになった。

もちろん、実装を学ぶフェーズもある。
概念モデルはその実装によって様々な現実的な制約を伴ってくるので、理解を深めるうえで実装を知ることは避けては通れないが、それは必要になった時に学べば良い。

以上

VLANで分割した自宅ラボターミナルサーバーへTELNET接続できなくてハマった件

今回はVLANによるネットワーク分割でハマった件と原因・解決策について備忘禄として残しておく。

VLANとは

VLANの前提となるLANはローカルエリアネットワークの略で、基本的には同じスイッチ(ルーターのスイッチポート含む)に直接つながっている機器同士がMACアドレスという機器固有のアドレスを使って通信する様子をイメージしてもらえれば良いかと思う。
(スイッチがスタッキングしてたり、色々と例外もあるんだけど。)
※LAN内でもIPアドレスは参照されるが、一旦IPアドレスを元に相手のMACアドレスを特定して実際の通信はMACアドレスに宛てられる。

VLANとはVirtual LANの略で、物理的なスイッチポートに囚われず、仮想的にネットワークを区切ったり、統合したりできる。

例えばVLANで一つのスイッチに繋がれた機器を2つのネットワークグループに分割すれば、これらのネットワーク同士はもはやMACアドレスで通信することはできなくなる。

そこで登場するのがルーター・L3スイッチなどのIPアドレスでルーティングをする機器。
一度分割したネットワークもこれでまた接続させることが出来る。

なんでそんな面倒なことをするかというと、LANの中は基本的に機器同士が常時おしゃべりをしてるので、適当にグループを分けてやらないと無駄話が飛び交って仕事が進まなくなるためだ。ただ別のグループへも必要な連絡事項は通す必要があるので、ルーターの出番という訳。

今回分割したネットワーク

今回はメインPCが所属するメインのネットワークから、Ciscoの検証用ネットワーク機材を管理するためのターミナルサーバーを切り離してみた。

f:id:t-hom:20220417162058p:plain

我が家のメインスイッチCiscoのCBS250にはVLANの機能が付いている。そしてCBS250はL2スイッチングだけではなく、L3ルーティングも部分的に対応しているのでネットワークを分割してもVLANを跨いで通信ができる。
特にアクセス制御リストで拒否設定等しないかぎり、問題なく通信できるはずだった。

設定内容

CBS250

既存のVLAN1に加えてVLAN99を作成してIPアドレス192.168.99.1を付与、ターミナルサーバーが接続されたポートをVLAN99に所属させた。

ターミナルサーバー

IPアドレス192.168.99.2を付与し、デフォルトゲートウェイを192.168.99.1に設定。

NTT ホームゲートウェイ

192.168.99.0/24ネットワーク宛ての通信が来たら192.168.1.254へ転送されるようルーティングを設定。

ハマったところ

同じネットワークに所属させていたときは通信できていたのだが、ネットワークを分けたことでTELNET接続してもブランク表示されるようになってしまった。

Wiresharkでパケットを見てみると、3WAYハンドシェイクの後TCP再送が繰り返されている。
f:id:t-hom:20220417161657p:plain

WindowsファイアウォールをOFFにしてみると、ちゃんと通信できることが分かった。
f:id:t-hom:20220417161737p:plain

しかしメインPCから開始した通信なのになんで手元のファイアウォールで蹴るのかさっぱり分からない。
ターミナルサーバーからしたら通信したいと言われてデータ送ったら捨てられるとか意味不明。。

原因

メインPC側で返信が破棄されてしまう原因は、往路と復路が違うためにファイアウォールが自分が送った通信の返信だと判断できずにデータを捨てていたっぽい。

経路をa~hの記号で図示してみた。
f:id:t-hom:20220417162902p:plain

(a) メインPCは192.168.99.2宛てに通信しようとするが、その時点で自分と異なるネットワーク宛てだと分かるので、デフォルトゲートウェイであるNTT-ホームゲートウェイのMACアドレスに宛ててフレームを送る。

(b) Cisco CBS250はMACアドレスをみてフレームをNTTホームゲートウェイへ転送する。この時点ではL2スイッチとしての仕事しかしてない。だってMACアドレス的には俺宛じゃないし。。という理屈。

(c) NTTホームゲートウェイは自分のMAC宛に届いたフレームからIPパケットを取り出して、自分が持ってる経路情報を確認。次の宛先がCBS250であることを知る。そこでCBS250のMACアドレス宛にフレームを転送する。もし人格があれば「おいCBS250、お前のとこやん」て思ってるはず。

(d) CBS250はここで初めて、自分のMACアドレス宛に届いたフレームからIPパケットを見て、自分に繋がってるCiscoターミナルサーバー宛であることを知り、そちらに転送する。

(e) ターミナルサーバーはデータを受け取り、返信先を確認する。

(f) ターミナルサーバーは返信先の192.168.1.152が別ネットワーク宛てだと分かるのでデフォルトゲートウェイに設定してあるCBS250のMAC宛に返信データのフレームを送信。

(g) CBS250は自分のMACアドレス宛に届いたIPパケットから配送先が192.168.1.152であることを確認し、自分に接続されたネットワーク宛てなのでMACアドレスのリスト(ARPテーブル)を確認。自分に接続されたメインPC宛てであることが分かったのでメインPCのMACアドレス宛にフレームを転送。

(h) メインPCに到着したデータはファイアウォールで検査される。しかし送ったのと異なるルートで返ってきた返信は、PC側から開始した一連の通信であることが認められず、ファイアウォールはこの返信を破棄する。

ということらしい。

改善策

とりあえずメインPCをスタティックIPにして、手動でデフォルトゲートウェイをCBS250にしてたみたところ、行きも返りもNTTホームムゲートウェイを通らずCBS250だけで完結したのでファイアウォールではじかれるようなこともなくなった。

まぁこのままだとネット接続できないのでCBS250側に0.0.0.0/0宛ての通信を192.168.1.1へ通信させるよう設定。(いわゆるデフォルトゲートウェイ)。

これでひとまずの改善策となった。

本格的に設定を固める際はDHCPサーバー側で配布するゲートウェイをCBS250にしてやる必要があるが、とりあえず実証実験は終わったので一旦満足。

以上

Amazon AWS S3とSambaサーバーの組み合わせで、自宅用の実用的なアーカイブソリューションを構築してみた

今回はAmazon AWS S3とSambaサーバーを組み合わせて、とりいそぎ使えそうなアーカイブソリューションが構築できたのでご紹介。
めったに参照しないけど無くなると困るファイルを、安全かつ恒久的にアーカイブする。

概要と使い方

仕組みを説明したのが以下の図。
f:id:t-hom:20220410223600p:plain

  1. Sambaサーバー上にあるToArchiveというフォルダーに大事なファイルを配置する。
  2. cronジョブにより実行されるバッチがToArchive内のファイルをAmazon S3へアップロードする
  3. 上記バッチはアップロードが完了したローカルファイルをSambaサーバー上のArchiveフォルダへ移動する。

この仕組みのメリットは図上の吹き出し参照。

特にファイルを間違って消しそうになっても、Archiveフォルダは書き込みアクセス権が無くて弾いてくれるのがありがたい。
f:id:t-hom:20220410230213p:plain

各種設定

/etc/samba/smb.conf

[global]
        dos charset = CP932
        unix charset = UTF-8

        workgroup = WORKGROUP
        security = user

        interfaces = 192.168.1.107 127.0.0.1
        bind interfaces only = yes
        socket address = 192.168.1.255

        printcap name = /dev/null
        log level = 1

[Archive]
        path = /mnt/pool/Archive
        writeable = no

[ToArchive]
        path = /mnt/pool/ToArchive
        writeable = yes

ここでのポイントは[Archive]のwritableをnoにしておくこと。

設定ファイルを保存したらsmbを再起動。

sudo systemctl restart smb.service

SE Linux環境でsambaに特定フォルダへアクセスを許可する設定コマンド

sudo chcon -t samba_share_t /mnt/pool/ToArchive
sudo chcon -t samba_share_t /mnt/pool/Archive

cronから実行するシェルスクリプト /home/thom/archive.sh

#!/bin/bash
FILES="/mnt/pool/ToArchive/*"

if [ -n "$(ls -A /mnt/pool/ToArchive/)" ];then
  for f in $FILES
  do
    echo "Processing: $f"
    /usr/local/bin/aws s3 cp $f s3://ここにS3バケット名/ --profile ここにユーザー名
    mv $f /mnt/pool/Archive/
  done
else
  exit
fi

crontab

*/1 * * * * /home/thom/archive.sh

関連のある過去記事

thom.hateblo.jp
thom.hateblo.jp

要改善点

同名の別ファイルをうっかりToArchiveに置いてしまうと恐らくアーカイブが上書きされてしまうので、バッチ処理にif文でArchiveに同じファイル名が存在するときは処理をしないか、あるいはArchiveErrorというフォルダを作ってそこに移動するか等考えたい。
これはさっさとやらないとまずそう。Redmineにタスク登録しておこう。

あと暗号化の仕組みもまだ導入できてない。
検証は以下の記事で終わってるので面倒くさがってるだけなんだけど、Redmineタスクには登録したので、近いうちに組み込みたいと思う。
thom.hateblo.jp

Linux(CentOS 7)からAWSのS3ストレージへ重要ファイルをクラウドアーカイブする

今回はAmazon AWSのS3ストレージサービスを活用して重要なファイルをクラウドにアーカイブしてみる。

アーカイブとは

バックアップとアーカイブの違いは、日常的に更新するかどうかである。
例えばWindows10には標準でOneDriveが付いてくるので、ここに入れておけばハードディスクが故障してもクラウド上にファイルは残る。そういう意味でクラウドバックアップは既に普段使いしていると言える。

ただ、ユーザー操作のミスでファイルを書き換えてしまったり消してしまった場合はクラウド上でもそうした操作が同期されてデータが消失するリスクが残る。

したがって、更新はしないけど無くなると困る重要なファイルは、どこか別のところに避けておきたい。
これが基本的なアーカイブの考え方である。大切な思い出の写真とかが分かりやすい例かなと思う。

Amazon AWS S3とは

AWS S3は低価格なストレージサービスで、バケット(要するにバケツ)という場所にファイルをどんどん放り込んでいくという使い方をする。一般的なファイルシステムのようにフォルダーで整理するということは想定されておらず、とにかく一個所にファイルを放り込んでいくイメージ。またデフォルトで3か所のデータセンターにデータが保存されるのでデータの耐久性は99.999999999%(イレブンナイン)。今回のアーカイブ用途にはうってつけだ。

料金表はこちら。
aws.amazon.com

多く見積もって最大で100GB保管する想定で計算してみたところ、月額515円と個人でも十分支払い可能な料金である。(22年4月10日現在の為替レートによる)
f:id:t-hom:20220410132942p:plain

※情報が間違っていても責任は負いかねるので実際に活用される方は自己責任で。

※S3ストレージが安いだけで、他のAWSサービスは普通に高いので注意。特にインターネットで一般公開するようなサーバーはアクセス量が予測できないのでちゃんと調べて計算しないと怖い印象。

実際に設定してみる

※AWSのアカウント取得とかは割愛。
※ここで紹介するのは私がこうやったという手順なので、適切かどうかは保証しない。

アクセスユーザーの作成

まずサービスメニューからIAMを開く
f:id:t-hom:20220410135054p:plain

IAMというのはIdentity and Access Managementの略で、要はIDとアクセス権限の管理。

ユーザーメニューからユーザーを追加をクリック
f:id:t-hom:20220410135646p:plain

ユーザー名を決めて、アクセスキー・プログラムによるアクセスにチェックを入れて次のステップへ
f:id:t-hom:20220410135716p:plain

既存ポリシー直接アタッチを選択してs3を検索、出てきたAmazonS3FullAccessを選択して次のステップへ
f:id:t-hom:20220410135752p:plain

タグは特に要らないのでそのまま次のステップへ
f:id:t-hom:20220410135946p:plain

確認画面で問題なければユーザーの作成をクリック
f:id:t-hom:20220410140008p:plain

アクセスキーとシークレットアクセスキーを控えておく
f:id:t-hom:20220410140034p:plain

以上でユーザー作成は完了

S3ストレージの開設

サービスメニューからS3を開く
f:id:t-hom:20220410140310p:plain

バケットメニューからバケットを作成する
f:id:t-hom:20220410140326p:plain

バケット名とリージョンを選択した後、下のほうにスクロールして作成をクリック
f:id:t-hom:20220410140424p:plain
バケット名は他人とも被ってはいけないらしい。
とりあえずテストで作る場合は日付時刻を入れると被る可能性が極めて低いのでおススメ。

S3のリージョンはローカルにもコピーを持つ前提で災害対策を考えるなら自分が住んでる地域とは少し離れてる方が理想。私は関西住まいなので東京リージョンを選択した。
f:id:t-hom:20220410141130p:plain

これで、Linuxにインストールするawsのクライアントからは「s3://testbacket202204101241/」としてアクセスできるようになる。
また、このページからブラウザ上でのアップロード・ダウンロード・削除などの操作も可能。

Linux上の設定 (CentOS 7の場合)

CentOS 7の場合は次のようにしてpython3とawscliをインストール

sudo yum update
sudo yum install python3
sudo pip3 install awscli

awscliの接続設定を行う
aws configure --profile AWSのIAMで作ったユーザー名 と入力するとアクセスキー、シークレットキー、デフォルトリージョン名、出力フォーマットを聞かれるので順次入力する。つぎに環境変数でデフォルトプロファイルに設定。

aws configure --profile testuser1

# 上記を実行すると以下4点が順番に質問される。
AWS Access Key ID [None]: ここにさっき控えたアクセスキーを張る
AWS Secret Access Key [None]: ここにさっき控えたシークレットアクセスキーを張る
Default region name [None]: ap-northeast-1
Default output format [None]: json

# 今作ったプロファイルをデフォルトに指定
export AWS_DEFAULT_PROFILE=testuser1

以上で使う準備が完了。

使用方法

Linux上でアップロードしたいファイルを指定する。

# aws s3 cp アップロードしたいファイル s3://バケットのパス/
aws s3 cp test.jpg s3://testbacket202204101241/

あとはブラウザからAWSのS3にアクセスしてバケットを覗くとちゃんとデータが入ってるのが分かる。

AWSを初めて触る方にオススメの書籍

あんまり重たい書籍だと途中で嫌になるけど、以下の書籍はさくっと読めて概念を理解するのにちょうどよかった。

この記事で書いたような具体的な手順は解説されていないが、参考URLが書かれているのでそこまで困らないと思う。

ただしある程度のインフラ知識が前提なので、IT未経験ですという人に向けた書籍ではない。
基本情報技術者程度の知識か、もしくは自分でオンプレミスのサーバー運用経験があればよく理解できると思う。

以上

gzipとOpenSSLを使ったファイル圧縮&暗号化&復号化

今回は特定フォルダ内のファイルを個別に圧縮&暗号化するBashスクリプトをご紹介。

挙動

Archiveフォルダに入れた複数ファイルを①gzipで圧縮し、②OpenSSLで暗号化し、③Encryptedフォルダに出力し、④処理済のオリジナルファイルはArchivedに移動させる。
f:id:t-hom:20220409175757p:plain

用途

最近自宅にSambaサーバーを立てたのだが、重要なファイルはこれだけでは障害時に不安が残るので、AWSのS3ストレージ等に自動転送しておきたい。
その際に流出対策として今回暗号化の仕組みを作成した。

暗号化するコード

今回はパスワードもコード内に入れてしまう。
クラウド保存時の流出対策なので、ローカルで見えてしまうのはまぁ良しとする。

#!/bin/bash
FILES="/home/thom/Test/Archive/*"
TIMESTAMP=`date +'%Y%m%d%H%M%S'`
PASSWORD="pass"

for f in $FILES
do
  echo "Encripting: $f"
  gzip -c $f | openssl enc -e -aes-256-cbc -pbkdf2 -iter 99999 -salt -k $PASSWORD -out /home/thom/Test/Encrypted/${TIMESTAMP}_$(basename -- $f).aes
  mv $f /home/thom/Test/Archived/
done

実行するとArchiveフォルダに入っているファイルが処理され、YYYYmmddHHMMSS_オリジナルファイル名.aesとしてEncryptedフォルダに保存される。
処理前のオリジナルはArchivedに移動される。

復号化するコード

これを実行するとEncryptedフォルダ内のファイルがDecryptedフォルダーに復号化される。

#!/bin/bash
FILES="/home/thom/Test/Encrypted/*"
PASSWORD="pass"
for f in $FILES
do
  echo "Decrypting: $f"
  openssl enc -d -aes-256-cbc -pbkdf2 -iter 99999 -salt -k $PASSWORD -in $f | gzip -d > /home/thom/Test/Decrypted/$(basename -- ${f%.*})
done

参考サイト

qiita.com
qiita.com

アーカイブと圧縮と暗号化について

Windows文化だとあんまり意識することなく、パスワード付きzipとして一緒くたにしているケースが多い。
本来はそれぞれ別の概念なので、軽く説明しておこうと思う。

アーカイブ

複数ファイルやフォルダ等を一つのファイルにまとめる機能。
Linuxだとtarコマンドが有名。

圧縮

特定の圧縮アルゴリズムを用いてファイルの容量を削減する機能。
Linuxだとgzipコマンドが有名。
Linuxでよく見かける.tar.gzという拡張子はtarでアーカイブした後にgzipで圧縮されているファイルということ。

暗号化

特定の暗号化アルゴリズムを用いてファイルの中身を第三者に知られないようにする機能。
Windowsのパスワード付きzipはツールを使えば比較的簡単に暗号化を破られると言われているので今回はOpenSSLというツールでAESというより強力な暗号化アルゴリズム使って暗号化した。

以上

Raspberry Piと赤外線人感センサーを使った睡眠時間ロギングシステム

今回はRaspberry Pi Zeroと赤外線人感センサーを使って睡眠時間のロギングシステムを構築したので紹介。

といっても全然大したものではなくて、単純にベッドに人感センサーを向けて定期的に検出を行い、検出されたらログに追記するという仕組み。

モノ

試作1号。。ひどい見た目だけど検証用途としては要はデータ取れたらなんでも良いのでマスキングテープが活躍。
f:id:t-hom:20220409095643p:plain
ラズパイのGPIOピン18番に人感センサーのデータピンを接続している。

センサーはAmazonでHC-SR501を検索すると適当なメーカーのものがいくつもヒットする。
私はHiletGo社のものを買ったけど今は在庫切れのようだ。まぁどれでも同じだと思う。

プログラム

ラズパイで実行させる検出プログラム

以下は10秒ごとに人体検出を試み、検出されたらファイルに記録して次の検出まで60秒間検出停止する。
まだ検証段階なので特にデーモン化等は考えておらず、SSHで繋いで実行させたらSSHセッションそのまま保持させている。

from datetime import datetime
import time
import RPi.GPIO as GPIO

INTERVAL = 10
SLEEPTIME = 60
GPIO_PIN = 18

GPIO.setmode(GPIO.BCM)
GPIO.setup(GPIO_PIN, GPIO.IN)

if __name__ == '__main__':
    try:
        print ("To Cancel:CTRL+C")
        cnt = 1
        while True:
            if(GPIO.input(GPIO_PIN) == GPIO.HIGH):
                with open('/home/thom/data.csv', 'a') as f:
                    print(datetime.now().strftime('%Y-%m-%d %H:%M:%S') + ', 1', file=f)
                print(datetime.now().strftime('%Y-%m-%d %H:%M:%S') + ', 1')
                cnt = cnt + 1
                time.sleep(SLEEPTIME)
            else:
                print(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
                time.sleep(INTERVAL)
    except KeyboardInterrupt:
        print("Closing")
    finally:
        GPIO.cleanup()
        print("GPIO cleaned")

PCでデータ加工用に作成中のプログラム

出力データはそのままでは非常に見づらいのでpandasを使って1時間単位の検出回数を集計する。

import pandas as pd

df = pd.read_csv('./sleepdata.csv', header=None, parse_dates=[0])
df = df.resample('60T', on=0).sum()
df = df.loc[df[1] > 0]

with pd.option_context('display.max_rows', None, 'display.max_columns', None):
    print(df)

1週間記録してみたデータ

プログラムで加工しただけでも一応分かるものの、入眠と起床が分かりにくいので空行で区切ってコメントを付けてみた。
見返すと酷い生活リズム。。最近色々テクノロジー系にハマってたのでその弊害かなと思う。

# 仮眠
2022-04-01 17:00:00  55
2022-04-01 18:00:00  29

# 睡眠
2022-04-02 01:00:00  10
2022-04-02 02:00:00  12
2022-04-02 03:00:00  40
2022-04-02 04:00:00  28
2022-04-02 05:00:00  37
2022-04-02 06:00:00  20
2022-04-02 07:00:00  30
2022-04-02 08:00:00  60
2022-04-02 09:00:00  45
2022-04-03 06:00:00  32
2022-04-03 07:00:00  35
2022-04-03 08:00:00  42
2022-04-03 09:00:00  53
2022-04-03 10:00:00  36

# 仮眠→爆睡
2022-04-03 17:00:00  13
2022-04-03 18:00:00  36
2022-04-03 19:00:00  50
2022-04-03 20:00:00  35
2022-04-03 21:00:00  52
2022-04-03 22:00:00  14

# 睡眠
2022-04-04 02:00:00  34
2022-04-04 03:00:00  13
2022-04-04 04:00:00  40
2022-04-04 05:00:00  39
2022-04-04 06:00:00  43
2022-04-04 07:00:00  59
2022-04-04 08:00:00  25
2022-04-05 03:00:00  20
2022-04-05 04:00:00  47
2022-04-05 05:00:00  52
2022-04-05 06:00:00  35
2022-04-05 07:00:00  53
2022-04-05 08:00:00  51
2022-04-05 09:00:00   5

# 仮眠
2022-04-05 17:00:00  18
2022-04-05 18:00:00  58
2022-04-05 19:00:00  60
2022-04-05 20:00:00  48

# 睡眠
2022-04-06 02:00:00  14
2022-04-06 03:00:00  37
2022-04-06 04:00:00  50
2022-04-06 05:00:00  22
2022-04-06 06:00:00  48
2022-04-06 07:00:00  45
2022-04-06 08:00:00  52

# 睡眠
2022-04-06 23:00:00  28
2022-04-07 00:00:00  31
2022-04-07 01:00:00  55
2022-04-07 02:00:00  49
2022-04-07 03:00:00  52
2022-04-07 04:00:00  44
2022-04-07 05:00:00  60
2022-04-07 06:00:00  60
2022-04-07 07:00:00  51
2022-04-07 08:00:00  46
2022-04-07 09:00:00   9

# 仮眠
2022-04-07 17:00:00  10
2022-04-07 20:00:00  10

# 睡眠
2022-04-07 22:00:00  14
2022-04-07 23:00:00  52
2022-04-08 00:00:00  55
2022-04-08 01:00:00  44
2022-04-08 02:00:00  51
2022-04-08 03:00:00  60
2022-04-08 04:00:00  60
2022-04-08 05:00:00  17
2022-04-08 06:00:00   8

ちなみに平日の9時台に数件検出されてるのはサボりとか遅刻ではなくて、センサーの検出時間を最大にしてるのでベッドを離れても数分検出が続くためだ。ここ2年くらいは在宅勤務なので8時55分くらいまで寝てることがある。。

難点と改良

最初はセンサーの感度をMAXにしてたけど、部屋の前を横切っただけでベッド上にいる判定になってしまうので、検出感度を下げてみた。ところが感度が低いと検出精度が悪かったので取り付け方向を変えて段ボールフードで覆うことで枕を狙い撃ちすることにした。これまたひどいビジュアルだけど。。
f:id:t-hom:20220409102214p:plain

あとそもそも人感センサーは動きを検出するので静止してると検出しない。
普通の人間は寝てる間もモゾモゾするので検出しないわけではないものの、周期的に静止状態になるので深い眠りに落ちている間の検出ができなかったりする。本当は15分単位でデータ集約したかったんだけど、それだと起きてる判定になる時間が多かったので精度を落として1時間単位の集約とした。

まぁそもそもベッドに居る=寝てるとは限らないので、Kindle読んでたりスマホいじってたりする時間を除くと最近十分な睡眠がとれているかどうかは怪しいのだが。

今後の展望としてはデータ加工まではラズパイ側で対応して、メイン機に転送してグラフ表示をしたい。
3Dプリンターでちゃんとしたラズパイケースとかセンサー用のフードを作るのはそこまでできた後で良いかな。。ダサイけど。

以上

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。