MicrocosmWorksחדשנות ותכנון קוסמוס דיגיטלי
אודותצור קשר
MicrocosmWorksמחדשים ומתכננים קוסמוס דיגיטלי

מספקים פתרונות IT חשובים. אנו נלהבים מטכנולוגיה, אבטחה ועוזרים לעסקים לצמוח באמצעות תשתית IT אמינה וחדשנית.

[email protected]
+91 7011868196
New Delhi, India

מרכז צמיחה AI

מרכז AIחדשנות סטארטאפמאיץ ארגוני

פתרונות

כל הפתרונותאפליקציות בריאות וכושרפלטפורמת וידאו AIפיתוח סוכני AI

משאבים

תובנותמדריכי תעשייהתוכניות מקרה שימושתבניות ארכיטקטורהמחקרי מקרה

חברה

אודותינוצור קשרהעבודה שלנו

שירותים

ייעוץ דיגיטליתשתית ענןפיתוח SaaSפיתוח AIטכנולוגיית וידאו
פיתוח ERPהתאמה אישית של Zohoפיתוח Odooאינטגרציה של Salesforceפיתוח CRM מותאם אישית
אינטגרציה של QuickBooksפתרונות IoTפיתוח בלוקצ'יין
ייעוץ סייברתמיכה טכנית - L3

© 2026 MicrocosmWorks. כל הזכויות שמורות.

מדיניות פרטיותתנאי שירות
חזרה לתובנות
AI Development

שליטה ב-GoPro דרך WiFi ב-Flutter: ה-Open GoPro HTTP API, מנגנוני Keep-Alive, והעברת קבצים בסטרימינג

התחברות ל-GoPro דרך WiFi מ-Flutter באמצעות ה-Open GoPro HTTP API, עם מנגנוני keep-alive והעברת קבצים בסטרימינג.

Saurav Kumar Gupta's image Saurav Kumar Gupta
•
July 17, 2026
•
עודכן August 19, 2026
•
2 min read
GoPro camera connected to a Flutter app over Wi-Fi for remote control and media file transfer..webp
2 min read

אין SDK, אין "ריקוד צימוד" של BLE — רק ה-IP הקבוע של המצלמה, לקוח Dio, וכמה "מלכודות" שיכולות להרוס לכם את היום בשקט אם לא תכירו אותן.

למה WiFi, ולא Bluetooth?

רוב המדריכים בנושא "התחברות ל-GoPro" מתחילים עם Bluetooth Low Energy: סריקה, צימוד, החלפת מאפייני GATT, ואז שימוש ב-BLE כדי להפעיל WiFi, ואז העברה דרך WiFi בכל מקרה. זה עובד, אבל זה כרוך בהרבה טקסים — ו-BLE איטי ומסורבל עבור הדבר שהמשתמשים באמת רוצים, והוא לשלוט במצלמה ולהוריד ממנה את הצילומים במהירות.

לכן באפליקציה זו אנו מדלגים על BLE לחלוטין. המשתמש מצטרף לנקודת הגישה של ה-WiFi של ה-GoPro מהגדרות הטלפון שלו, ומרגע זה והלאה הכל הוא HTTP רגיל מול ה-Open GoPro API של המצלמה בכתובת קבועה:

http://10.5.5.9:8080

שליטה במצלמה, הגדרות, רישום מדיה, והורדת קבצים הם כולם בקשות HTTP GET ל-IP זה. כל האינטגרציה היא 100% Dart עם חבילת dio — אפס Kotlin/Swift מקורי. זו הכותרת, וכפי שנראה בסוף, זה גם המקום היחיד שבו העיצוב שביר.

התהליך:

הצטרפות ל-GoPro WiFi AP (הגדרות טלפון)
   → איתור שהמצלמה נגישה (בדיקת TCP ל-10.5.5.9:8080)
   → התחברות + זיהוי דגם (GET /gopro/camera/state)
   → הפעלת טיימרים של keep-alive (30 שניות) + בדיקת מצב (5 שניות)
   → שליטה: צמצם / מצב / הגדרות
   → רישום מדיה (GET /gopro/media/list)
   → הורדה עם turbo + התקדמות (dio.download)

