آموزش تصویری

راهنمای فنی پیاده‌سازی و کالیبراسیون حسگرها با استفاده از SensorManager API

سخت است که گوشی‌های هوشمند امروزی را صرفاً «کامپیوتر» بنامیم؛ این دستگاه‌ها بسیار فراتر از نیاکان دسکتاپی خود عمل می‌کنند: می‌توانند دما را اندازه‌گیری کنند، ارتفاع شما از سطح دریا را بگویند، رطوبت هوا را تعیین کنند، و اگر ناگهان جهت‌یابی یا نحوه‌ی اثرگذاری گرانش را فراموش کنید، آن را نیز اصلاح خواهند کرد. همانطور که حدس زده‌اید، جادوی پشت همه‌ی این‌ها، حسگرهای داخلی (Onboard Sensors) دستگاه هستند. امروز قصد داریم تا با این حسگرها بهتر آشنا شویم.

کار با کلاس SensorManager

برای کار با حسگرهای سخت‌افزاری موجود در دستگاه‌های اندرویدی، باید از کلاس SensorManager استفاده کنید که از طریق متد استاندارد getSystemService به آن دسترسی پیدا می‌کنید:

SensorManager sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);

انواع حسگرهای سخت‌افزاری

قبل از شروع کار با هر حسگری، باید نوع آن را مشخص کنید. ساده‌ترین راه استفاده از کلاس Sensor است که همه‌ی انواع حسگرها را به صورت ثابت‌های (Constants) از پیش تعریف‌شده در خود دارد. بیایید نگاهی دقیق‌تر به آن‌ها بیندازیم:

  • Sensor.TYPE_ACCELEROMETER: یک شتاب‌سنج سه‌محوره که شتاب را در امتداد محورهای X، Y و Z گزارش می‌کند (واحد: متر بر مجذور ثانیه).
  • Sensor.TYPE_LIGHT: حسگر نور محیط که میزان روشنایی (Illuminance) را بر حسب لوکس (lux) گزارش می‌کند و معمولاً برای تنظیم خودکار روشنایی صفحه نمایش استفاده می‌شود.
  • Sensor.TYPE_AMBIENT_TEMPERATURE: یک دماسنج که دمای محیط را بر حسب درجه‌ی سانتی‌گراد گزارش می‌دهد.
  • Sensor.TYPE_PROXIMITY: حسگر مجاورت که فاصله‌ی بین دستگاه و کاربر را (بر حسب سانتی‌متر) نشان می‌دهد. این همان حسگری است که هنگام تماس تلفنی، صفحه‌نمایش را خاموش می‌کند. در برخی دستگاه‌ها عملکرد آن عملاً باینری است: «نزدیک» یا «دور».
  • Sensor.TYPE_GYROSCOPE: یک ژیروسکوپ سه‌محوره که سرعت زاویه‌ای دستگاه را در امتداد سه محور (واحد: رادیان بر ثانیه) گزارش می‌کند.
  • Sensor.TYPE_MAGNETIC_FIELD: یک مغناطیس‌سنج (مگنتومتر) که میدان مغناطیسی را در امتداد سه محور بر حسب میکروتسلا (µT) گزارش می‌دهد. این حسگر در دستگاه‌هایی با قطب‌نمای سخت‌افزاری وجود دارد.
  • Sensor.TYPE_PRESSURE: حسگر فشار اتمسفر (فشارسنج یا بارومتر) که فشار فعلی را بر حسب میلی‌بار (mbar) گزارش می‌دهد. با کمی دانش فیزیک می‌توانید ارتفاع را از این مقدار استخراج کنید یا به سادگی از متد SensorManager.getAltitude استفاده کنید.
  • Sensor.TYPE_RELATIVE_HUMIDITY: حسگر رطوبت نسبی که بر حسب درصد گزارش می‌شود. این حسگر در ترکیب با فشار می‌تواند به پیش‌بینی‌های اولیه‌ی آب و هوا کمک کند.
  • Sensor.TYPE_STEP_COUNTER (API 19+): یک گام‌شمار که تعداد گام‌ها را از آخرین راه‌اندازی دستگاه گزارش می‌دهد (فقط پس از راه‌اندازی مجدد، ریست می‌شود).
  • Sensor.TYPE_MOTION_DETECT (API 24+): یک حسگر تشخیص حرکت دستگاه. اگر دستگاه برای تقریباً ۵ تا ۱۰ ثانیه در حال حرکت باشد، مقدار ۱ را برمی‌گرداند (احتمالاً پایه‌ای برای یک ویژگی ضد سرقت).
  • Sensor.TYPE_HEART_BEAT (API 24+): یک حسگر تشخیص ضربان قلب.
  • Sensor.TYPE_HEART_RATE (API 20+): حسگر ضربان قلب که تعداد ضربان در دقیقه (BPM) را گزارش می‌دهد. توجه: این حسگر به مجوز android.permission.BODY_SENSORS در مانیفست نیاز دارد.

