دریافت و نمایش سنسورها
این نمونه JSON تولیدشده توسط عملکرد شروع سنسور را با app.watchVar
نمایش میدهد. دکمهها عملکردهای تعریفشدهٔ پنل را اجرا میکنند؛ نوع سنسور و
فاصلهٔ نمونهها را در همان عملکرد انتخاب میکنید و نیاز به تغییر کد ندارید.
افزودن نمونه
- ویجت Flutter سفارشی را اضافه کنید و فایل sensors.dart را دانلود و بارگذاری کنید.
- در پارامترهای JSON این مقدار را قرار دهید:
{"title":"سنسور حرکت","variable":"sensorData"}
- در فهرست عملکردهای همین ویجت، با شناسهٔ
startSensorعملکرد شروع سنسور را تعریف کنید: شناسهٔ دریافتmotion، نوع شتابسنج با گرانش، متغیر خروجیsensorDataو فاصلهٔ دادهها ۲۰۰ میلیثانیه. - با شناسهٔ
stopSensorعملکرد توقف سنسور را تعریف و شناسهٔ دریافت راmotionقرار دهید. - ارتفاع ویجت را «به اندازه محتوا» بگذارید، صفحه را ذخیره و خروجی جدید بسازید. دکمهٔ شروع را بزنید؛ متغیر خروجی را لازم نیست از قبل بسازید.
نام variable باید با متغیر خروجی عملکرد یکی باشد. startSensor و
stopSensor شناسههای اتصال کد به عملکردهای ویجتاند؛ motion شناسهٔ نشست
سنسور در همان صفحه است. دکمه بدون عملکرد متناظر غیرفعال میشود. شرطهای
عملکرد نیز اعمال میشوند؛ برای این مثال شرطی که مانع اجرا شود نگذارید.
انتخاب سنسور و واحدها
| نوع در پنل | مقدار sensor در خروجی | کاربرد و واحد |
|---|---|---|
| شتابسنج با گرانش | accelerometer | x/y/z بر حسب m/s²؛ مناسب تراز در حالت سکون |
| شتاب حرکت بدون گرانش | userAccelerometer | x/y/z بر حسب m/s²؛ حرکت دستگاه |
| ژیروسکوپ | gyroscope | سرعت زاویهای بر حسب rad/s؛ زاویه یا جهت شمال نیست |
| تشخیص تکان | shake | خروجی شتاب حرکت فقط هنگام عبور از آستانهٔ تکان |
برای ژیروسکوپ، نوع عملکرد startSensor را تغییر دهید. برای تکان، آستانهٔ
پیشفرض ۱۵ m/s² و فاصلهٔ مجاز تکانها ۱۰۰۰ میلیثانیه است. میتوانید عملکرد
«با دریافت داده / تکان» را مثلاً به افزایش شمارنده یا نمایش پیام وصل کنید؛
در حالت تکان، خروجی فقط با هر تکان معتبر بهروز میشود. در حالتهای پیوسته،
برای هر نمونهٔ دریافتشده عملکرد اجرا میشود؛ کار سنگین یا درخواست شبکه را
برای هر نمونه اجرا نکنید.
فاصلهٔ دادهها در پنل ۵۰ تا ۵۰۰۰ میلیثانیه است. زمان واقعی دریافت به دستگاه و پلتفرم وابسته است. دریافت مغناطیسسنج و فشارسنج در عملکرد پنل فعلاً گزینه ندارد؛ برای مغناطیسسنج نمونهٔ قطبنما را ببینید.
ساختار خروجی و خطا
{"status":"active","sensor":"accelerometer","id":"motion","simulated":false,"timestamp":1790000000000,"x":0,"y":0,"z":9.81,"magnitude":9.81}
| فیلد | معنا |
|---|---|
status | starting، active، paused یا error |
x، y، z | دادهٔ محورهای دستگاه؛ فقط در نمونهٔ داده وجود دارند |
magnitude | اندازهٔ بردار، یعنی sqrt(x²+y²+z²) |
timestamp | زمان انتشار، میلیثانیه از مبدأ Unix |
sensor، id | نوع و شناسهٔ نشست |
simulated | آیا دادهٔ شبیهسازی است |
error | در وضعیت خطا: مانند sensor_unavailable، no_sensor_data یا sensor_disconnected |
در عملکرد شروع، «عملکرد هنگام خطای سنسور» را برای پیام یا رفتار جایگزین تعریف
کنید. برنامه نباید وجود سختافزار را فرض کند. ویجت داده را فقط در وضعیت
active نمایش میدهد. دکمهٔ توقف ابتدا عملکرد توقف را اجرا میکند و سپس
متغیر خروجی همین نمونه را حذف میکند تا آخرین داده بهعنوان دریافت زنده
نمایش داده نشود. اگر توقف را از بیرون اجرا کنید، آخرین مقدار باقی میماند؛
برای پاککردن نمایش، آن متغیر را نیز حذف کنید.
چرخهٔ عمر و چند نمونه
نشست سنسور متعلق به صفحه است. هنگام پوشیدهشدن صفحه یا پسزمینه، دریافت مکث و هنگام برگشت از سر گرفته میشود؛ با بستهشدن صفحه آزاد میشود. شروع دوباره با همان شناسه در همان صفحه نشست قبلی را جایگزین میکند. شناسهٔ خالی در عملکرد توقف، همهٔ نشستهای همان صفحه را متوقف میکند.
برای چند دریافت همزمان، شناسه و متغیر خروجی مستقل بدهید. متغیر خروجی فقط
در حافظه است؛ نام آن نباید با متغیر دستگاه یا متغیر دیگری مشترک باشد.
حذف خود ویجت، نشست صفحه را متوقف نمیکند؛ اگر صفحه باز میماند، عملکرد
توقف را نیز اجرا کنید. StreamBuilder اشتراک نمایش داده را آزاد میکند.
پیشنمایش و پلتفرم
در عملکرد شروع، شبیهسازی پیشنمایش را فعال کنید و x/y/z را تعیین کنید. در حالت تکان، شبیهسازی با فاصلهٔ تکانها رویداد تولید میکند. این تنظیم فقط برای پیشنمایش عملکرد است؛ خروجی نصبشده از سختافزار واقعی استفاده میکند و خود کد ویجت سفارشی در شبیهساز پنل اجرا نمیشود.
پکیج sensors_plus: 6.1.2 در سازنده فعال است. وجود پکیج تضمین وجود سنسور
نیست؛ بهخصوص روی دسکتاپ و برخی دستگاهها باید حالت خطا را در نظر بگیرید.
وب به پشتیبانی مرورگر وابسته است و مغناطیسسنج این پکیج در مرورگرهای معمول
قابل استفاده نیست. جزئیات در
راهنمای رسمی sensors_plus
آمده است. افزودن تنظیم بومی جدید از داخل ویجت پشتیبانی نمیشود.
کد کامل
import 'package:flutter/material.dart';
import 'package:my_app/appeditor/custom/AppEditorWidgetContext.dart';
Widget buildCustomWidget(BuildContext context, AppEditorWidgetContext app) {
return SensorMonitor(app: app);
}
class SensorMonitor extends StatefulWidget {
const SensorMonitor({super.key, required this.app});
final AppEditorWidgetContext app;
State<SensorMonitor> createState() => _SensorMonitorState();
}
class _SensorMonitorState extends State<SensorMonitor> {
late String _variable;
late Stream<dynamic> _samples;
bool _busy = false;
void _bind() {
_variable = widget.app.params['variable'] as String? ?? 'sensorData';
_samples = widget.app.watchVar(_variable);
}
void initState() {
super.initState();
_bind();
}
void didUpdateWidget(covariant SensorMonitor oldWidget) {
super.didUpdateWidget(oldWidget);
if (_variable != (widget.app.params['variable'] as String? ?? 'sensorData') ||
!identical(oldWidget.app, widget.app)) _bind();
}
Future<void> _run(String name) async {
final app = widget.app;
final variable = _variable;
setState(() => _busy = true);
try {
await app.runAction(name);
// Sensor stop preserves its last sample. Clear only this output variable
// so this monitor does not display an old sample as a live measurement.
if (name == 'stopSensor' && app.mounted) await app.removeVar(variable);
} catch (_) {
app.showMessage('اجرای عملکرد سنسور ممکن نشد');
} finally {
if (mounted) setState(() => _busy = false);
}
}
String _number(dynamic value) => value is num && value.toDouble().isFinite
? value.toStringAsFixed(3) : '—';
Widget build(BuildContext context) => StreamBuilder<dynamic>(
stream: _samples,
builder: (context, snapshot) {
final data = snapshot.data is Map ? snapshot.data as Map : const {};
final status = data['status'];
final active = status == 'active';
final label = switch (status) {
'starting' => 'در حال شروع',
'active' => 'دریافت داده',
'paused' => 'دریافت مکث شده',
'error' => 'خطای سنسور: ${data['error'] ?? 'نامشخص'}',
_ => 'دریافت شروع نشده یا متوقف شده است',
};
return Card(child: Padding(
padding: const EdgeInsets.all(16),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Text(widget.app.params['title'] as String? ?? 'دادهٔ سنسور',
style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 8),
Text(label, textAlign: TextAlign.center),
if (data['simulated'] == true) const Text('دادهٔ شبیهسازیشده'),
Text('نوع: ${data['sensor'] ?? '—'}'),
if (active) ...[
Text('x: ${_number(data['x'])} y: ${_number(data['y'])} z: ${_number(data['z'])}',
textDirection: TextDirection.ltr),
Text('magnitude: ${_number(data['magnitude'])}',
textDirection: TextDirection.ltr),
Text('زمان نمونه: ${data['timestamp'] ?? '—'}'),
],
const SizedBox(height: 8),
Wrap(spacing: 12, children: [
ElevatedButton(onPressed: !_busy && widget.app.hasAction('startSensor')
? () => _run('startSensor') : null, child: const Text('شروع')),
OutlinedButton(onPressed: !_busy && widget.app.hasAction('stopSensor')
? () => _run('stopSensor') : null, child: const Text('توقف')),
]),
]),
));
},
);
}