1. איתור המצלמה ללא הרשאות סריקת WiFi

האינסטינקט הראשון הוא לספור WiFi SSIDs ולחפש אחד שמתחיל ב-GoPro. אל תעשו זאת — זה גורר איתו הרשאות מיקום ו-WiFi-scan APIs ספציפיים לפלטפורמה.

GoPro תמיד נמצאת באותו IP, כך שהאיתור הוא פשוט: האם אני יכול לפתוח TCP socket ל-10.5.5.9:8080? ללא HTTP, ללא הרשאות, ללא ניתוח SSID.

static const String goProIp = '10.5.5.9';
static const int goProPort = 8080;
static const Duration checkInterval = Duration(seconds: 2);
static const Duration connectionTimeout = Duration(seconds: 3);

Future<void> _checkConnection() async {
  try {
    final socket = await Socket.connect(goProIp, goProPort, timeout: connectionTimeout);
    socket.destroy();
    if (_currentState != GoProWifiState.connected) {
      _currentState = GoProWifiState.connected;
      _connectionStateController.add(_currentState);
    }
  } catch (e) {
    if (_currentState != GoProWifiState.disconnected) {
      _currentState = GoProWifiState.disconnected;
      _connectionStateController.add(_currentState);
    }
  }
}

אנו מבצעים בדיקה (poll) כל 2 שניות ומשדרים שינויי מצב על פני stream. מסך החיבור נרשם ומשתנה למצב "מחובר" ברגע שה-socket נפתח. מכיוון שאין גילוי התקנים אמיתי, השירות מסנתז CameraDevice וירטואלי (מבוסס על ה-IP הקבוע) כך ששאר שכבת ההפשטה של המצלמה לא צריכה לדעת את ההבדל.

2. לקוח ה-HTTP: Dio ונקודות הקצה של Open GoPro

הלקוח הוא עטיפת Dio דקה. שימו לב ל-connect timeout הקצר (אתם ב-LAN — אם זה איטי, זה מקולקל) ו-receive timeout נדיב עבור קריאות בקרה:

static const String baseUrl = 'http://10.5.5.9:8080';

GoProHttpClient() {
  _dio = Dio(BaseOptions(
    baseUrl: baseUrl,
    connectTimeout: const Duration(seconds: 5),
    receiveTimeout: const Duration(seconds: 10),
    headers: {'Accept': 'application/json'},
  ));
}

ה-Open GoPro API אחיד להפליא: הכל הוא בקשת HTTP GET, אפילו פקודות ושינויי הגדרות. להלן נקודות הקצה שהאפליקציה הזו משתמשת בהן בפועל:

מטרהMethod + PathQuery
הפעלת צמצם (הקלטה / צילום)GET /gopro/camera/shutter/start—
עצירת צמצםGET /gopro/camera/shutter/stop—
Keep-aliveGET /gopro/camera/keep_alive—
מצב מלא (סטטוס + הגדרות)GET /gopro/camera/state—
הגדרת קבוצת קביעות מוגדרות מראשGET /gopro/camera/presets/set_groupid (1000/1001/1002)
שינוי הגדרהGET /gopro/camera/settingsetting, option
רשימת מדיהGET /gopro/media/list—
הורדת קובץGET /videos/DCIM/{dir}/{file}(bytes)
תמונה ממוזערת / screennailGET /gopro/media/thumbnail / .../screennailpath
העברת טורבוGET /gopro/media/turbo_transferp (1/0)
מחיקת קבוצת 360. (legacy)GET /gp/gpControl/command/storage/delete/groupp

פרט נחמד: גם משפחת ה-/gopro/... המודרנית וגם משפחת ה-/gp/gpControl/... הישנה קיימות במקביל. הסרת קבצי קבוצות chaptered.360/GS, שה-API החדש אינו מסוגל לטפל בהם בצורה נקייה, היא המצב היחיד שבו אנו פונים לנקודת הקצה הישנה.

תבנית הריפוי העצמי של 403 (הטריק הנקי ביותר כאן)