حسگرهای مجازی: زندگی آسان‌تر برای توسعه‌دهندگان

حسگرهای ذکر شده در بالا، حسگرهای سخت‌افزاری هستند و اغلب بدون هیچ گونه فیلتر یا نرمال‌سازی مقادیر، مستقل از یکدیگر عمل می‌کنند. برای «آسان کردن زندگی توسعه‌دهندگان»، گوگل چندین حسگر مجازی (Virtual Sensors) را معرفی کرده که نتایج ساده‌تر و دقیق‌تری را ارائه می‌دهند.

به عنوان مثال، حسگر Sensor.TYPE_GRAVITY خروجی شتاب‌سنج را از یک فیلتر پایین‌گذر (Low-Pass Filter) عبور می‌دهد و جهت و اندازه‌ی فعلی گرانش را در امتداد سه محور برمی‌گرداند، در حالی که Sensor.TYPE_LINEAR_ACCELERATION یک فیلتر بالاقدر (High-Pass Filter) اعمال می‌کند تا مقادیر شتاب (با حذف اثر گرانش) را در امتداد سه محور به دست آورد.

نکته: برای ساخت یک اپلیکیشن با داده‌های حسگر، نیازی به بیرون رفتن یا پرش از ارتفاع نیست؛ شبیه‌ساز (Emulator) اندروید SDK می‌تواند هر مقدار آزمایشی را به برنامه‌ی شما تزریق کند.

لیست کردن حسگرهای موجود

برای یافتن اینکه چه حسگرهایی روی یک گوشی هوشمند موجود هستند، از متد getSensorList آبجکت SensorManager استفاده کنید:

List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);

لیست حاصل شامل تمام حسگرهای پشتیبانی‌شده، هم سخت‌افزاری و هم مجازی خواهد بود. علاوه بر این، برخی از آن‌ها ممکن است دارای چندین پیاده‌سازی مستقل باشند که در مصرف برق، تأخیر (Latency)، محدوده‌ی عملیاتی و دقت متفاوت هستند.

برای دریافت لیست تمام حسگرهای موجود از یک نوع خاص، ثابت مناسب آن را مشخص کنید. به عنوان مثال، کد زیر:

List<Sensor> pressureList = sensorManager.getSensorList(Sensor.TYPE_PRESSURE);

تمام حسگرهای بارومتریک (فشارسنج) موجود را برمی‌گرداند. پیاده‌سازی‌های سخت‌افزاری در ابتدا و پیاده‌سازی‌های مجازی در انتهای لیست قرار می‌گیرند (این قانون برای همه‌ی انواع حسگرها صادق است).

انتخاب حسگر پیش‌فرض

برای به دست آوردن پیاده‌سازی پیش‌فرض حسگر (این حسگرها برای وظایف رایج مناسب هستند و از نظر مصرف برق متعادلند)، از متد getDefaultSensor استفاده کنید:

