-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtemp.py
More file actions
225 lines (190 loc) · 9.69 KB
/
temp.py
File metadata and controls
225 lines (190 loc) · 9.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
import serial
import time
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
from smartcard.System import readers
from smartcard.util import toHexString
import threading
import os
import sys # sysモジュールをインポート
# --- Flaskアプリケーションの設定 ---
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///access_log.db' # データベースファイル名
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # SQLAlchemyイベントトラッキングを無効化
db = SQLAlchemy(app)
# --- データベースモデルの定義 ---
# ユーザー情報テーブル
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
idm = db.Column(db.String(160), unique=True, nullable=False) # FeliCaのIDm
name = db.Column(db.String(80), nullable=False)
# 必要に応じて、さらにカラムを追加可能 (例: role, department)
def __repr__(self):
return f'<User {self.name} ({self.idm})>'
# 入退室履歴テーブル
class AccessLog(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.now)
status = db.Column(db.String(20), nullable=False) # '入室' or '退室'
user = db.relationship('User', backref=db.backref('access_logs', lazy=True))
def __repr__(self):
return f'<AccessLog {self.user.name} {self.status} at {self.timestamp}>'
# --- Arduinoへのシリアル通信設定 ---
# Arduinoのシリアルポートを正確に指定してください。
# Raspberry Piでは通常 '/dev/ttyACM0' や '/dev/ttyUSB0' です。
# 'ls /dev/tty*' コマンドで確認できます。
ARDUINO_PORT = '/dev/ttyACM0'
BAUD_RATE = 9600
# グローバル変数としてシリアルポートオブジェクトを保持
ser = None
# シリアルポートの初期化関数
def init_serial_connection():
global ser
try:
ser = serial.Serial(ARDUINO_PORT, BAUD_RATE, timeout=1)
time.sleep(2) # Arduinoのリセット時間を待つ
print(f"Serial: Connected to Arduino on {ARDUINO_PORT}")
send_to_arduino("System Ready", "Place your card")
except serial.SerialException as e:
print(f"Serial Error: Could not connect to Arduino on {ARDUINO_PORT}. {e}")
ser = None # 接続失敗時はNoneにする
# --- PaSoRiリーダーとFeliCaカード読み取り関数 ---
def read_felica_card_idm():
try:
available_readers = readers()
# print(f"Available readers: {available_readers}") # デバッグ用
if not available_readers:
print("NFC Error: No smart card readers found. Is PaSoRi connected and pcscd running?")
return None
# PaSoRiリーダーを選択 (名前でフィルタリング)
# あなたの環境でのPaSoRiの正確な名前(pcsc_scanで確認)に合わせてください
pasori_reader = next((r for r in available_readers if 'PaSoRi' in str(r) or 'FeliCa' in str(r)), None)
if not pasori_reader:
print("NFC Error: PaSoRi reader not found. Please check its name or connection.")
return None
# print(f"Using reader: {pasori_reader}") # デバッグ用
connection = pasori_reader.createConnection()
connection.connect()
# IDmを取得するPCSCコマンド
idm_apdu = [0xFF, 0xCA, 0x00, 0x00, 0x00] # GET DATA (IDm) command
response, sw1, sw2 = connection.transmit(idm_apdu)
if sw1 == 0x90 and sw2 == 0x00:
idm = toHexString(response).replace(" ", "")
return idm
else:
print(f"NFC Error: Failed to get IDm. SW: {hex(sw1)} {hex(sw2)}")
return None
except Exception as e:
# カードが離された、または読み取りエラーの場合もここにくるので、エラー表示はデバッグ時のみ推奨
# print(f"NFC Read Exception: {e}")
return None
# --- Arduinoへデータを送信するヘルパー関数 ---
def send_to_arduino(line1, line2):
if ser and ser.is_open: # serが有効かつ開いていることを確認
data_to_send = f"{line1}\n{line2}\n"
try:
ser.write(data_to_send.encode('utf-8'))
print(f"Arduino Sent: '{data_to_send.strip()}'")
except serial.SerialException as e:
print(f"Arduino Send Error: {e}. Attempting to reconnect...")
ser.close() # エラー時は一度閉じる
init_serial_connection() # 再接続を試みる
else:
print("Arduino Not Connected. Cannot send data. (Please check serial port setup)")
# --- カード読み取りと処理のメインループ(別スレッドで実行) ---
def card_reading_loop():
send_to_arduino("System Starting", "") # 起動メッセージ
time.sleep(1)
send_to_arduino("Ready", "Place your card")
while True:
idm = read_felica_card_idm()
if idm:
with app.app_context(): # データベース操作はFlaskのコンテキスト内で
user = User.query.filter_by(idm=idm).first()
if user:
# ユーザーが見つかった場合、入退室を切り替える
last_log = AccessLog.query.filter_by(user_id=user.id)\
.order_by(AccessLog.timestamp.desc()).first()
if last_log and last_log.status == '入室':
new_status = '退室'
else:
new_status = '入室'
log_entry = AccessLog(user_id=user.id, status=new_status)
db.session.add(log_entry)
db.session.commit()
print(f"Access recorded: {user.name} - {new_status}")
send_to_arduino(user.name, new_status)
else:
# 未登録ユーザー
print(f"Unknown card detected! IDm: {idm}")
send_to_arduino("Unknown Card", "Please register")
time.sleep(5) # LCD表示時間 + 冷却期間 (連続読み取り防止)
send_to_arduino("Ready", "Place your card") # 次の読み取りを促すメッセージ
time.sleep(0.5) # ポーリング間隔 (カードがなくても一定間隔でチェック)
# --- Webアプリケーションのルート定義 ---
# ホームページ(入退室履歴表示)
@app.route('/')
def index():
access_logs = AccessLog.query.order_by(AccessLog.timestamp.desc()).limit(20).all() # 最新20件
return render_template('index.html', logs=access_logs)
# ユーザー管理ページ
@app.route('/users', methods=['GET', 'POST'])
def manage_users():
if request.method == 'POST':
action = request.form.get('action')
user_id = request.form.get('user_id')
if action == 'add':
idm = request.form['idm'].strip() # 前後の空白を削除
name = request.form['name'].strip()
if idm and name:
# 既に登録済みのIDmでないかチェック
existing_user = User.query.filter_by(idm=idm).first()
if not existing_user:
new_user = User(idm=idm, name=name)
db.session.add(new_user)
db.session.commit()
else:
print(f"Warning: IDm {idm} already exists for user {existing_user.name}")
elif action == 'delete' and user_id:
user_to_delete = User.query.get(user_id)
if user_to_delete:
# ユーザーに関連するログも削除(Cascade deleteを設定していない場合)
AccessLog.query.filter_by(user_id=user_id).delete()
db.session.delete(user_to_delete)
db.session.commit()
return redirect(url_for('manage_users'))
users = User.query.all()
return render_template('users.html', users=users)
# --- アプリケーション起動時の処理 ---
if __name__ == '__main__':
# シリアル接続の初期化を試みる (接続失敗してもアプリは起動)
init_serial_connection()
# Flaskアプリケーションコンテキストを手動でプッシュ
# これにより、db.create_all()などの操作がアプリの起動時に実行可能になる
with app.app_context():
db.create_all() # 存在しないテーブルを作成
# テストユーザー(一度だけ実行)
# データベースが空の場合のみ実行される
if not User.query.first():
print("Adding initial users...")
user1 = User(idm="F637CF05", name="Soma Taniguchi") # ここをあなたのIDmに合わせる!
# user2 = User(idm="YOUR_SECOND_IDM_HERE", name="別のユーザー") # 必要なら追加
db.session.add(user1)
# db.session.add(user2)
db.session.commit()
print("Initial users added.")
# カード読み取りループを別スレッドで開始
reader_thread = threading.Thread(target=card_reading_loop, daemon=True)
reader_thread.start()
# Flaskアプリを起動(外部からアクセス可能にするためhost='0.0.0.0')
# デバッグ中はdebug=TrueでもOKだが、本番運用ではFalse推奨
try:
app.run(host='0.0.0.0', port=5000, debug=False)
except Exception as e:
print(f"Flask App Error: {e}")
# アプリが終了する前にシリアルポートを閉じる
if ser and ser.is_open:
ser.close()
sys.exit(1) # エラー終了