אינך יכול לדעת מראש אילו הגדרות תקפות במצב הנוכחי של המצלמה — רזולוציה חוקית ב-16:9 אינה חוקית ב-9:16, אפשרויות FPS תלויות ברזולוציה, וכן הלאה. GoPro מספרת לך את זה בדרך הקשה: היא מחזירה HTTP 403 עם רשימת האפשרויות שהיו תקפות, ו-HTTP 500 כשהיא רק עסוקה לרגע.

אז אנו מנתחים את גוף ה-403 לשגיאה מטיפוס ספציפי ומנסים שוב את ה-500:

Future<void> setSetting(int settingId, int optionValue) async {
  const maxRetries = 3;
  for (int attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      await _dio.get('/gopro/camera/setting',
          queryParameters: {'setting': settingId, 'option': optionValue});
      return;
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.badResponse) {
        final statusCode = e.response?.statusCode;
        if (statusCode == 403) {
          final body = e
.response?.data;
          List<int> availableIds = [];
          if (body is Map<String, dynamic>) {
            final options = body['available_options'];
            if (options is List) {
              availableIds = options
                  .whereType<Map<String, dynamic>>()
                  .map((o) => o['id'] as int? ?? -1)
                  .where((id) => id >= 0)
                  .toList();
            }
          }
          throw SettingRejectedError(
            settingId: settingId, rejectedOption: optionValue,
            availableOptionIds: availableIds);
        }
        if (statusCode == 500 && attempt < maxRetries) {
          await Future.delayed(Duration(milliseconds: attempt * 500)); // linear backoff
          continue;
        }
      }
      rethrow;
    }
  }
}

האפשרויות המדווחות של המצלמה משמשות את שכבת השירות כדי לשכתב את רשימת היכולות שלה אם היא מזהה SettingRejectedError. עבור כל דגם, אנו מתחילים עם טבלת יכולות אופטימית וקוד קשיח, ומאפשרים למצלמה לתקן אותנו לאורך זמן הריצה. זה מעשי יותר ומתקן את עצמו מאשר לנסות לדמות מראש את כל מטריצת ההגדרות של GoPro.

3. Keep-alive: הטיימר שאסור לשכוח

לאחר כ-60 שניות של חוסר פעילות, ה-GoPro WiFi AP מסיים את הסשן שלכם. החיבור התקין שלכם מתנתק בשקט באמצע סשן אם לא תדחפו אותו. לפיכך, אנו מפעילים שני טיימרים בעת חיבור:

  • Keep-alive כל 30 שניות → GET /gopro/camera/keep_alive
  • בדיקת סטטוס (Status poll) כל 5 שניות → GET /gopro/camera/state

void _startKeepAlive() {
  _keepAliveTimer?.cancel();
  _keepAliveTimer = Timer.periodic(
    GoProHttpConstants.keepAliveInterval, // 30s
    (_) => _sendKeepAlive(),
  );
}

Future<void> _sendKeepAlive() async {
  if (!isConnected || _isDisposed) return;
  try {
    await _httpClient.keepAlive();
  } catch (e) {
    if (!_isDisposed) _handleDisconnection(); // a failed keep-alive == we're disconnected
  }
}

Keep-alive שנכשל הוא גם אות הניתוק שלנו — זו הדרך האמינה ביותר לשים לב שהמשתמש יצא מטווח ה-WiFi.

4. שליטה במצלמה: צמצם, מצב, ו"ריקוד העדשות" של MAX 2

הצמצם הוא החלק הקל — התחלה, המתנה של 500 מילישניות, בדיקה חוזרת של המצב. צילום תמונה הוא אותו "צמצם/התחלה"; האם תקבלו וידאו או תמונה תלוי במצב הנוכחי.