Sensor defPressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);

اگر پیاده‌سازی سخت‌افزاری برای نوع حسگر درخواستی وجود داشته باشد، به صورت پیش‌فرض بازگردانده می‌شود. در غیر این صورت، نسخه‌ی مجازی جایگزین می‌شود؛ و اگر دستگاه اصلاً حسگر مناسبی نداشته باشد، getDefaultSensor مقدار null را برمی‌گرداند.

دریافت خوانش حسگر

برای دریافت رویدادهای تولید شده توسط حسگر، باید یک پیاده‌سازی از واسط SensorEventListener را با استفاده از همان SensorManager ثبت کنید. این کار در عمل یک خط کد است:

Sensor defPressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);

sensorManager.registerListener(workingSensorEventListener, defPressureSensor, SensorManager.SENSOR_DELAY_NORMAL);

در اینجا، ما فشارسنجی را که قبلاً به دست آمده، با استفاده از متد registerListener ثبت می‌کنیم و حسگر را به عنوان پارامتر دوم و نرخ به‌روزرسانی داده‌ها را به عنوان پارامتر سوم ارسال می‌کنیم.

کلاس SensorManager چهار ثابت استاتیک برای تعریف نرخ به‌روزرسانی دارد:

  • SensorManager.SENSOR_DELAY_FASTEST: سریع‌ترین نرخ به‌روزرسانی ممکن.
  • SensorManager.SENSOR_DELAY_GAME: نرخی که معمولاً توسط بازی‌های فعال با ژیروسکوپ استفاده می‌شود.
  • SensorManager.SENSOR_DELAY_NORMAL: نرخ به‌روزرسانی پیش‌فرض.
  • SensorManager.SENSOR_DELAY_UI: نرخی مناسب برای به‌روزرسانی رابط کاربری.

توجه: مشخص کردن فرکانس به‌روزرسانی، تضمین نمی‌کند که دقیقاً همان نرخ را برآورده کند. در عمل، ممکن است داده‌های حسگر سریع‌تر یا کندتر از حد درخواست‌شده برسند.

پارامتر اول (که هنوز مورد بحث قرار نگرفته) پیاده‌سازی واسط SensorEventListener است که در آن مقادیر واقعی را دریافت می‌کنیم:

private final SensorEventListener workingSensorEventListener = new SensorEventListener() {
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // نظارت بر تغییرات دقت
    }

    public void onSensorChanged(SensorEvent event) {
        // دریافت فشار اتمسفر بر حسب میلی‌بار
        double pressure = event.values[0];
    }
};

متد onSensorChanged یک شیء SensorEvent دریافت می‌کند که تمام رویدادهای مربوط به حسگر را توصیف می‌کند: event.sensor یک مرجع به حسگر، event.accuracy دقت خوانش، event.timestamp زمان رویداد بر حسب نانوثانیه، و مهم‌تر از همه، آرایه‌ی event.values است. برای یک حسگر فشار، تنها یک مقدار ارائه می‌شود، در حالی که یک شتاب‌سنج سه مقدار (یکی برای هر محور) را عرضه می‌کند.

هرگاه دیگر به حسگر نیاز ندارید، باید آن را لغو ثبت (Unregister) کنید:

sensorManager.unregisterListener(workingSensorEventListener);

اندازه‌گیری فشار و ارتفاع

برای محاسبه‌ی ارتفاع فعلی از سطح دریا، می‌توانید از متد getAltitude استفاده کنید:

float altitude = SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, pressure);

اما این مقدار خوانشی ایده‌آل است که ارتفاع محل از سطح دریا را در نظر نمی‌گیرد. شما می‌توانید آن را به صورت دستی کالیبره کنید.

