Movatterモバイル変換


[0]ホーム

URL:


株式会社en-gine株式会社en-gine
株式会社en-ginePublicationへの投稿
🔐

多店舗展開するジムの会員入退室管理を材料費数万円で実現し、24時間営業にした話

に公開
2024/07/16
5件

ジムの会員管理システムを作った僕に「エニタイムフィットネスみたいなことがしたい」とジムを家族経営するお客さんから相談された。

「えっ!?会員管理を作ったついでにエニタイムフィットネスみたいな仕組みをやりたい!?予算は無い!?不正防止のため、入退室時の写真も撮りたい?!ログもとりたい!?」

さすが筋トレに明け暮れてるオーナーさんの要望はマッチョだと思った。

普通にやれば電子錠の仕組みや工事やらで一店舗あたり数百万から一千万掛かるような仕組みだろう。

そんな予算無いみたいだし、既存の店舗をそんな大々的に工事もできない。そもそも自分にそんな工事の知識もない。

結果Raspberrypiを使い、それを一店舗予算10万円代で実現、会員カードを他店舗と共有した24時間営業にできた。

その詳しい技術的な内訳を共有する。

(なお執筆時点では2024年だが、これ自体は5年前、2019年の仕事である。)

前提

  • 当時県内で3店舗ほど家族経営されていたフィットネスジム
  • その時点では朝10時くらいから夜10時までスタッフが常駐される時間だけ空けていた
  • それをさらに多店舗展開するにあたって24時間営業することになった
  • その時の会員管理システムを自分で作った
  • その時に鍵の入退室も含めて管理できないかと相談があった

システム内容

  • 鍵をICカードにかざした時に会員が正常なステータスかをAPIで判断
  • 正常なステータスであれば鍵を開錠
  • 共連れなどを防ぐため、その時の様子はカメラで撮影し、入退室ログとして残す

鍵の写真

## 鍵開錠の様子(開場後自動で閉じる)
https://youtube.com/shorts/cHkDXyWnqGQ?feature=share

https://youtube.com/shorts/L38OvMiYM4k?feature=share

検討した事

5年前は民泊などの影響もあり、ちょうど鍵のIOT機器やサービスが出始めたころであった。
そこで別のAkerunなどの開錠システムも検討したが、

  • こちらで制作したジムの会員管理とシームレスに連携できない

  • 電池式で不安
    (電池が切れた場合夜中に入れなくなる可能性がある。)

  • 要件として退出時にもカメラを撮影したいとリクエストされていた

  • 月額サブスクをクライアントが嫌がった

  • どうしてもネットワークを介して解錠命令を送るためカードをかざしたあとのラグが気になった

等の点により自前で実装することにした。

構成

結果、クライアントがネットで探してきた「NOAKEL」という端末を調べた所、これが使えそうだと思った。
NOAKELの画像

  • 電源式である
  • 接点入力が出来れば開錠命令をリモコンから発信できる

そこでRaspberryPiとICカードリーダーを使い、

またテストしたところ、ガラス戸レベルであれば外側から触れても接触にあまり問題なかったため、扉の内外両方からICカードをかざすことが可能であった。

機器の構成

図示するとこんな感じ

講師図

ソフトウェアの仕組み

図示するとこんな感じ

ソフトウェアの仕組み

RaspberryPiのプログラム

新規会員登録時にICカードのIDを紐づけてもらう作業を行い、それを会員に対して発行する。

会員が入室時にICカードリーダーをかざした際、読みとったIDを会員管理システムにAPIで確認し、OKな会員かNGな会員か(例えば滞納してるなど)をチェックする。

RaspberryPi側では、GPIOを操作し、タッチ音としてリレー出力で圧電ブザーを鳴らすと共に、cv2を使いRaspberryPiにUSBでつないだカメラでキャプチャを行い、それをRaspberryPiのシリアル番号と共にアプリケーション側に送る。

そして開錠OKな会員であれば、GPIOの接点出力モジュールからNOAKELに対して開錠命令を送る。