מעבר מצבים הוא המקום שבו זה נהיה מעניין, כי "מצב" ב-GoPro MAX 2 הוא למעשה שתי הגדרות: קבוצת קביעות מוגדרות מראש (וידאו = 1000, תמונה = 1001, Timelapse = 1002) בתוספת עדשה (הגדרה 194: 0 = עדשה בודדת/HERO, 1 = 360). כדי לעשות זאת נכון נדרשו מספר כללים לא מובנים שהוטמעו בקוד:

  1. בצעו סריאליזציה (Serialize) לשינויי מצב. לחיצות מהירות על תת-מצבים נכנסות לתור דרך שרשרת Completer כך שהן לא יכולות להתחרות זו בזו למצב לא עקבי.
  2. עצרו את הצמצם תחילה. הקושחה (Firmware) דוחה שינויי קביעות מוגדרות מראש (preset) עם HTTP 400 בזמן הקלטה, ולכן אנו מפסיקים באופן יזום את הקידוד לפני המעבר.
  3. סדר ועיכובים חשובים. ב-MAX 2: הגדירו תחילה את קבוצת הקביעות המוגדרות מראש (preset group) (+400 אלפיות השנייה), ואז את העדשה באמצעות Setting 194 (+1500 אלפיות השנייה). ההשהיה של 1.5 שניות אמיתית — זוהי המעבר הפיזי של מכלול העדשות.
  4. סמכו על המצב המיועד (intended state), לא על המצב הנבדק (polled state). אנו עוקבים אחר _lastIntendedIs360 / _lastIntendedPresetGroup ומבצעים השוואה (diff) מול אלה, מכיוון שבדיקת הסטטוס (status poll) של 5 שניות יכולה להעניק לכם תמונת מצב מיושנת באמצע המעבר.

יש גם תכונה מוזרה ומתוחכמת של GoPro ששווה לציין: מזהי רזולוציה תלויים ביחס הגובה-רוחב. 4K היא אפשרות 1 ב-16:9, 109 ב-9:16, ו-112 ב-4:3. אז לאחר שינוי יחס הגובה-רוחב אנו מבצעים שאילתה חוזרת על היכולות — אותו ID אחרת היה אומר רזולוציה שונה.

5. רישום מדיה

GET /gopro/media/list מחזיר מבנה מקובץ לפי תיקיות עם מפתחות קצרים וידועים לשמצה: d = תיקייה, fs = קבצים, n = שם קובץ, s = גודל, cre/mod = חותמות זמן (timestamps) של שניות יוניקס, g = מזהה קבוצה, glrv = גודל הפרוקסי ברזולוציה נמוכה.

final media = response['media'] as List<dynamic>?;
for (final dir in media) {
  final directory = dirMap['d'] as String? ?? '';
  final filesList = dirMap['fs'] as List<dynamic>?;
for (final fileJson in filesList) {
    files.add(GoProCameraFile.fromJson(directory, fileMap));
  }
}
files.sort((a, b) => b.modifiedAt.compareTo(a.modifiedAt)); // newest first

סוג המדיה נלמד מקידומת שם הקובץ + סיומת: GS = וידאו 360, GX = וידאו HERO, GT = timelapse, .360 = וידאו כדורי, .GPR/.RAW = תמונת RAW. לקידומות אלה יש חשיבות בהמשך, מכיוון שקבצי 360. מתנהגים אחרת מכל השאר.

6. הורדות בסטרימינג: טורבו, התקדמות וקבצים נלווים

העברת הבתים בפועל היא dio.download() ישירות לנתיב קובץ, עם receive timeout ארוך מכיוון שקליפ 360. של 1 GB לוקח זמן:

Future<void> downloadMediaToFile(
  String directory, String filename, String destPath,
  {void Function(int received, int total)? onProgress}) async {
  await _dio.download(
    '/videos/DCIM/$directory/$filename',
    destPath,
    onReceiveProgress: onProgress,
    options: Options(receiveTimeout: const Duration(minutes: 30)),
  );
}

סביב ליבה זו, שירות ההעברה מתזמר את הפרטים המעשיים:

העברת טורבו. לפני ההורדה אנו מפעילים מצב טורבו (turbo_transfer?p=1) — GoPro עוברת ל-WiFi בתדר 5 GHz להעברות מהירות יותר באופן ניכר — ואנו מכבים אותו בבלוק finally כך שהוא תמיד מנוקה, גם במקרה של שגיאה.