ایده این است که ارتفاع را نه از یک خوانش مطلق فشار، بلکه از اختلاف فشار بین نقاط شروع و پایان تخمین بزنید. برای این کار، باید ابتدا در روی زمین اولین خوانش فشار (initPressure) را بگیرید. سپس به پشت بام بروید و خوانش بعدی (pressure) را بگیرید.

اختلاف ارتفاع مورد نیاز (که در واقع همان اختلاف ارتفاع است) به سادگی محاسبه می‌شود:

float altitude = SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, pressure) - SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, initPressure);

اندازه‌گیری نور محیط

حسگر نور محیط جالب است؛ زیرا فراتر از گزارش میزان روشنایی بر حسب لوکس، می‌تواند ارزیابی کیفی از محیط نیز ارائه دهد. این یک خوانش می‌تواند به صورت زیر تفسیر شود:

...
if (!Float.isNaN(currentLight)) {
    String lightStr = "Sunny";
    if (currentLight <= SensorManager.LIGHT_CLOUDY) lightStr = "Night";
    else if (currentLight <= SensorManager.LIGHT_OVERCAST) lightStr = "Overcast";
    else if (currentLight <= SensorManager.LIGHT_SUNLIGHT) lightStr = "Partly cloudy";
}

همانطور که می‌بینید، سطوح زیادی وجود ندارد، اما برای مقاصد غیر هواشناسی کافی هستند.

اپلیکیشن اندازه‌گیری نیروی G (G-Force)

شتاب، نرخ تغییر سرعت است. در نتیجه، یک شتاب‌سنج اندازه‌گیری می‌کند که سرعت یک دستگاه در امتداد محورهای مختصات چقدر سریع تغییر می‌کند. برای اندازه‌گیری نیروی G نسبت به حالت سکون، باید شتاب‌ها را در امتداد هر سه محور جمع کرده و سهم گرانش (شتاب ناشی از سقوط آزاد) را حذف کنید.

در متد onSensorChanged اپلیکیشن اندازه‌گیری نیروی G، پس از خواندن شتاب در سه محور، ابتدا اندازه‌ی بردار (Vector Magnitude) را محاسبه می‌کنیم:

double a = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2));

سپس، شتاب ناشی از گرانش زمین را از آن مقدار کم می‌کنیم. مقدار گرانش زمین در SensorManager به عنوان STANDARD_GRAVITY تعریف شده است (این کلاس همچنین ثابت‌هایی برای ماه، پلوتو و هر سیاره‌ی دیگری در منظومه‌ی شمسی ارائه می‌دهد):

acceleration = Math.abs((float)(a-calibration)); // calibration = SensorManager.STANDARD_GRAVITY

نکته‌ی مهم: از آنجا که شتاب‌سنج‌ها به عنوان حسگرهای گرانش نیز عمل می‌کنند، اگر گرانش را در کد خود باقی بگذارید (یعنی متغیر کالیبراسیون را کم نکنید)، شتاب محلی سقوط آزاد سیاره‌ای را که روی آن هستید، می‌خوانید. روی زمین این مقدار ۹.۸۱ متر بر مجذور ثانیه (۱ جی) است.

جمع‌بندی

امروز اجزای ضروری هر گوشی هوشمند مدرن، یعنی حسگرهای آن، را بررسی کردیم. این تراشه‌های کوچک زندگی روزمره را بسیار آسان‌تر می‌کنند و با کمی خلاقیت می‌توانید یک گوشی اندرویدی را به یک دستیار کاربردی برای کشف دنیاهای دیگر تبدیل کنید.

بیشتر بخوانید:

رفع خطایDon’t Cover the Earphone Area در اندروید
چگونه گوشی اندرویدی را بدون روت کردن شخصی سازی کنیم؟
نحوه مشاهده شماره‌های مسدود شده در اندروید و مدیریت آن ها

وحید خاکپور

متولد 76 . مبتدی در همه چیز. علاقه مند به یادگیری و به اشتراک گذاشتن تجربه های مفید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *