সেন্সর ও বায়োমেট্রিক
এই পাঠে যা শিখবেন
- কেন সেন্সর ডেটাকে একটি স্ট্রিম হিসেবে মডেল করা উচিত, এককালীন কল হিসেবে নয়
- একটি self-contained
Stream/Observableক্লাস —subscribe()ওemit() - রিয়েল-টাইমে আসা অ্যাক্সিলারোমিটার রিডিং থেকে ম্যাগনিটিউড গণনা করে "শেক" ডিটেকশন
- বায়োমেট্রিক অথেন্টিকেশনকে একটি সাধারণ ডিটারমিনিস্টিক গেট হিসেবে সিমুলেট করা
১ · সেন্সর ডেটা একটি স্ট্রিম, একটি একক মান নয়
পারমিশন বা লোকেশনের বিপরীতে, অ্যাক্সিলারোমিটার ও জাইরোস্কোপ একবার কল করে একটি উত্তর পাওয়ার মতো ফিচার নয় — এগুলো প্রতি সেকেন্ডে বহুবার নতুন রিডিং পাঠাতে থাকে যতক্ষণ অ্যাপ শুনছে। তাই এদের সঠিকভাবে মডেল করার একমাত্র উপায় একটি ইভেন্ট স্ট্রিমEvent Stream / Observableএকটি সোর্স যা সময়ের সাথে ক্রমাগত নতুন মান "emit" করে, এবং যেকোনো সংখ্যক "subscriber" সেই মানগুলো আসার সাথে সাথে পেতে পারে — একটি সাধারণ ফাংশন-কলের বিপরীতে যা শুধু একবারই একটি মান রিটার্ন করে। — যেখানে সাবস্ক্রাইবাররা প্রতিটি নতুন রিডিং আসার সাথে সাথে তা পায়।
২ · একটি self-contained Stream ক্লাস
নিচের Stream ক্লাসটি এই পাঠের জন্য স্বয়ংসম্পূর্ণভাবে সংজ্ঞায়িত — subscribe(callback)
একটি callback যোগ করে, আর emit(value) সাথে সাথে সব সাবস্ক্রাইবারকে নতুন মান দিয়ে কল করে
(synchronous — কোনো ডিলে নেই)। আমাদের সাবস্ক্রাইবার প্রতিটি (x, y, z) রিডিং থেকে
$\sqrt{x^2+y^2+z^2}$ সূত্রে প্রকৃত ম্যাগনিটিউড (মহাকর্ষের একক "g"-তে) গণনা করে, এবং তা একটি থ্রেশহোল্ডের সাথে
তুলনা করে শেক ডিটেক্ট করে।
# একটি self-contained ইভেন্ট স্ট্রিম -- subscribe()/emit()
import math
class Stream:
def __init__(self, name):
self.name = name
self._subscribers = []
def subscribe(self, callback):
self._subscribers.append(callback)
return callback
def emit(self, value):
for callback in self._subscribers:
callback(value)
SHAKE_THRESHOLD_G = 2.0 # এই ম্যাগনিটিউডের (g) বেশি হলে "শেক" ধরা হবে
def on_accelerometer_reading(reading):
"""প্রতিটি রিডিং আসার সাথে সাথে সত্যিকারের ম্যাগনিটিউড গণনা করে -- এটিই real-time ডিটেকশন"""
x, y, z = reading
magnitude = math.sqrt(x ** 2 + y ** 2 + z ** 2)
if magnitude >= SHAKE_THRESHOLD_G:
print(f" [accel] reading={reading} magnitude={magnitude:.3f}g -> শেক ডিটেক্ট হলো!")
else:
print(f" [accel] reading={reading} magnitude={magnitude:.3f}g -> স্বাভাবিক")
accel_stream = Stream("accelerometer")
accel_stream.subscribe(on_accelerometer_reading)
# চারটি সিমুলেটেড রিডিং -- বিশ্রামরত অবস্থায় মহাকর্ষের কারণে ম্যাগনিটিউড প্রায় ১g থাকে
readings = [
(0.0, 0.0, 1.0), # স্থির -- শুধু মহাকর্ষ
(0.1, -0.05, 0.98), # সামান্য নড়াচড়া -- এখনও স্বাভাবিক
(2.5, -1.8, 0.9), # হঠাৎ ঝাঁকুনি -- থ্রেশহোল্ড পার হবে
(0.05, 0.02, 1.01), # আবার স্থির
]
for r in readings:
accel_stream.emit(r)
(2.5, -1.8, 0.9)-এর ম্যাগনিটিউড $\sqrt{2.5^2 + 1.8^2 + 0.9^2} \approx 3.21$g —
থ্রেশহোল্ড ২.০g-এর অনেক বেশি, তাই এটিই একমাত্র "শেক ডিটেক্ট হলো!" প্রিন্ট করে। বাকি তিনটি রিডিংয়ের ম্যাগনিটিউড
১g-এর কাছাকাছি (বিশ্রামরত অবস্থায় মহাকর্ষের প্রত্যাশিত মান), তাই স্বাভাবিক থাকে — এটি real, সাথে সাথে গণনা,
কোনো হার্ডকোড করা ফলাফল নয়।
৩ · বায়োমেট্রিক — একটি ডিটারমিনিস্টিক বুলিয়ান গেট
ফিঙ্গারপ্রিন্ট বা ফেস আইডি সেন্সর স্ট্রিম নয় — এটি একটি একবারের যাচাই যা সফল অথবা ব্যর্থ হয়। বাস্তব সেন্সরের
হার্ডওয়্যার ম্যাচিং অ্যালগরিদম Pyodide-এ সিমুলেট করা সম্ভব নয়, তাই আমরা ম্যাচের ফলাফল স্পষ্টভাবে
ডিটারমিনিস্টিক match_outcome হিসেবে দিচ্ছি — L41-এর request_permission-এর মতোই,
র্যান্ডম নয়।
# বায়োমেট্রিক অথেন্টিকেশন -- একটি সাধারণ ডিটারমিনিস্টিক বুলিয়ান গেট
BIOMETRIC_ENROLLED = {
"fingerprint": True,
"face": True,
}
def biometric_auth(method, match_outcome):
"""match_outcome ডিটারমিনিস্টিকভাবে দেওয়া হয় -- real সেন্সর ম্যাচের ফলাফল সিমুলেট করতে, র্যান্ডম নয়"""
if not BIOMETRIC_ENROLLED.get(method, False):
print(f" [biometric] {method:12s} -> এই ডিভাইসে enrolled ডেটা নেই, অথেন্টিকেশন ব্যর্থ")
return False
if match_outcome:
print(f" [biometric] {method:12s} -> মিলেছে, অথেন্টিকেশন সফল")
return True
else:
print(f" [biometric] {method:12s} -> মেলেনি, অথেন্টিকেশন ব্যর্থ")
return False
print("== ফিঙ্গারপ্রিন্ট ম্যাচ হলো ==")
biometric_auth("fingerprint", True)
print("\n== ফেস আইডি মেলেনি ==")
biometric_auth("face", False)
print("\n== এই ডিভাইসে enrolled নেই এমন মেথড ==")
biometric_auth("iris", True)
সেন্সর ও বায়োমেট্রিক দুটোই ডিভাইস ফিচার, কিন্তু তাদের সঠিক প্রোগ্রামিং-মডেল ভিন্ন — অ্যাক্সিলারোমিটার/
জাইরোস্কোপ একটি ক্রমাগত Stream, যেখানে প্রতিটি রিডিং সাথে সাথে প্রসেস হয়; বায়োমেট্রিক একটি
একবারের ডিটারমিনিস্টিক গেট, ঠিক পারমিশনের মতো। সঠিক মডেল বেছে নেওয়াই সঠিক সিমুলেশনের প্রথম ধাপ।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
অ্যাক্সিলারোমিটার রিডিংকে একটি সাধারণ ফাংশন-কল (যেমন get_reading()) হিসেবে না বানিয়ে
একটি Stream হিসেবে বানানো হলো কেন?
কারণ অ্যাক্সিলারোমিটার একবারে একটি মান দিয়ে থেমে যায় না — এটি প্রতি মুহূর্তে নতুন ডেটা পাঠাতে থাকে।
একটি সাধারণ ফাংশন-কল শুধু একবার কল করা মুহূর্তের অবস্থা জানায়, কিন্তু Stream-এর
subscribe() একবার সেট করলেই ভবিষ্যতের সব রিডিং স্বয়ংক্রিয়ভাবে সাবস্ক্রাইবারের কাছে পৌঁছায় —
এটিই "শেক ডিটেকশন"-এর মতো ক্রমাগত পর্যবেক্ষণ সম্ভব করে।
প্র ০২
SHAKE_THRESHOLD_G যদি ১.০ (২.০-এর বদলে) সেট করা হতো, উপরের চারটি রিডিংয়ের মধ্যে কতগুলো
"শেক" হিসেবে ধরা পড়তো?
প্রথম রিডিংয়ের ম্যাগনিটিউড ঠিক ১.০g — >= শর্তে এটিও শেক হিসেবে ধরা পড়তো। চতুর্থ রিডিংয়ের
ম্যাগনিটিউডও প্রায় ১.০১g, তাই সেটিও থ্রেশহোল্ড পার করতো। ফলে চারটির মধ্যে তিনটি (প্রথম, তৃতীয়, চতুর্থ)
"শেক" হিসেবে ধরা পড়তো — শুধু দ্বিতীয়টি (≈০.৯৮৬g) বাদ যেতো। এটি দেখায় থ্রেশহোল্ড খুব কম রাখলে স্বাভাবিক
মহাকর্ষকেও ভুলভাবে "শেক" ধরে ফেলা যায় (false positive)।
প্র ০৩
বায়োমেট্রিক কোড সেলে match_outcome কেন সরাসরি কোডে True/False হিসেবে দেওয়া হলো,
Python-এর random মডিউল দিয়ে র্যান্ডম সিমুলেট করা হলো না কেন?
এই কোর্সের একটি মূল নিয়ম হলো — সব সিমুলেশন ডিটারমিনিস্টিক হতে হবে, যাতে প্রতিবার রান করলে ঠিক একই আউটপুট আসে (reproducible)। র্যান্ডম মিলিয়ে দিলে প্রতিবার ফলাফল বদলে যেতো, তখন কোনো নির্দিষ্ট আউটপুট নিয়ে আলোচনা করা বা শেখানো সম্ভব হতো না — real OS-এর সিদ্ধান্ত এখানে explicit প্যারামিটার দিয়ে "দেওয়া" হয়, অনুমান করা হয় না।
অনুশীলন
-
চিন্তা করুন: জাইরোস্কোপ (ঘূর্ণন গতি মাপে) ও অ্যাক্সিলারোমিটার (রৈখিক ত্বরণ মাপে) — দুটোই
একই
Streamক্লাস দিয়ে মডেল করা যাবে কিনা, নাকি আলাদা কিছু দরকার হবে বলে আপনার মনে হয়?একই
Streamক্লাসই যথেষ্ট —Streamনিজে ডেটার আকৃতি সম্পর্কে কিছু জানে না, শুধু মান emit ও deliver করে। জাইরোস্কোপের জন্য একটি আলাদাStream("gyroscope")ইনস্ট্যান্স বানিয়ে ভিন্ন আকৃতির রিডিং (যেমন rotation rate) emit করলেই চলবে — সাবস্ক্রাইবার ফাংশনই ঠিক করবে সেই ডেটা দিয়ে কী গণনা করা হবে। -
পরীক্ষা করুন: উপরের অ্যাক্সিলারোমিটার কোড সেলে
readingsলিস্টে একটি পঞ্চম রিডিং(0.0, 3.0, 0.0)যোগ করুন এবংaccel_stream.emit((0.0, 3.0, 0.0))কল করে দেখুন এটি শেক হিসেবে ধরা পড়ে কিনা, এবং কেন।ম্যাগনিটিউড হবে $\sqrt{0^2 + 3.0^2 + 0^2} = 3.0$g, যা থ্রেশহোল্ড ২.০g-এর চেয়ে বেশি — তাই এটিও "শেক ডিটেক্ট হলো!" প্রিন্ট করবে। এটি নিশ্চিত করে ডিটেকশন লজিক শুধু নির্দিষ্ট অক্ষে (x) নয়, বরং তিনটি অক্ষের সম্মিলিত ম্যাগনিটিউডের ওপর কাজ করে — যেকোনো দিকের হঠাৎ ঝাঁকুনি ধরতে পারে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ অ্যাপ লাইফসাইকেল, মোবাইল UI/UX, MVVM/MVI আর্কিটেকচার, নেটিভ বনাম ক্রস-প্ল্যাটফর্ম, নেভিগেশন, স্টেট ম্যানেজমেন্ট, লোকাল স্টোরেজ, ডিভাইস ফিচার ও ডিপ্লয়মেন্ট — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
-
Full-Stack Web Frameworks কোর্স সহোদর কোর্স
ওয়েবে
DeviceMotionEvent-এর মতো API দিয়ে সীমিত সেন্সর অ্যাক্সেস মেলে (এবং সব ব্রাউজারে সমর্থিত নয়) — মোবাইলে নেটিভ সেন্সর API অনেক বেশি নির্ভরযোগ্য ও উচ্চ-ফ্রিকোয়েন্সি ডেটা দেয়। - সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics, Full-Stack Web Frameworks ও Mobile App Development — সব এক জায়গায়।