main.py
#!/usr/bin python# -*- coding: utf-8 -*-import osimport requestsimport jsonimport binasciiimport nfcimport threadingimport timeimport cv2import base64import mimetypesimport RPi.GPIOas GPIOfrom classesimport sound#My Definition Classfrom classesimport relay#My Definition Class#from classes import camera #My Definition Classfrom libimport functions#My Definition Functions# Suica待ち受けの1サイクル秒TIME_cycle=1.0# Suica待ち受けの反応インターバル秒TIME_interval=0.2# タッチされてから次の待ち受けを開始するまで無効化する秒TIME_wait=3GPIO_PORT=4#realyIMG_FILE='/home/pi/amegym/image/picture.jpg'API_URL='[APIのURL]'ACCESS_TOKEN='[APIのアクセストークン]'# NFC接続リクエストのための準備target_req_felica= nfc.clf.RemoteTarget("212F")# 106A(NFC type A)で設定target_req_nfc= nfc.clf.RemoteTarget("106A")cam=Nonerasp_id= functions.getserial()#シリアルナンバーの取得print('Waiting for Tag...')whileTrue:# USBに接続されたNFCリーダに接続してインスタンス化    clf= nfc.ContactlessFrontend('usb')# clf.sense( [リモートターゲット], [検索回数], [検索の間隔] )    target_res= clf.sense(target_req_nfc,target_req_felica,  iterations=int(TIME_cycle//TIME_interval)+1, interval=TIME_interval)# カメラデバイスを取得if camisNone:        cam= cv2.VideoCapture(0)# カメラFPSを60FPSに設定        cam.set(cv2.CAP_PROP_FPS,30)        cam.set(cv2.CAP_PROP_FRAME_WIDTH,1920)        cam.set(cv2.CAP_PROP_FRAME_HEIGHT,1080)ifnot target_resisNone:        beep= sound.SOUND()        beep.onRead()        tag= nfc.tag.activate(clf, target_res)        tag.sys=3#IDmを取り出す        idm= binascii.hexlify(tag._nfcid).upper()# readで画像をキャプチャ、imgにRGBのデータが入ってくる        res, img= cam.read()# 保存        cv2.imwrite(IMG_FILE, img)print('took picture!')        prepend_info='data:%s;base64'% mimetypes.guess_type(IMG_FILE)[0]        base_64_data= base64.b64encode(open(IMG_FILE,'rb').read()).decode("ascii")        image_data_base64='%s,%s'%(prepend_info, base_64_data)print('data encoded')        request_args={"RFID": idm,"DeviceID": rasp_id,"access_token": ACCESS_TOKEN,"image":   image_data_base64,}#session = requests.session()        serverRes=  requests.post(API_URL, request_args)print(serverRes)if serverRes.status_code==200:print(serverRes.text.encode("utf-8"))            res= serverRes.json()if res["msg"]=="success":print("relay on")                GPIO.setmode(GPIO.BCM)                GPIO.setup(GPIO_PORT, GPIO.OUT, initial=1)                GPIO.output(GPIO_PORT,1)                time.sleep(0.5)                GPIO.output(GPIO_PORT,0)                GPIO.cleanup()else:                beep.onFalse()#end ifelse:            beep.onError()print(res)        cam.release()        cam=Nonedel beepprint('sleep '+str(TIME_wait)+' seconds')        time.sleep(TIME_wait)    clf.close()#end while

まとめ

RaspberryPiも半導体の影響で値上がりしたが、材料費でNOAKELを入れても数万円でから十数万円程度で店舗を24時間営業にすることが出来た。

これを現在6店舗で展開しているが、大きなトラブルも無く5年間運用している。

Tomohide Hirakawa(平川知秀)

株式会社en-gine代表。AIDXで企業の悩みを解決します。AIチャット、AI OCR、AI 営業ツールTypeScript、Next.js、Go、Flutter、AWS、Google Cloud各種

株式会社en-gine

AIをフル活用した生産的な実装。言語特性とアーキテクチャを理解したクリーンなコード。テスト網羅性を意識した堅実なプログラム。en-gineではエンジニア(未経験者はポテンシャル採用)、営業、PMを募集しております。人が困ってることをITで解決するために始めた会社です。案件もお気軽にご相談ください。

バッジを贈って著者を応援しよう

バッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。

romaroma

日本語の理解が不足してましたらすみません
材料費でNOAKELを入れても数万円でから十数万円程度と書かれてますが
一店舗予算10万円では赤字だったのでしょうか?

Tomohide Hirakawa(平川知秀)Tomohide Hirakawa(平川知秀)

わかりにくくてすみません。クライアントも見る可能性あるので、詳しい予算や原価はぼかしてます。一店舗あたり、ノアケル入れて20万以内で収まったと思います。当時の原価など、詳しく知りたければxなどからお問い合わせくださればお伝えします。

romaroma

ご回答ありがとうございます、繊細な事をうかがい失礼いたしました
非常に安価なシステムで驚きました

1
greenteagreentea

安定稼働!素晴らしいですね!

1
Tomohide Hirakawa(平川知秀)Tomohide Hirakawa(平川知秀)

ありがとうございます!
最初はどんな頻度で障害起きるか分からず、大変ドキドキでしたがうまく行って良かったです。

株式会社en-gine代表。AIDXで企業の悩みを解決します。AIチャット、AI OCR、AI 営業ツールTypeScript、Next.js、Go、Flutter、AWS、Google Cloud各種


[8]ページ先頭

©2009-2025 Movatter.jp