התקדמות + מהירות, מווסתות. חישוב מהירות העברה בכל קריאה חוזרת (callback) של onReceiveProgress הוא בזבזני ולא יציב, לכן אנו מחשבים אותו מחדש רק כל 500 מילישניות:

onProgress: (received, total) {
  if (_isCancelled) return;
  final now = DateTime.now();
  String? speed;
  if (now.difference(lastSpeedUpdate).inMilliseconds >= 500) {
    final bytesPerSecond = (received - lastBytes) /
        (now.difference(lastSpeedUpdate).inMilliseconds / 1000);
    speed = _formatSpeed(bytesPerSecond.round());
    lastSpeedUpdate = now; lastBytes = received;
  }
  _transferController.add(GoProFileTransfer(
    status: FileTransferStatus.downloading,
    progress: total > 0 ? received / total : 0,
    speed: speed, currentBytes: received
, totalBytes: total));
}

ביטול הוא דגל _isCancelled שיתופי; בביטול אנו מוחקים את הקובץ החלקי ופולטים אירוע ביטול.

קבצים נלווים. זה החלק שלא תמצאו בתיעוד. עבור MP4. בעל עדשה בודדת, אנו מושכים גם את ה-LRV. (פרוקסי ברזולוציה נמוכה ש-GoPro מקליטה לצד הווידאו המלא) ואת התמונה הממוזערת THM., ואז מאנדקסים את ה-LRV להפעלה מהירה בתוך האפליקציה. שתי "מלכודות שמות" מורגשות כאן:

  • HERO8+ משנה את שם הפרוקסי: GX######.MP4 / GH######.MP4 הופך ל-GL######.LRV (התו השני מתחלף ל-L), עם חלופה באותו שם עבור מצלמות ישנות יותר.
  • קבצי 360. אינם תומכים כלל בנקודות הקצה של תמונה ממוזערת (thumbnail) או screennail. אנו חוזרים לקובץ הנלווה THM., וכמוצא אחרון מורידים את הקובץ עצמו. ומכיוון ש-360. הוא בעל "עין דג כפולה" (dual-fisheye), אנו חותכים את התמונה הממוזערת לעדשה הקדמית — באמצעות compute() ב-isolate ברקע, כך שממשק המשתמש (UI) לעולם לא נתקע.

מצב ההעברה נחשף דרך Riverpod notifier שנרשם ל-streams של קבצים והעברות, משאיר העברה שהסתיימה גלויה למשך 2 שניות, ומציג שגיאות למשך 5 שניות. ראוי לציין: בקרה והעברת קבצים משתמשות בשני לקוחות Dio נפרדים לאותה מצלמה, כך שהורדה יכולה להתבצע בזמן שבדיקת הסטטוס (status poll) וה-keep-alive ממשיכים.

7. שתי "מלכודות" שלא נמצאות בשום מדריך

HTTP בטקסט רגיל (Cleartext) חסום כברירת מחדל באנדרואיד מודרני

http://10.5.5.9:8080 הוא טקסט רגיל (plaintext), ואנדרואיד 9+ חוסם תעבורת טקסט רגיל (cleartext) מהקופסה. עליכם לאפשר זאת במפורש:

<!-- AndroidManifest.xml -->
<application android:usesCleartextTraffic="true" ... >

בתוספת network_security_config.xml שההגדרה הבסיסית שלו מאפשרת cleartext (אתם עדיין יכולים לאכוף HTTPS עבור דומייני הענן/backend שלכם). תפספסו את זה וכל קריאת GoPro תיכשל עם שגיאת חיבור מבלבלת.

קישור הרשת החסר — הנקודה השבירה בעיצוב

הנה החלק הכנה. באנדרואיד מודרני, כאשר אתם מצטרפים ל-GoPro AP, הטלפון בדרך כלל שומר את החיבור הסלולרי כרשת ברירת המחדל מכיוון של-GoPro AP אין אינטרנט. המשמעות היא שבקשת HTTP ל-10.5.5.9 יכולה להיות מנותבת דרך החיבור הסלולרי ולהיכשל — גם אם אתם "מחוברים" למצלמה.

התיקון החזק הוא מקורי: קריאה ל-ConnectivityManager.bindProcessToNetwork(goProNetwork) לפני העברות ול-bindProcessToNetwork(null) לאחר מכן. לנתיב GoPro ב-Dart טהור אין מקבילה — וזה נוטה לעבוד בכל מקרה מכיוון שה-OS בדרך כלל מנתב את הכתובת הקבועה 10.5.5.9 ל-AP. אבל זה החלק השביר ביותר בארכיטקטורה, ובמכשירים/גרסאות אנדרואיד מסוימים זה הדבר הראשון שנשבר. אם אתם מפרסמים זאת, הוסיפו את קישור הרשת (network binding) — זהו המקום היחיד שבו הישארות ב-100% Dart עולה לכם באמינות.

לקחים שנלמדו

  1. אינכם זקוקים ל-BLE. אם המשתמש יכול להצטרף ל-AP, ה-Open GoPro HTTP API מעניק לכם שליטה מלאה והעברות מהירות ב-Dart טהור.
  2. אתרו באמצעות בדיקת TCP (TCP probe), לא סריקת SSID — ללא הרשאת מיקום, ללא קוד פלטפורמה.
  3. Keep-alive כל 30 שניות אחרת הסשן מתנתק. התייחסו ל-keep-alive שנכשל כאות הניתוק שלכם.
  4. תנו למצלמה לתקן אתכם. לולאת הריפוי העצמי של 403-available_options עדיפה על מידול כל מטריצת ההגדרות של GoPro.
  5. מעבר מצבים הוא מכונת מצבים (state machine) — בצעו לו סריאליזציה, עצרו הקלטה תחילה, כבדו את עיכובי מעבר העדשות, וסמכו על המצב המיועד (intended state) על פני המצב הנבדק (polled state).
  6. קבצים נלווים ומוזרויות 360. הם המקום שבו טמונות הבעיות — שינוי שם LRV, אין נקודת קצה לתמונה ממוזערת, חיתוך dual-fisheye.
  7. שתי "מלכודות" תשתית יפילו אתכם בשקט: תצורת תעבורת cleartext, ו(במיוחד) קישור התהליך לרשת המצלמה כדי שבקשות לא יברחו דרך החיבור הסלולרי.

התמורה: טלפון שמתחבר ל-GoPro דרך WiFi, מפעיל כל פקד, ומזרים גיגה-בייטים של צילומים ממנה עם התקדמות חיה — וכמעט הכל הוא רק Dart בנוי היטב המדבר עם http://10.5.5.9:8080.
 

FlutterGoProHTTP APIStreaming
Saurav Kumar Gupta's image

אודות המחבר

Saurav Kumar Gupta

AI & Cloud Solutions Expert at MicrocosmWorks

Building innovative AI-powered solutions and helping businesses transform through cutting-edge technology.

רוצים ללמוד עוד?

צרו קשר לדון כיצד נוכל לעזור ליישם פתרונות אלו עבור העסק שלכם.

צרו קשר

שאלות נפוצות

Yes. Once the phone is connected to the GoPro's WiFi network, the Open GoPro HTTP API allows you to control the camera, change settings, list media, and download files using standard HTTP requests.

GoPro automatically ends inactive WiFi sessions after about 60 seconds. Sending periodic keep-alive requests maintains the connection and prevents unexpected disconnections during camera control or file transfers.

Flutter apps can use the Open GoPro HTTP API with Dio for streaming downloads, enable Turbo Transfer for faster speeds, and track download progress in real time.

A 403 response indicates the requested setting isn't valid for the camera's current mode. The API also returns supported options, allowing the application to adapt automatically.

On Android, requests may be routed over cellular instead of the GoPro's WiFi network. Binding the app to the GoPro network improves connection reliability during camera control and media transfers.

Comments (0)

Share your thoughts and join the conversation

Leave a Comment

Your email will not be published

No comments yet

Be the first to share your thoughts!